From 98ccfc86fa78fb5dccbc0cd69eaf08af79b36113 Mon Sep 17 00:00:00 2001 From: Paymon Date: Fri, 3 Nov 2017 17:33:35 +0000 Subject: [PATCH] Initial commit --- .gitattributes | 63 + .gitignore | 267 ++ GCop.json | 57 + .../Attributes/RestrictToAttribute.cs | 26 + .../Binding/BasicViewDataContainer.cs | 7 + .../Binding/OliveModelBinder.cs | 206 ++ .../Extentions/@NamespaceConflictFix.cs | 122 + .../ControlValidationTranslation.cs | 61 + .../Extensions.HtmlHelpers_Partially.cs | 64 + .../Mvc.RemovalCandidates.csproj | 12 + .../Utilities/HttpApplication.cs | 67 + .../MySqlDataProvider.Criteria.cs | 162 ++ .../MySqlDataProvider.cs | 155 ++ .../Olive.Entities.Data.MySql.csproj | 24 + Olive.Entities.Data.MySql/Package.nuspec | 18 + .../Olive.Entities.Data.SqlServer.csproj | 23 + Olive.Entities.Data.SqlServer/Package.nuspec | 18 + .../SqlDataProvider.Criteria.cs | 162 ++ .../SqlDataProvider.cs | 185 ++ .../SqlServerManager.cs | 113 + .../API/ConcurrencyDataException.cs | 9 + .../API/Database/Database.Count.cs | 23 + .../API/Database/Database.Delete.cs | 113 + .../API/Database/Database.Find.cs | 18 + .../API/Database/Database.Get.cs | 160 ++ .../API/Database/Database.GetList.cs | 18 + .../Database/Database.ProviderManagement.cs | 166 ++ .../API/Database/Database.Save.List.cs | 41 + .../API/Database/Database.Save.cs | 273 ++ Olive.Entities.Data/API/Database/Database.cs | 124 + .../Database/Query/AssociationInclusion.cs | 64 + .../Database/Query/DatabaseQuery.Execution.cs | 137 + .../Database/Query/DatabaseQuery.OrderBy.cs | 58 + .../Query/DatabaseQuery.Polymorphism.cs | 28 + .../Query/DatabaseQuery.TEntity.Aggregate.cs | 59 + .../Database/Query/DatabaseQuery.TEntity.cs | 41 + .../API/Database/Query/DatabaseQuery.cs | 87 + .../Database/Query/DatabaseQueryExtensions.cs | 66 + Olive.Entities.Data/API/DatabaseContext.cs | 36 + Olive.Entities.Data/Ado.Net/DataAccess.cs | 251 ++ .../Ado.Net/DataAccessProfiler.cs | 73 + Olive.Entities.Data/Ado.Net/DataProvider.cs | 282 ++ .../Ado.Net/DatabaseStateChangeCommand.cs | 30 + .../Ado.Net/DbTransactionScope.cs | 163 ++ .../Ado.Net/ITransactionScope.cs | 27 + .../Ado.Net/InterfaceDataProvider.cs | 145 ++ .../Ado.Net/SubqueryMapping.cs | 22 + Olive.Entities.Data/Engine/Cache.cs | 302 +++ .../Engine/DataProviderFactoryInfo.cs | 120 + .../DataProviderModelConfigurationSection.cs | 19 + Olive.Entities.Data/Extensions/@Misc.cs | 60 + Olive.Entities.Data/Extensions/DataTable.cs | 192 ++ .../Extensions/TransactionExtensions.cs | 29 + .../Olive.Entities.Data.csproj | 26 + Olive.Entities.Data/Package.nuspec | 18 + .../Attributes/AutoNumberAttribute.cs | 20 + .../Attributes/CacheDependantAttribute.cs | 48 + .../Attributes/CacheObjectsAttribute.cs | 52 + .../Attributes/CalculatedAttribute.cs | 38 + .../Attributes/DateOnlyAttribute.cs | 11 + .../Attributes/IdByDatabaseAttribute.cs | 9 + .../Attributes/InitializerAttribute.cs | 36 + .../Attributes/LogEventsAttribute.cs | 31 + .../Attributes/ManyToManyAttribute.cs | 48 + .../Attributes/PersistentAttribute.cs | 34 + .../Attributes/SecureFileAttribute.cs | 9 + .../Attributes/SoftDeleteAttribute.cs | 87 + .../Attributes/TransientEntityAttribute.cs | 16 + .../UserInfoAccessorInitializerAttribute.cs | 7 + .../Auditing/ApplicationEventManager.cs | Bin 0 -> 7694 bytes Olive.Entities/Auditing/AuditSaveEventArgs.cs | 17 + .../DefaultApplicationEventManager.cs | Bin 0 -> 38216 bytes Olive.Entities/Auditing/IApplicationEvent.cs | 17 + Olive.Entities/Auditing/UndoContext.cs | 124 + Olive.Entities/Blob/Blob.cs | 575 +++++ .../Blob/BlobStorageProviderFactory.cs | 25 + .../Blob/DiskBlobStorageProvider.cs | 91 + Olive.Entities/Blob/IBlobStorageProvider.cs | 13 + Olive.Entities/Blob/IPickyBlobContainer.cs | 33 + Olive.Entities/Blob/IPickyBlobUrlContainer.cs | 10 + Olive.Entities/CachedReference.cs | 58 + Olive.Entities/Database/BinaryCriterion.cs | 60 + Olive.Entities/Database/Criterion.cs | 305 +++ .../Database/DbTransactionScopeOption.cs | 9 + .../Database/DirectDatabaseCriterion.cs | 96 + Olive.Entities/Database/FilterFunction.cs | 70 + Olive.Entities/Database/ICriterion.cs | 15 + Olive.Entities/Database/IDatabase.cs | 135 + Olive.Entities/Database/IDatabaseQuery.cs | 59 + Olive.Entities/Database/ITransactionScope.cs | 11 + .../QueryOptions/FullTextSearchQueryOption.cs | 26 + .../QueryOptions/PagingQueryOption.cs | 37 + .../Database/QueryOptions/QueryOption.cs | 53 + .../Database/QueryOptions/RangeQueryOption.cs | 14 + .../Database/QueryOptions/SortQueryOption.cs | 20 + .../QueryOptions/TakeTopQueryOption.cs | 15 + .../Database/QueryOptions/WhereQueryOption.cs | 11 + Olive.Entities/Engine/AggregateFunction.cs | 10 + Olive.Entities/Engine/CriteriaExtractor.cs | 270 ++ Olive.Entities/Engine/EntityFinder.cs | 140 + Olive.Entities/Engine/IDataAccess.cs | 28 + Olive.Entities/Engine/IDataProvider.cs | 48 + Olive.Entities/Engine/IDataProviderFactory.cs | 18 + Olive.Entities/Entity.T.cs | 50 + Olive.Entities/Entity.cs | 306 +++ Olive.Entities/EntityManager.cs | 266 ++ Olive.Entities/Extensions/@Misc.cs | 25 + Olive.Entities/Extensions/Entity.cs | 163 ++ Olive.Entities/Extensions/Guid.cs | 28 + Olive.Entities/Extensions/IHierarchy.cs | 69 + Olive.Entities/GuidEntity.cs | 75 + Olive.Entities/IEntity.cs | 50 + Olive.Entities/IHierarchy.cs | 13 + Olive.Entities/IntEntity.cs | 96 + Olive.Entities/Olive.Entities.csproj | 19 + Olive.Entities/Package.nuspec | 18 + Olive.Entities/SaveBehaviour.cs | 25 + Olive.Entities/SaveEventArgs.cs | 12 + Olive.Entities/Sorting/ISortable.cs | 10 + Olive.Entities/Sorting/Sorter.cs | 229 ++ Olive.Entities/ValidationException.cs | 16 + .../BaseCustomModelBindAttribute.cs | 92 + Olive.Mvc/Attributes/CopyDataAttribute.cs | 12 + Olive.Mvc/Attributes/CustomBoundAttribute.cs | 9 + Olive.Mvc/Attributes/HasDefaultAttribute.cs | 10 + .../Attributes/KeepWhiteSpaceAttribute.cs | 10 + .../Attributes/LocalizedDateAttribute.cs | 7 + .../Attributes/MasterDetailsAttribute.cs | 12 + Olive.Mvc/Attributes/OnBoundAttribute.cs | 29 + Olive.Mvc/Attributes/OnPreBindingAttribute.cs | 21 + Olive.Mvc/Attributes/OnPreBoundAttribute.cs | 26 + .../RequiredUnlessDeletingAttribute.cs | 53 + Olive.Mvc/Attributes/TimeAttribute.cs | 31 + Olive.Mvc/Attributes/ViewDataAttribute.cs | 24 + Olive.Mvc/Authentication/ExternalLoginInfo.cs | 13 + .../OwinAuthenticaionProvider.cs | 99 + Olive.Mvc/Binding/IViewComponent.cs | 9 + Olive.Mvc/Binding/IViewModel.cs | 4 + Olive.Mvc/Binding/ListPaginationBinder.cs | 29 + Olive.Mvc/Binding/ListSortExpressionBinder.cs | 26 + Olive.Mvc/Binding/OliveBinderProvider.cs | 419 +++ Olive.Mvc/Binding/OliveModelBinder.cs | 121 + Olive.Mvc/Binding/ViewModelServices.cs | 225 ++ .../Extentions/Extensions.HtmlHelpers.cs | 381 +++ .../Extentions/Extensions.SelectListItem.cs | 153 ++ Olive.Mvc/Extentions/Extensions.UrlHelpers.cs | 249 ++ Olive.Mvc/Extentions/Extensions.cs | 117 + Olive.Mvc/Extentions/Http.cs | 95 + Olive.Mvc/Olive.Mvc.csproj | 26 + Olive.Mvc/Package.nuspec | 18 + .../DefaultFileUploadMarkupGenerator.cs | 46 + Olive.Mvc/Services/FileUploadService.cs | 75 + Olive.Mvc/Services/PaginationNavigation.cs | 123 + Olive.Mvc/Services/TempFileService.cs | 47 + .../PrefixIdentificationsTagHelper.cs | 25 + .../ReplaceIdentificationsTagHelper.cs | 30 + Olive.Mvc/TagHelpers/SelectTagHelper.cs | 44 + .../ValidationTranslatorTagHelper.cs | 32 + Olive.Mvc/Utilities/ColumnSelection.cs | 68 + Olive.Mvc/Utilities/Controller.File.cs | 26 + Olive.Mvc/Utilities/Controller.cs | 264 ++ Olive.Mvc/Utilities/DatabaseFilters.cs | 12 + Olive.Mvc/Utilities/EmptyListItem.cs | 21 + .../Utilities/ExecuteBindMethodsFilter.cs | 91 + Olive.Mvc/Utilities/ITaskManager.cs | 7 + Olive.Mvc/Utilities/JsonHandlerAttribute.cs | 16 + Olive.Mvc/Utilities/JsonNetResult.cs | 54 + Olive.Mvc/Utilities/ListPagination.cs | 129 + Olive.Mvc/Utilities/ListSortExpression.cs | 78 + Olive.Mvc/Utilities/MenuItem.cs | 24 + Olive.Mvc/Utilities/NotificationAction.cs | 49 + Olive.Mvc/Utilities/OptionalBooleanFilter.cs | 52 + Olive.Mvc/Utilities/PageLifecycleStage.cs | 8 + Olive.Mvc/Utilities/RangeFileContentResult.cs | 31 + Olive.Mvc/Utilities/RangeFileResult.cs | 281 ++ Olive.Mvc/Utilities/RazorPage.cs | 47 + ...erencesMetadataReferenceFeatureProvider.cs | 60 + Olive.Mvc/Utilities/RouteTemplate.cs | 128 + Olive.Mvc/Utilities/SecureFileAccessor.cs | 115 + Olive.Mvc/Utilities/Startup.cs | 108 + Olive.Mvc/Utilities/ViewComponent.cs | 43 + Olive.Mvc/Utilities/ViewLocationExpander.cs | 31 + Olive.Mvc/Utilities/ViewRenderService.cs | 66 + .../Authentication/IAuthenticationProvider.cs | 16 + Olive.Web/Authentication/IUser.cs | 32 + .../UserInfoAccessorInitializer.cs | 18 + Olive.Web/Authentication/UserServices.cs | 55 + Olive.Web/CookieAwareWebClient.cs | 24 + Olive.Web/DI/Context.cs | 54 + Olive.Web/DI/OliveDependencies.cs | 47 + Olive.Web/Extensions/@Misc.cs | 212 ++ Olive.Web/Extensions/Exception.cs | 78 + Olive.Web/Olive.Web.csproj | 25 + Olive.Web/Package.nuspec | 18 + Olive.Web/SystemExtensions/Common.cs | 567 ++++ Olive.Web/SystemExtensions/Http.cs | 149 ++ Olive.Web/UrlRewriting/IWebResource.cs | 9 + Olive.Web/UrlRewriting/UrlRewriting.cs | 129 + Olive.Web/Web/CookieProperty.cs | 194 ++ Olive.Web/Web/HttpContextCache.cs | 46 + Olive.Web/Web/IWebRequestLog.cs | 41 + Olive.Web/Web/SecureFileDispatcher.cs | 160 ++ Olive.Web/Web/WebRequestLogMiddleware.cs | 35 + Olive.Web/Web/WebRequestLogService.cs | 150 ++ Olive.sln | 178 ++ Olive/-Extensions/@Misc.cs | 120 + Olive/-Extensions/Boolean.cs | 26 + Olive/-Extensions/DataTable.cs | 189 ++ Olive/-Extensions/DateTime.cs | 893 +++++++ Olive/-Extensions/Delegates.cs | 86 + Olive/-Extensions/DirectoryInfo.cs | 199 ++ Olive/-Extensions/Double.cs | 205 ++ Olive/-Extensions/EmbeddedResource.cs | 76 + Olive/-Extensions/Encryption.cs | 84 + Olive/-Extensions/Exception.cs | 125 + Olive/-Extensions/Expression.cs | 61 + Olive/-Extensions/FileInfo.cs | 380 +++ Olive/-Extensions/Integer.cs | 205 ++ Olive/-Extensions/Linq.cs | 1288 ++++++++++ Olive/-Extensions/Object.Get.cs | 261 ++ Olive/-Extensions/Random.cs | 76 + Olive/-Extensions/Range.cs | 103 + Olive/-Extensions/Reflection.cs | 440 ++++ Olive/-Extensions/String.Conversion.cs | 216 ++ Olive/-Extensions/String.cs | 1384 ++++++++++ Olive/-Extensions/StringBuilder.cs | 58 + Olive/-Extensions/TimeSpan.cs | 154 ++ Olive/-Extensions/Xml.cs | 121 + Olive/Logging/DefaultLogger.cs | 49 + Olive/Logging/ILogger.cs | 11 + Olive/Logging/Log.cs | 40 + Olive/Logo.png | Bin 0 -> 9558 bytes Olive/Nuget.png | Bin 0 -> 4624 bytes Olive/Olive.csproj | 27 + Olive/Package.nuspec | 18 + Olive/Security/Encryption.cs | 141 + Olive/Security/JsonExposedAttribute.cs | 8 + Olive/Security/PessimisticJsonConverter.cs | 69 + Olive/Security/SecurePassword.cs | 71 + Olive/TPL/AsyncEvent/AsyncEvent.Handler.cs | 132 + Olive/TPL/AsyncEvent/AsyncEvent.Raise.cs | 82 + Olive/TPL/AsyncEvent/AsyncEvent.TArg.cs | 99 + Olive/TPL/AsyncEvent/AsyncEvent.cs | 30 + Olive/TPL/AsyncEvent/AsyncEventExtensions.cs | 190 ++ .../AsyncEvent/AsyncEventHandlingException.cs | 9 + Olive/TPL/AsyncEvent/BaseAsyncEvent.cs | 107 + .../AsyncEvent/ConcurrentEventRaisePolicy.cs | 23 + Olive/TPL/AsyncLock/AsyncLock.cs | 54 + Olive/TPL/AsyncLock/AsyncLockDeque.cs | 64 + Olive/TPL/AsyncLock/AsyncWaitQueue.cs | 45 + Olive/TPL/AsyncLock/DisposableAwaitable.cs | 15 + Olive/TPL/CallContext.cs | 27 + Olive/TPL/ConcurrentList.cs | 279 ++ Olive/Utilities/Base32Integer.cs | 65 + Olive/Utilities/CachedValue.cs | 54 + Olive/Utilities/Config.cs | 171 ++ Olive/Utilities/EscapeGCopAttribute.cs | 15 + Olive/Utilities/LocalTime.cs | 155 ++ Olive/Utilities/PascalCaseIdGenerator.cs | 85 + Olive/Utilities/ProcessContext.cs | 73 + Olive/Utilities/PropertyComparer.cs | 30 + Olive/Utilities/Range.cs | 130 + Olive/Utilities/ShortGuid.cs | 190 ++ Olive/Utilities/TemporaryFile.cs | 80 + OliveVSIX/Key.snk | Bin 0 -> 596 bytes OliveVSIX/NugetPacker/NugetPacker.cs | 112 + OliveVSIX/NugetPacker/NugetPackerLogic.cs | 152 ++ OliveVSIX/NugetPacker/NugetPackerPackage.cs | 69 + OliveVSIX/NugetPacker/NugetPackerPackage.vsct | 93 + .../NugetPacker/Resources/NugetPacker.png | Bin 0 -> 1172 bytes OliveVSIX/NugetPacker/VSPackage.resx | 126 + OliveVSIX/OliveVSIX.csproj | 204 ++ OliveVSIX/Properties/AssemblyInfo.cs | 33 + OliveVSIX/Resources/Olive.png | Bin 0 -> 1614 bytes OliveVSIX/packages.config | 22 + OliveVSIX/source.extension.vsixmanifest | 26 + Services/Olive.Services.CSV/CSVReader.cs | 73 + .../CsvDataReader.Enums.cs | 101 + .../CsvDataReader.RecordEnumerator.cs | 160 ++ .../LumenWorks.Framework/CsvDataReader.cs | 2285 +++++++++++++++++ .../Exceptions/ExceptionMessage.Designer.cs | 204 ++ .../Exceptions/MalformedCsvException.cs | 204 ++ .../Exceptions/MissingFieldCsvException.cs | 102 + .../ParseErrorEventArgs.cs | 51 + .../Olive.Services.CSV.csproj | 20 + Services/Olive.Services.CSV/Package.nuspec | 18 + .../Olive.Services.Compression.csproj | 19 + .../Olive.Services.Compression/Package.nuspec | 18 + .../Olive.Services.Compression/SevenZip.cs | 191 ++ .../Olive.Services.Drawing/BitmapHelper.cs | 133 + Services/Olive.Services.Drawing/Extensions.cs | 28 + .../GifPalleteGenerator.cs | 74 + .../Olive.Services.Drawing/GifProcessor.cs | 111 + .../GraphicExtensions.cs | 184 ++ .../Olive.Services.Drawing/ImageOptimizer.cs | 141 + .../Olive.Services.Drawing.csproj | 25 + .../Olive.Services.Drawing/Package.nuspec | 18 + .../Compiler.cs | 165 ++ .../DynamicExpressionsCompiler.cs | 157 ++ .../Olive.Services.DynamicExpressions.csproj | 9 + .../Olive.Services.DynamicExpressions.nuspec | 13 + .../Email.Sending/EmailExtensions.cs | 140 + .../Email.Sending/EmailSendingEventArgs.cs | 19 + .../Email.Sending/EmailService.Attachments.cs | 110 + .../Email.Sending/EmailService.cs | 380 +++ .../Email.Sending/IEmailQueueItem.cs | 108 + .../Email.Sending/IEmailTemplate.cs | 32 + .../Email.Sending/SmtpNetworkSetting.cs | 14 + .../Olive.Services.Email/EmailTestService.cs | 230 ++ .../Olive.Services.Email/ExtensionMethods.cs | 36 + .../Olive.Services.Email.csproj | 25 + Services/Olive.Services.Email/Package.nuspec | 18 + Services/Olive.Services.Excel/ExcelCell.cs | 39 + .../Olive.Services.Excel/ExcelCellStyle.cs | 306 +++ .../ExcelExporter.ExcelColumn.cs | 95 + .../ExcelExporter.ExcelDropDownColumn.cs | 25 + .../ExcelExporter.Output.cs | 14 + .../Olive.Services.Excel/ExcelExporter.T.cs | 616 +++++ .../Olive.Services.Excel/ExcelExporter.cs | 35 + .../Olive.Services.Excel/ExtensionMethods.cs | 37 + .../Olive.Services.Excel.csproj | 22 + Services/Olive.Services.Excel/Package.nuspec | 18 + .../GeoLocationExtensions.cs | 53 + .../GeoLocationService.cs | 107 + .../Olive.Services.GeoLocation/IGeoLocated.cs | 7 + .../IGeoLocation.cs | 14 + .../Olive.Services.GeoLocation.csproj | 20 + .../Olive.Services.GeoLocation/Package.nuspec | 18 + .../ExtensionMethods.cs | 20 + .../GoogleAutoDetect.cs | 59 + .../GoogleTranslate.cs | 25 + .../Olive.Services.Globalization/ILanguage.cs | 11 + .../Olive.Services.Globalization/IPhrase.cs | 11 + .../LanguageExtensions.cs | 9 + .../Olive.Services.Globalization.csproj | 23 + .../Package.nuspec | 18 + .../TranslationDownloadedEventArgs.cs | 38 + .../TranslationRequestedEventArgs.cs | 13 + .../Translator.cs | 394 +++ .../ImpersonationSession.cs | 125 + ...Olive.Services.ImpersonationSession.csproj | 24 + .../Package.nuspec | 18 + .../IIntegrationQueueItem.cs | 47 + .../IServiceImplementor.cs | 23 + .../IntegrationExtensions.cs | 11 + .../IntegrationManager.cs | 247 ++ .../IntegrationService.cs | 41 + .../IntegrationTestInjector.cs | 39 + .../Olive.Services.Integration.csproj | 19 + .../Olive.Services.Integration/Package.nuspec | 18 + Services/Olive.Services.IpFilter/IpFilter.cs | 163 ++ .../Olive.Services.IpFilter.csproj | 22 + .../Olive.Services.IpFilter/Package.nuspec | 18 + .../Olive.Services.PDF/IHtml2PdfConverter.cs | 7 + .../Olive.Services.PDF.csproj | 19 + Services/Olive.Services.PDF/Package.nuspec | 18 + Services/Olive.Services.PDF/PdfService.cs | 38 + Services/Olive.Services.SMS/ISMSSender.cs | 15 + Services/Olive.Services.SMS/ISmsQueueItem.cs | 44 + .../Olive.Services.SMS.csproj | 19 + Services/Olive.Services.SMS/Package.nuspec | 18 + Services/Olive.Services.SMS/SmsExtensions.cs | 39 + .../Olive.Services.SMS/SmsSendingEventArgs.cs | 16 + Services/Olive.Services.SMS/SmsService.cs | 61 + .../AutomatedTask.Logic.cs | 287 +++ .../AutomatedTask.cs | 155 ++ .../AutomatedTaskStatus.Logic.cs | 16 + .../AutomatedTaskStatus.cs | 30 + .../Olive.Services.TaskAutomation.csproj | 18 + .../Package.nuspec | 18 + .../DatabaseChangeWatcher.cs | 45 + Services/Olive.Services.Testing/Example.cs | 127 + .../Olive.Services.Testing.csproj | 28 + .../Olive.Services.Testing/Package.nuspec | 18 + Services/Olive.Services.Testing/Snapshot.cs | 454 ++++ .../TestDatabaseGenerator.cs | 427 +++ .../Olive.Services.Testing/WebTestManager.cs | 253 ++ nuget.exe | Bin 0 -> 4266712 bytes 378 files changed, 37082 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 GCop.json create mode 100644 Mvc.RemovalCandidates/Attributes/RestrictToAttribute.cs create mode 100644 Mvc.RemovalCandidates/Binding/BasicViewDataContainer.cs create mode 100644 Mvc.RemovalCandidates/Binding/OliveModelBinder.cs create mode 100644 Mvc.RemovalCandidates/Extentions/@NamespaceConflictFix.cs create mode 100644 Mvc.RemovalCandidates/Extentions/ControlValidationTranslation.cs create mode 100644 Mvc.RemovalCandidates/Extentions/Extensions.HtmlHelpers_Partially.cs create mode 100644 Mvc.RemovalCandidates/Mvc.RemovalCandidates.csproj create mode 100644 Mvc.RemovalCandidates/Utilities/HttpApplication.cs create mode 100644 Olive.Entities.Data.MySql/MySqlDataProvider.Criteria.cs create mode 100644 Olive.Entities.Data.MySql/MySqlDataProvider.cs create mode 100644 Olive.Entities.Data.MySql/Olive.Entities.Data.MySql.csproj create mode 100644 Olive.Entities.Data.MySql/Package.nuspec create mode 100644 Olive.Entities.Data.SqlServer/Olive.Entities.Data.SqlServer.csproj create mode 100644 Olive.Entities.Data.SqlServer/Package.nuspec create mode 100644 Olive.Entities.Data.SqlServer/SqlDataProvider.Criteria.cs create mode 100644 Olive.Entities.Data.SqlServer/SqlDataProvider.cs create mode 100644 Olive.Entities.Data.SqlServer/SqlServerManager.cs create mode 100644 Olive.Entities.Data/API/ConcurrencyDataException.cs create mode 100644 Olive.Entities.Data/API/Database/Database.Count.cs create mode 100644 Olive.Entities.Data/API/Database/Database.Delete.cs create mode 100644 Olive.Entities.Data/API/Database/Database.Find.cs create mode 100644 Olive.Entities.Data/API/Database/Database.Get.cs create mode 100644 Olive.Entities.Data/API/Database/Database.GetList.cs create mode 100644 Olive.Entities.Data/API/Database/Database.ProviderManagement.cs create mode 100644 Olive.Entities.Data/API/Database/Database.Save.List.cs create mode 100644 Olive.Entities.Data/API/Database/Database.Save.cs create mode 100644 Olive.Entities.Data/API/Database/Database.cs create mode 100644 Olive.Entities.Data/API/Database/Query/AssociationInclusion.cs create mode 100644 Olive.Entities.Data/API/Database/Query/DatabaseQuery.Execution.cs create mode 100644 Olive.Entities.Data/API/Database/Query/DatabaseQuery.OrderBy.cs create mode 100644 Olive.Entities.Data/API/Database/Query/DatabaseQuery.Polymorphism.cs create mode 100644 Olive.Entities.Data/API/Database/Query/DatabaseQuery.TEntity.Aggregate.cs create mode 100644 Olive.Entities.Data/API/Database/Query/DatabaseQuery.TEntity.cs create mode 100644 Olive.Entities.Data/API/Database/Query/DatabaseQuery.cs create mode 100644 Olive.Entities.Data/API/Database/Query/DatabaseQueryExtensions.cs create mode 100644 Olive.Entities.Data/API/DatabaseContext.cs create mode 100644 Olive.Entities.Data/Ado.Net/DataAccess.cs create mode 100644 Olive.Entities.Data/Ado.Net/DataAccessProfiler.cs create mode 100644 Olive.Entities.Data/Ado.Net/DataProvider.cs create mode 100644 Olive.Entities.Data/Ado.Net/DatabaseStateChangeCommand.cs create mode 100644 Olive.Entities.Data/Ado.Net/DbTransactionScope.cs create mode 100644 Olive.Entities.Data/Ado.Net/ITransactionScope.cs create mode 100644 Olive.Entities.Data/Ado.Net/InterfaceDataProvider.cs create mode 100644 Olive.Entities.Data/Ado.Net/SubqueryMapping.cs create mode 100644 Olive.Entities.Data/Engine/Cache.cs create mode 100644 Olive.Entities.Data/Engine/DataProviderFactoryInfo.cs create mode 100644 Olive.Entities.Data/Engine/DataProviderModelConfigurationSection.cs create mode 100644 Olive.Entities.Data/Extensions/@Misc.cs create mode 100644 Olive.Entities.Data/Extensions/DataTable.cs create mode 100644 Olive.Entities.Data/Extensions/TransactionExtensions.cs create mode 100644 Olive.Entities.Data/Olive.Entities.Data.csproj create mode 100644 Olive.Entities.Data/Package.nuspec create mode 100644 Olive.Entities/Attributes/AutoNumberAttribute.cs create mode 100644 Olive.Entities/Attributes/CacheDependantAttribute.cs create mode 100644 Olive.Entities/Attributes/CacheObjectsAttribute.cs create mode 100644 Olive.Entities/Attributes/CalculatedAttribute.cs create mode 100644 Olive.Entities/Attributes/DateOnlyAttribute.cs create mode 100644 Olive.Entities/Attributes/IdByDatabaseAttribute.cs create mode 100644 Olive.Entities/Attributes/InitializerAttribute.cs create mode 100644 Olive.Entities/Attributes/LogEventsAttribute.cs create mode 100644 Olive.Entities/Attributes/ManyToManyAttribute.cs create mode 100644 Olive.Entities/Attributes/PersistentAttribute.cs create mode 100644 Olive.Entities/Attributes/SecureFileAttribute.cs create mode 100644 Olive.Entities/Attributes/SoftDeleteAttribute.cs create mode 100644 Olive.Entities/Attributes/TransientEntityAttribute.cs create mode 100644 Olive.Entities/Attributes/UserInfoAccessorInitializerAttribute.cs create mode 100644 Olive.Entities/Auditing/ApplicationEventManager.cs create mode 100644 Olive.Entities/Auditing/AuditSaveEventArgs.cs create mode 100644 Olive.Entities/Auditing/DefaultApplicationEventManager.cs create mode 100644 Olive.Entities/Auditing/IApplicationEvent.cs create mode 100644 Olive.Entities/Auditing/UndoContext.cs create mode 100644 Olive.Entities/Blob/Blob.cs create mode 100644 Olive.Entities/Blob/BlobStorageProviderFactory.cs create mode 100644 Olive.Entities/Blob/DiskBlobStorageProvider.cs create mode 100644 Olive.Entities/Blob/IBlobStorageProvider.cs create mode 100644 Olive.Entities/Blob/IPickyBlobContainer.cs create mode 100644 Olive.Entities/Blob/IPickyBlobUrlContainer.cs create mode 100644 Olive.Entities/CachedReference.cs create mode 100644 Olive.Entities/Database/BinaryCriterion.cs create mode 100644 Olive.Entities/Database/Criterion.cs create mode 100644 Olive.Entities/Database/DbTransactionScopeOption.cs create mode 100644 Olive.Entities/Database/DirectDatabaseCriterion.cs create mode 100644 Olive.Entities/Database/FilterFunction.cs create mode 100644 Olive.Entities/Database/ICriterion.cs create mode 100644 Olive.Entities/Database/IDatabase.cs create mode 100644 Olive.Entities/Database/IDatabaseQuery.cs create mode 100644 Olive.Entities/Database/ITransactionScope.cs create mode 100644 Olive.Entities/Database/QueryOptions/FullTextSearchQueryOption.cs create mode 100644 Olive.Entities/Database/QueryOptions/PagingQueryOption.cs create mode 100644 Olive.Entities/Database/QueryOptions/QueryOption.cs create mode 100644 Olive.Entities/Database/QueryOptions/RangeQueryOption.cs create mode 100644 Olive.Entities/Database/QueryOptions/SortQueryOption.cs create mode 100644 Olive.Entities/Database/QueryOptions/TakeTopQueryOption.cs create mode 100644 Olive.Entities/Database/QueryOptions/WhereQueryOption.cs create mode 100644 Olive.Entities/Engine/AggregateFunction.cs create mode 100644 Olive.Entities/Engine/CriteriaExtractor.cs create mode 100644 Olive.Entities/Engine/EntityFinder.cs create mode 100644 Olive.Entities/Engine/IDataAccess.cs create mode 100644 Olive.Entities/Engine/IDataProvider.cs create mode 100644 Olive.Entities/Engine/IDataProviderFactory.cs create mode 100644 Olive.Entities/Entity.T.cs create mode 100644 Olive.Entities/Entity.cs create mode 100644 Olive.Entities/EntityManager.cs create mode 100644 Olive.Entities/Extensions/@Misc.cs create mode 100644 Olive.Entities/Extensions/Entity.cs create mode 100644 Olive.Entities/Extensions/Guid.cs create mode 100644 Olive.Entities/Extensions/IHierarchy.cs create mode 100644 Olive.Entities/GuidEntity.cs create mode 100644 Olive.Entities/IEntity.cs create mode 100644 Olive.Entities/IHierarchy.cs create mode 100644 Olive.Entities/IntEntity.cs create mode 100644 Olive.Entities/Olive.Entities.csproj create mode 100644 Olive.Entities/Package.nuspec create mode 100644 Olive.Entities/SaveBehaviour.cs create mode 100644 Olive.Entities/SaveEventArgs.cs create mode 100644 Olive.Entities/Sorting/ISortable.cs create mode 100644 Olive.Entities/Sorting/Sorter.cs create mode 100644 Olive.Entities/ValidationException.cs create mode 100644 Olive.Mvc/Attributes/BaseCustomModelBindAttribute.cs create mode 100644 Olive.Mvc/Attributes/CopyDataAttribute.cs create mode 100644 Olive.Mvc/Attributes/CustomBoundAttribute.cs create mode 100644 Olive.Mvc/Attributes/HasDefaultAttribute.cs create mode 100644 Olive.Mvc/Attributes/KeepWhiteSpaceAttribute.cs create mode 100644 Olive.Mvc/Attributes/LocalizedDateAttribute.cs create mode 100644 Olive.Mvc/Attributes/MasterDetailsAttribute.cs create mode 100644 Olive.Mvc/Attributes/OnBoundAttribute.cs create mode 100644 Olive.Mvc/Attributes/OnPreBindingAttribute.cs create mode 100644 Olive.Mvc/Attributes/OnPreBoundAttribute.cs create mode 100644 Olive.Mvc/Attributes/RequiredUnlessDeletingAttribute.cs create mode 100644 Olive.Mvc/Attributes/TimeAttribute.cs create mode 100644 Olive.Mvc/Attributes/ViewDataAttribute.cs create mode 100644 Olive.Mvc/Authentication/ExternalLoginInfo.cs create mode 100644 Olive.Mvc/Authentication/OwinAuthenticaionProvider.cs create mode 100644 Olive.Mvc/Binding/IViewComponent.cs create mode 100644 Olive.Mvc/Binding/IViewModel.cs create mode 100644 Olive.Mvc/Binding/ListPaginationBinder.cs create mode 100644 Olive.Mvc/Binding/ListSortExpressionBinder.cs create mode 100644 Olive.Mvc/Binding/OliveBinderProvider.cs create mode 100644 Olive.Mvc/Binding/OliveModelBinder.cs create mode 100644 Olive.Mvc/Binding/ViewModelServices.cs create mode 100644 Olive.Mvc/Extentions/Extensions.HtmlHelpers.cs create mode 100644 Olive.Mvc/Extentions/Extensions.SelectListItem.cs create mode 100644 Olive.Mvc/Extentions/Extensions.UrlHelpers.cs create mode 100644 Olive.Mvc/Extentions/Extensions.cs create mode 100644 Olive.Mvc/Extentions/Http.cs create mode 100644 Olive.Mvc/Olive.Mvc.csproj create mode 100644 Olive.Mvc/Package.nuspec create mode 100644 Olive.Mvc/Services/DefaultFileUploadMarkupGenerator.cs create mode 100644 Olive.Mvc/Services/FileUploadService.cs create mode 100644 Olive.Mvc/Services/PaginationNavigation.cs create mode 100644 Olive.Mvc/Services/TempFileService.cs create mode 100644 Olive.Mvc/TagHelpers/PrefixIdentificationsTagHelper.cs create mode 100644 Olive.Mvc/TagHelpers/ReplaceIdentificationsTagHelper.cs create mode 100644 Olive.Mvc/TagHelpers/SelectTagHelper.cs create mode 100644 Olive.Mvc/TagHelpers/ValidationTranslatorTagHelper.cs create mode 100644 Olive.Mvc/Utilities/ColumnSelection.cs create mode 100644 Olive.Mvc/Utilities/Controller.File.cs create mode 100644 Olive.Mvc/Utilities/Controller.cs create mode 100644 Olive.Mvc/Utilities/DatabaseFilters.cs create mode 100644 Olive.Mvc/Utilities/EmptyListItem.cs create mode 100644 Olive.Mvc/Utilities/ExecuteBindMethodsFilter.cs create mode 100644 Olive.Mvc/Utilities/ITaskManager.cs create mode 100644 Olive.Mvc/Utilities/JsonHandlerAttribute.cs create mode 100644 Olive.Mvc/Utilities/JsonNetResult.cs create mode 100644 Olive.Mvc/Utilities/ListPagination.cs create mode 100644 Olive.Mvc/Utilities/ListSortExpression.cs create mode 100644 Olive.Mvc/Utilities/MenuItem.cs create mode 100644 Olive.Mvc/Utilities/NotificationAction.cs create mode 100644 Olive.Mvc/Utilities/OptionalBooleanFilter.cs create mode 100644 Olive.Mvc/Utilities/PageLifecycleStage.cs create mode 100644 Olive.Mvc/Utilities/RangeFileContentResult.cs create mode 100644 Olive.Mvc/Utilities/RangeFileResult.cs create mode 100644 Olive.Mvc/Utilities/RazorPage.cs create mode 100644 Olive.Mvc/Utilities/ReferencesMetadataReferenceFeatureProvider.cs create mode 100644 Olive.Mvc/Utilities/RouteTemplate.cs create mode 100644 Olive.Mvc/Utilities/SecureFileAccessor.cs create mode 100644 Olive.Mvc/Utilities/Startup.cs create mode 100644 Olive.Mvc/Utilities/ViewComponent.cs create mode 100644 Olive.Mvc/Utilities/ViewLocationExpander.cs create mode 100644 Olive.Mvc/Utilities/ViewRenderService.cs create mode 100644 Olive.Web/Authentication/IAuthenticationProvider.cs create mode 100644 Olive.Web/Authentication/IUser.cs create mode 100644 Olive.Web/Authentication/UserInfoAccessorInitializer.cs create mode 100644 Olive.Web/Authentication/UserServices.cs create mode 100644 Olive.Web/CookieAwareWebClient.cs create mode 100644 Olive.Web/DI/Context.cs create mode 100644 Olive.Web/DI/OliveDependencies.cs create mode 100644 Olive.Web/Extensions/@Misc.cs create mode 100644 Olive.Web/Extensions/Exception.cs create mode 100644 Olive.Web/Olive.Web.csproj create mode 100644 Olive.Web/Package.nuspec create mode 100644 Olive.Web/SystemExtensions/Common.cs create mode 100644 Olive.Web/SystemExtensions/Http.cs create mode 100644 Olive.Web/UrlRewriting/IWebResource.cs create mode 100644 Olive.Web/UrlRewriting/UrlRewriting.cs create mode 100644 Olive.Web/Web/CookieProperty.cs create mode 100644 Olive.Web/Web/HttpContextCache.cs create mode 100644 Olive.Web/Web/IWebRequestLog.cs create mode 100644 Olive.Web/Web/SecureFileDispatcher.cs create mode 100644 Olive.Web/Web/WebRequestLogMiddleware.cs create mode 100644 Olive.Web/Web/WebRequestLogService.cs create mode 100644 Olive.sln create mode 100644 Olive/-Extensions/@Misc.cs create mode 100644 Olive/-Extensions/Boolean.cs create mode 100644 Olive/-Extensions/DataTable.cs create mode 100644 Olive/-Extensions/DateTime.cs create mode 100644 Olive/-Extensions/Delegates.cs create mode 100644 Olive/-Extensions/DirectoryInfo.cs create mode 100644 Olive/-Extensions/Double.cs create mode 100644 Olive/-Extensions/EmbeddedResource.cs create mode 100644 Olive/-Extensions/Encryption.cs create mode 100644 Olive/-Extensions/Exception.cs create mode 100644 Olive/-Extensions/Expression.cs create mode 100644 Olive/-Extensions/FileInfo.cs create mode 100644 Olive/-Extensions/Integer.cs create mode 100644 Olive/-Extensions/Linq.cs create mode 100644 Olive/-Extensions/Object.Get.cs create mode 100644 Olive/-Extensions/Random.cs create mode 100644 Olive/-Extensions/Range.cs create mode 100644 Olive/-Extensions/Reflection.cs create mode 100644 Olive/-Extensions/String.Conversion.cs create mode 100644 Olive/-Extensions/String.cs create mode 100644 Olive/-Extensions/StringBuilder.cs create mode 100644 Olive/-Extensions/TimeSpan.cs create mode 100644 Olive/-Extensions/Xml.cs create mode 100644 Olive/Logging/DefaultLogger.cs create mode 100644 Olive/Logging/ILogger.cs create mode 100644 Olive/Logging/Log.cs create mode 100644 Olive/Logo.png create mode 100644 Olive/Nuget.png create mode 100644 Olive/Olive.csproj create mode 100644 Olive/Package.nuspec create mode 100644 Olive/Security/Encryption.cs create mode 100644 Olive/Security/JsonExposedAttribute.cs create mode 100644 Olive/Security/PessimisticJsonConverter.cs create mode 100644 Olive/Security/SecurePassword.cs create mode 100644 Olive/TPL/AsyncEvent/AsyncEvent.Handler.cs create mode 100644 Olive/TPL/AsyncEvent/AsyncEvent.Raise.cs create mode 100644 Olive/TPL/AsyncEvent/AsyncEvent.TArg.cs create mode 100644 Olive/TPL/AsyncEvent/AsyncEvent.cs create mode 100644 Olive/TPL/AsyncEvent/AsyncEventExtensions.cs create mode 100644 Olive/TPL/AsyncEvent/AsyncEventHandlingException.cs create mode 100644 Olive/TPL/AsyncEvent/BaseAsyncEvent.cs create mode 100644 Olive/TPL/AsyncEvent/ConcurrentEventRaisePolicy.cs create mode 100644 Olive/TPL/AsyncLock/AsyncLock.cs create mode 100644 Olive/TPL/AsyncLock/AsyncLockDeque.cs create mode 100644 Olive/TPL/AsyncLock/AsyncWaitQueue.cs create mode 100644 Olive/TPL/AsyncLock/DisposableAwaitable.cs create mode 100644 Olive/TPL/CallContext.cs create mode 100644 Olive/TPL/ConcurrentList.cs create mode 100644 Olive/Utilities/Base32Integer.cs create mode 100644 Olive/Utilities/CachedValue.cs create mode 100644 Olive/Utilities/Config.cs create mode 100644 Olive/Utilities/EscapeGCopAttribute.cs create mode 100644 Olive/Utilities/LocalTime.cs create mode 100644 Olive/Utilities/PascalCaseIdGenerator.cs create mode 100644 Olive/Utilities/ProcessContext.cs create mode 100644 Olive/Utilities/PropertyComparer.cs create mode 100644 Olive/Utilities/Range.cs create mode 100644 Olive/Utilities/ShortGuid.cs create mode 100644 Olive/Utilities/TemporaryFile.cs create mode 100644 OliveVSIX/Key.snk create mode 100644 OliveVSIX/NugetPacker/NugetPacker.cs create mode 100644 OliveVSIX/NugetPacker/NugetPackerLogic.cs create mode 100644 OliveVSIX/NugetPacker/NugetPackerPackage.cs create mode 100644 OliveVSIX/NugetPacker/NugetPackerPackage.vsct create mode 100644 OliveVSIX/NugetPacker/Resources/NugetPacker.png create mode 100644 OliveVSIX/NugetPacker/VSPackage.resx create mode 100644 OliveVSIX/OliveVSIX.csproj create mode 100644 OliveVSIX/Properties/AssemblyInfo.cs create mode 100644 OliveVSIX/Resources/Olive.png create mode 100644 OliveVSIX/packages.config create mode 100644 OliveVSIX/source.extension.vsixmanifest create mode 100644 Services/Olive.Services.CSV/CSVReader.cs create mode 100644 Services/Olive.Services.CSV/LumenWorks.Framework/CsvDataReader.Enums.cs create mode 100644 Services/Olive.Services.CSV/LumenWorks.Framework/CsvDataReader.RecordEnumerator.cs create mode 100644 Services/Olive.Services.CSV/LumenWorks.Framework/CsvDataReader.cs create mode 100644 Services/Olive.Services.CSV/LumenWorks.Framework/Exceptions/ExceptionMessage.Designer.cs create mode 100644 Services/Olive.Services.CSV/LumenWorks.Framework/Exceptions/MalformedCsvException.cs create mode 100644 Services/Olive.Services.CSV/LumenWorks.Framework/Exceptions/MissingFieldCsvException.cs create mode 100644 Services/Olive.Services.CSV/LumenWorks.Framework/ParseErrorEventArgs.cs create mode 100644 Services/Olive.Services.CSV/Olive.Services.CSV.csproj create mode 100644 Services/Olive.Services.CSV/Package.nuspec create mode 100644 Services/Olive.Services.Compression/Olive.Services.Compression.csproj create mode 100644 Services/Olive.Services.Compression/Package.nuspec create mode 100644 Services/Olive.Services.Compression/SevenZip.cs create mode 100644 Services/Olive.Services.Drawing/BitmapHelper.cs create mode 100644 Services/Olive.Services.Drawing/Extensions.cs create mode 100644 Services/Olive.Services.Drawing/GifPalleteGenerator.cs create mode 100644 Services/Olive.Services.Drawing/GifProcessor.cs create mode 100644 Services/Olive.Services.Drawing/GraphicExtensions.cs create mode 100644 Services/Olive.Services.Drawing/ImageOptimizer.cs create mode 100644 Services/Olive.Services.Drawing/Olive.Services.Drawing.csproj create mode 100644 Services/Olive.Services.Drawing/Package.nuspec create mode 100644 Services/Olive.Services.DynamicExpressions/Compiler.cs create mode 100644 Services/Olive.Services.DynamicExpressions/DynamicExpressionsCompiler.cs create mode 100644 Services/Olive.Services.DynamicExpressions/Olive.Services.DynamicExpressions.csproj create mode 100644 Services/Olive.Services.DynamicExpressions/Olive.Services.DynamicExpressions.nuspec create mode 100644 Services/Olive.Services.Email/Email.Sending/EmailExtensions.cs create mode 100644 Services/Olive.Services.Email/Email.Sending/EmailSendingEventArgs.cs create mode 100644 Services/Olive.Services.Email/Email.Sending/EmailService.Attachments.cs create mode 100644 Services/Olive.Services.Email/Email.Sending/EmailService.cs create mode 100644 Services/Olive.Services.Email/Email.Sending/IEmailQueueItem.cs create mode 100644 Services/Olive.Services.Email/Email.Sending/IEmailTemplate.cs create mode 100644 Services/Olive.Services.Email/Email.Sending/SmtpNetworkSetting.cs create mode 100644 Services/Olive.Services.Email/EmailTestService.cs create mode 100644 Services/Olive.Services.Email/ExtensionMethods.cs create mode 100644 Services/Olive.Services.Email/Olive.Services.Email.csproj create mode 100644 Services/Olive.Services.Email/Package.nuspec create mode 100644 Services/Olive.Services.Excel/ExcelCell.cs create mode 100644 Services/Olive.Services.Excel/ExcelCellStyle.cs create mode 100644 Services/Olive.Services.Excel/ExcelExporter.ExcelColumn.cs create mode 100644 Services/Olive.Services.Excel/ExcelExporter.ExcelDropDownColumn.cs create mode 100644 Services/Olive.Services.Excel/ExcelExporter.Output.cs create mode 100644 Services/Olive.Services.Excel/ExcelExporter.T.cs create mode 100644 Services/Olive.Services.Excel/ExcelExporter.cs create mode 100644 Services/Olive.Services.Excel/ExtensionMethods.cs create mode 100644 Services/Olive.Services.Excel/Olive.Services.Excel.csproj create mode 100644 Services/Olive.Services.Excel/Package.nuspec create mode 100644 Services/Olive.Services.GeoLocation/GeoLocationExtensions.cs create mode 100644 Services/Olive.Services.GeoLocation/GeoLocationService.cs create mode 100644 Services/Olive.Services.GeoLocation/IGeoLocated.cs create mode 100644 Services/Olive.Services.GeoLocation/IGeoLocation.cs create mode 100644 Services/Olive.Services.GeoLocation/Olive.Services.GeoLocation.csproj create mode 100644 Services/Olive.Services.GeoLocation/Package.nuspec create mode 100644 Services/Olive.Services.Globalization/ExtensionMethods.cs create mode 100644 Services/Olive.Services.Globalization/GoogleAutoDetect.cs create mode 100644 Services/Olive.Services.Globalization/GoogleTranslate.cs create mode 100644 Services/Olive.Services.Globalization/ILanguage.cs create mode 100644 Services/Olive.Services.Globalization/IPhrase.cs create mode 100644 Services/Olive.Services.Globalization/LanguageExtensions.cs create mode 100644 Services/Olive.Services.Globalization/Olive.Services.Globalization.csproj create mode 100644 Services/Olive.Services.Globalization/Package.nuspec create mode 100644 Services/Olive.Services.Globalization/TranslationDownloadedEventArgs.cs create mode 100644 Services/Olive.Services.Globalization/TranslationRequestedEventArgs.cs create mode 100644 Services/Olive.Services.Globalization/Translator.cs create mode 100644 Services/Olive.Services.ImpersonationSession/ImpersonationSession.cs create mode 100644 Services/Olive.Services.ImpersonationSession/Olive.Services.ImpersonationSession.csproj create mode 100644 Services/Olive.Services.ImpersonationSession/Package.nuspec create mode 100644 Services/Olive.Services.Integration/IIntegrationQueueItem.cs create mode 100644 Services/Olive.Services.Integration/IServiceImplementor.cs create mode 100644 Services/Olive.Services.Integration/IntegrationExtensions.cs create mode 100644 Services/Olive.Services.Integration/IntegrationManager.cs create mode 100644 Services/Olive.Services.Integration/IntegrationService.cs create mode 100644 Services/Olive.Services.Integration/IntegrationTestInjector.cs create mode 100644 Services/Olive.Services.Integration/Olive.Services.Integration.csproj create mode 100644 Services/Olive.Services.Integration/Package.nuspec create mode 100644 Services/Olive.Services.IpFilter/IpFilter.cs create mode 100644 Services/Olive.Services.IpFilter/Olive.Services.IpFilter.csproj create mode 100644 Services/Olive.Services.IpFilter/Package.nuspec create mode 100644 Services/Olive.Services.PDF/IHtml2PdfConverter.cs create mode 100644 Services/Olive.Services.PDF/Olive.Services.PDF.csproj create mode 100644 Services/Olive.Services.PDF/Package.nuspec create mode 100644 Services/Olive.Services.PDF/PdfService.cs create mode 100644 Services/Olive.Services.SMS/ISMSSender.cs create mode 100644 Services/Olive.Services.SMS/ISmsQueueItem.cs create mode 100644 Services/Olive.Services.SMS/Olive.Services.SMS.csproj create mode 100644 Services/Olive.Services.SMS/Package.nuspec create mode 100644 Services/Olive.Services.SMS/SmsExtensions.cs create mode 100644 Services/Olive.Services.SMS/SmsSendingEventArgs.cs create mode 100644 Services/Olive.Services.SMS/SmsService.cs create mode 100644 Services/Olive.Services.TaskAutomation/AutomatedTask.Logic.cs create mode 100644 Services/Olive.Services.TaskAutomation/AutomatedTask.cs create mode 100644 Services/Olive.Services.TaskAutomation/AutomatedTaskStatus.Logic.cs create mode 100644 Services/Olive.Services.TaskAutomation/AutomatedTaskStatus.cs create mode 100644 Services/Olive.Services.TaskAutomation/Olive.Services.TaskAutomation.csproj create mode 100644 Services/Olive.Services.TaskAutomation/Package.nuspec create mode 100644 Services/Olive.Services.Testing/DatabaseChangeWatcher.cs create mode 100644 Services/Olive.Services.Testing/Example.cs create mode 100644 Services/Olive.Services.Testing/Olive.Services.Testing.csproj create mode 100644 Services/Olive.Services.Testing/Package.nuspec create mode 100644 Services/Olive.Services.Testing/Snapshot.cs create mode 100644 Services/Olive.Services.Testing/TestDatabaseGenerator.cs create mode 100644 Services/Olive.Services.Testing/WebTestManager.cs create mode 100644 nuget.exe diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..1ff0c4230 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..8908c6014 --- /dev/null +++ b/.gitignore @@ -0,0 +1,267 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# GCop files +GCopLog.txt +/GCopReport.json +/GCopReport.json +@Assemblies \ No newline at end of file diff --git a/GCop.json b/GCop.json new file mode 100644 index 000000000..debcf06fe --- /dev/null +++ b/GCop.json @@ -0,0 +1,57 @@ +{ + "ReadMe": "To learn how to configure this, see:http://gcop.co/doc/config", + "Projects": [], + "DomainProjectNames": "", + "General": [ + { + "Path": "*.g.i.cs", + "Exclude": "*" + }, + { + "Path": "*\\DAL\\*", + "Exclude": "*" + }, + { + "Path": "*\\Pages\\*", + "Exclude": "*" + }, + { + "Path": "*\\Entities\\*", + "Exclude": "*" + }, + { + "Path": "*\\@Modules\\*", + "Exclude": "*" + }, + { + "Path": "*\\Modules\\*", + "Exclude": "*" + }, + { + "Path": "*\\Controller.cs", + "Exclude": "*" + }, + { + "Path": "*\\Test\\*", + "Exclude": "*" + }, + { + "Path": "*\\TaskManager.cs", + "Exclude": "*" + } + ], + "Report": { + "Enabled": true, + "AnalyzerErrorInterval": 30, + "AnalyzerPerformanceInterval": 30, + "ProjectInfoInterval": 30 + }, + "Diagnostics": { + "Enabled": false, + "URL": null + }, + "Logging": { + "LogExceptionsToFile": false, + "LogExceptionsToOutputWindow": true + } +} \ No newline at end of file diff --git a/Mvc.RemovalCandidates/Attributes/RestrictToAttribute.cs b/Mvc.RemovalCandidates/Attributes/RestrictToAttribute.cs new file mode 100644 index 000000000..b5dca7a3f --- /dev/null +++ b/Mvc.RemovalCandidates/Attributes/RestrictToAttribute.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace Olive.Mvc +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] + public class RestrictToAttribute : AuthorizeAttribute + { + protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) + { + if (HttpContext.Current.Request.IsAjaxCall()) + { + var actions = new List + { + new { OutOfModal = true, WithAjax = false, Redirect = Config.Get("Authentication.LoginUrl") } + }; + + filterContext.Result = new JsonResult(actions); + } + else + { + base.HandleUnauthorizedRequest(filterContext); + } + } + } +} \ No newline at end of file diff --git a/Mvc.RemovalCandidates/Binding/BasicViewDataContainer.cs b/Mvc.RemovalCandidates/Binding/BasicViewDataContainer.cs new file mode 100644 index 000000000..a165b2394 --- /dev/null +++ b/Mvc.RemovalCandidates/Binding/BasicViewDataContainer.cs @@ -0,0 +1,7 @@ +namespace Olive.Mvc +{ + public class BasicViewDataContainer : IViewDataContainer + { + public ViewDataDictionary ViewData { get; set; } + } +} \ No newline at end of file diff --git a/Mvc.RemovalCandidates/Binding/OliveModelBinder.cs b/Mvc.RemovalCandidates/Binding/OliveModelBinder.cs new file mode 100644 index 000000000..ec1e85035 --- /dev/null +++ b/Mvc.RemovalCandidates/Binding/OliveModelBinder.cs @@ -0,0 +1,206 @@ +// namespace Olive.Mvc +// { +// using System; +// using System.Collections; +// using System.Collections.Concurrent; +// using System.Collections.Generic; +// using System.Collections.Specialized; +// using System.ComponentModel; +// using System.Linq; +// using System.Reflection; + +// public class OliveModelBinder : DefaultModelBinder +// { +// static ConcurrentDictionary CustomBindMethods = new ConcurrentDictionary(); +// protected override PropertyDescriptorCollection GetModelProperties(ControllerContext cContext, ModelBindingContext bContext) +// { +// var result = base.GetModelProperties(cContext, bContext).Cast().ToList(); +// foreach (PropertyDescriptor prop in GetTypeDescriptor(cContext, bContext).GetProperties()) +// { +// var fromQuery = prop.Attributes.OfType().ToList(); +// if (fromQuery.None()) continue; + +// var old = result.Single(x => x.Name == prop.Name); +// result.Remove(old); +// result.AddRange(ByFromQuery(bContext, prop, fromQuery)); +// } + +// var sortedProperties = result.OrderBy(x => x.PropertyType.Implements()).ToArray(); +// return new PropertyDescriptorCollection(sortedProperties); +// } + +// /// Sets the specified property by using the specified controller context, binding context, and property value. +// protected override void SetProperty(ControllerContext cContext, ModelBindingContext bContext, PropertyDescriptor pDescriptor, object value) +// { +// if (pDescriptor.PropertyType == typeof(string)) +// { +// var stringValue = (string)value; +// if (stringValue.HasValue()) +// { +// if (pDescriptor.Attributes.OfType().None()) +// stringValue = stringValue.Trim(); +// } + +// value = stringValue; +// } + +// base.SetProperty(cContext, bContext, pDescriptor, value); +// } + +// protected override void BindProperty(ControllerContext cContext, ModelBindingContext bContext, PropertyDescriptor propertyDescriptor) +// { +// if (propertyDescriptor.PropertyType.IsA()) +// { +// BindViewModelProperty(cContext, bContext, propertyDescriptor); +// } +// else if (propertyDescriptor.PropertyType.IsA()) +// { +// BindDocumentProperty(cContext, bContext, propertyDescriptor); +// } +// else if (propertyDescriptor.Attributes.OfType().Any()) +// { +// BindMasterDetailsProperty(cContext, bContext, propertyDescriptor); +// } +// else +// { +// base.BindProperty(cContext, bContext, propertyDescriptor); +// } +// } + +// void BindMasterDetailsProperty(ControllerContext cContext, ModelBindingContext bContext, PropertyDescriptor propertyDescriptor) +// { +// if ((cContext.Controller as Controller).Request.IsGet()) return; + +// var prefix = propertyDescriptor.Attributes.OfType().Single().Prefix + "-"; +// var listObject = Activator.CreateInstance(propertyDescriptor.PropertyType) as IList; +// var formData = cContext.RequestContext.HttpContext.Request.Form; + +// var childItemIds = formData.AllKeys.ExceptNull().Where(k => k.StartsWith(prefix) && k.EndsWith(".Item")) +// .Select(x => x.TrimStart(prefix).TrimEnd(".Item")).ToList(); + +// foreach (var id in childItemIds) +// { +// var formControlsPrefix = prefix + id + "."; +// // The request form keys that are related to this row +// var dataKeys = formData.AllKeys.ExceptNull().Where(x => x.StartsWith(formControlsPrefix)).ToList(); +// var instanceType = propertyDescriptor.PropertyType.GetGenericArguments().Single(); +// var instance = Activator.CreateInstance(instanceType); +// listObject.Add(instance); +// // Set the instance properties +// foreach (var key in dataKeys) +// { +// var propertyName = key.TrimStart(formControlsPrefix); +// var property = instanceType.GetProperty(propertyName); +// SetPropertyValue(cContext, bContext, instance, key, property, formData); +// } + +// // All properties are written to ViewModel. Now also write them on the model (Item property): +// var item = instance.GetType().GetProperty("Item").GetValue(instance); +// ViewModelServices.CopyData(instance, item); +// } + +// propertyDescriptor.SetValue(bContext.Model, listObject); +// } + +// static void SetPropertyValue(ControllerContext cContext, ModelBindingContext bContext, object model, string modelName, PropertyInfo modelProperty, NameValueCollection formData) +// { +// // find the correct binding context for this property type +// var modelBinder = OliveBinderProvider.SelectBinder(modelProperty.PropertyType); +// object typed = null; +// if (modelBinder != null) +// { +// var metadata = ModelMetadataProviders.Current.GetMetadataForType(null, modelProperty.PropertyType); +// var propertyContext = new ModelBindingContext { ValueProvider = bContext.ValueProvider, ModelMetadata = metadata, ModelName = modelName }; +// typed = modelBinder.BindModel(cContext, propertyContext); +// } +// else +// { +// // if Binder is null we've got a simple value type, throw it to view model services +// try +// { +// object data; + +// if (modelProperty.Defines()) +// data = cContext.HttpContext.Request.Unvalidated.Form[modelName]; +// else +// data = formData[modelName]; + +// typed = ViewModelServices.Convert(data, modelProperty.PropertyType); +// } +// catch +// { +// // No logging is needed +// bContext.ModelState.AddModelError("Invalid Data", new Exception($"The value '{formData[modelName]}' is not valid for {modelProperty.Name}.")); +// } +// } + +// // if we're still null at this point it's going to be a new entity +// if (typed == null && modelProperty.PropertyType.IsA() && modelName.EndsWith(".Item")) +// typed = Activator.CreateInstance(modelProperty.PropertyType); +// modelProperty.SetValue(model, typed); +// } + +// void BindDocumentProperty(ControllerContext cContext, ModelBindingContext bContext, PropertyDescriptor propertyDescriptor) +// { +// var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, bContext.ModelType, propertyDescriptor.Name); +// var pContext = new ModelBindingContext { ValueProvider = bContext.ValueProvider, ModelMetadata = metadata, ModelName = propertyDescriptor.Name }; +// var value = new DocumentModelBinder().BindModel(cContext, pContext); +// if (value != null) +// propertyDescriptor.SetValue(bContext.Model, value); +// } + +// void BindViewModelProperty(ControllerContext cContext, ModelBindingContext bContext, PropertyDescriptor propertyDescriptor) +// { +// var metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, bContext.ModelType, propertyDescriptor.Name); +// var pContext = new ModelBindingContext +// { +// ValueProvider = bContext.ValueProvider, +// ModelMetadata = metadata, //ModelName = propertyDescriptor.Name +// }; +// propertyDescriptor.SetValue(bContext.Model, BindModel(cContext, pContext)); +// } + +// IEnumerable ByFromQuery(ModelBindingContext bindingContext, PropertyDescriptor property, List attributes) +// { +// foreach (var attr in attributes) +// { +// var metadata = bindingContext.PropertyMetadata; +// if (metadata.ContainsKey(property.Name) && !metadata.ContainsKey(attr.Alias)) +// metadata.Add(attr.Alias, metadata[property.Name]); +// yield return new FromQueryPropertyDescriptor(attr.Alias, property); +// } +// } + +// protected override void OnModelUpdated(ControllerContext cContext, ModelBindingContext bContext) +// { +// base.OnModelUpdated(cContext, bContext); +// if (bContext.Model is IViewModel) +// { +// OnPreBoundAttribute.Enqueue(cContext, bContext.Model); +// OnBoundAttribute.Enqueue(cContext, bContext.Model); +// } + +// // Imagine a root ViewModel object which has properties (i.e. nested objects) which are +// // also ViewModel types themselves. +// // The invokation lines below should in fact be called during OnModelUpdated of the root object only +// // This is to ensure that all interim PreBinds happen, before all OnBound ones. +// // The following lines are called of course for all child objects (before calling it on the root object) +// // But they will do nothing (return early) when called for non-Root objects. +// OnPreBoundAttribute.InvokeAllForRoot(bContext, cContext.HttpContext); +// OnBoundAttribute.InvokeAllForRoot(bContext, cContext.HttpContext); +// } + +// protected override bool OnModelUpdating(ControllerContext cContext, ModelBindingContext bContext) +// { +// OnPreBoundAttribute.SetRoot(bContext, cContext.HttpContext); +// OnBoundAttribute.SetRoot(bContext, cContext.HttpContext); +// var result = base.OnModelUpdating(cContext, bContext); +// if (bContext.Model is IViewModel) +// { +// OnPreBindingAttribute.Execute(cContext, bContext.Model); +// } + +// return result; +// } +// } +// } \ No newline at end of file diff --git a/Mvc.RemovalCandidates/Extentions/@NamespaceConflictFix.cs b/Mvc.RemovalCandidates/Extentions/@NamespaceConflictFix.cs new file mode 100644 index 000000000..766b9b40e --- /dev/null +++ b/Mvc.RemovalCandidates/Extentions/@NamespaceConflictFix.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Linq.Expressions; + +namespace ASP +{ + /// + /// Defined in the ASP namespace to fix the namespece conflicts for extension methods with the same name. + /// These methods are preferred by the compiler for all code written in CSHTML files. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public static class SystemMvcWebExtensionsFix + { + delegate IHtmlContent SelectControlHelper(HtmlHelper helper, string name, IEnumerable selectList, object htmlAttributes); + + delegate IHtmlContent SelectControlHelper(HtmlHelper helper, Expression> expression, IEnumerable selectList, object htmlAttributes); + + delegate IHtmlContent InputControlHelper(HtmlHelper helper, string name, string value, object htmlAttributes); + + delegate IHtmlContent InputControlHelper(HtmlHelper helper, Expression> expression, object htmlAttributes); + + /// + /// Will replace all line breaks with a BR tag and return the result as a raw html. + /// + public static IHtmlContent ToHtmlLines(this string text) => new HtmlString(Olive.OliveExtensions.ToHtmlLines(text)); + + /// + /// Will join all items with a BR tag and return the result as a raw html. + /// + public static IHtmlContent ToHtmlLines(this IEnumerable items) => new HtmlString(Olive.OliveExtensions.ToHtmlLines(items)); + + /// + /// Will join all items with a BR tag and return the result as a raw html. + /// + public static IHtmlContent ToHtmlLines(this IEnumerable items) => new HtmlString(Olive.OliveExtensions.ToHtmlLines(items)); + + /// + /// Determines whether this text is null or empty. + /// + public static bool IsEmpty(this string text) => string.IsNullOrEmpty(text); + + /// + /// Returns an HTML select element for each property in the object that is represented by the specified expression + /// using the specified list items and HTML attributes. + /// + public static IHtmlContent DropDownListFor(this HtmlHelper htmlHelper, Expression> expression, IEnumerable selectList, object htmlAttributes) + { + return FixForEntityType(htmlHelper, expression, selectList, htmlAttributes, SelectExtensions.DropDownList, + SelectExtensions.DropDownListFor); + } + + public static IHtmlContent HiddenFor(this HtmlHelper htmlHelper, Expression> expression, object htmlAttributes = null) + { + return FixForEntityType(htmlHelper, expression, htmlAttributes, InputExtensions.Hidden, InputExtensions.HiddenFor); + } + + static IHtmlContent FixForEntityType(this HtmlHelper htmlHelper, Expression> expression, object htmlAttributes, InputControlHelper controlMethod, InputControlHelper generic) + { + var fixedCode = FixForEntityType(htmlHelper, expression, htmlAttributes, controlMethod); + + if (fixedCode != null) return fixedCode; + + return generic(htmlHelper, expression, htmlAttributes); + } + + static IHtmlContent FixForEntityType(this HtmlHelper htmlHelper, Expression> expression, IEnumerable selectList, object htmlAttributes, SelectControlHelper controlMethod, SelectControlHelper generic) + { + var fixedCode = FixForEntityType(htmlHelper, expression, selectList, htmlAttributes, controlMethod); + + if (fixedCode != null) return fixedCode; + + return ControlValidationTranslation.Translate(generic(htmlHelper, expression, selectList, htmlAttributes)); + } + + static IHtmlContent FixForEntityType(this HtmlHelper htmlHelper, Expression> expression, IEnumerable selectList, object htmlAttributes, SelectControlHelper controlMethod) + { + var property = expression.GetProperty(); + + if (property == null) return null; + + if (!property.PropertyType.IsA()) return null; + + var value = property.GetValue(htmlHelper.ViewData.Model) as IEntity; + if (value == null) return null; + + htmlHelper.ViewContext.ViewData[property.Name] = value.GetId().ToString(); + + return ControlValidationTranslation.Translate(controlMethod(htmlHelper, property.Name, selectList, htmlAttributes)); + } + + static IHtmlContent FixForEntityType(this HtmlHelper htmlHelper, Expression> expression, object htmlAttributes, InputControlHelper controlMethod) + { + var property = expression.GetProperty(); + + if (property == null) return null; + + string value; + + var type = property.PropertyType; + + if (type.IsA()) + { + var entity = property.GetValue(htmlHelper.ViewData.Model) as IEntity; + if (entity == null) return null; + value = entity.GetId().ToString(); + } + else if (type.IsA() && type.GetGenericArguments().FirstOrDefault()?.IsA() == true) + { + var list = property.GetValue(htmlHelper.ViewData.Model) as IEnumerable; + if (list == null) return null; + value = list.Cast().Select(x => x.GetId()).ToString("|"); + } + else return null; + + return ControlValidationTranslation.Translate(controlMethod(htmlHelper, property.Name, value, htmlAttributes)); + } + } +} \ No newline at end of file diff --git a/Mvc.RemovalCandidates/Extentions/ControlValidationTranslation.cs b/Mvc.RemovalCandidates/Extentions/ControlValidationTranslation.cs new file mode 100644 index 000000000..47fe79026 --- /dev/null +++ b/Mvc.RemovalCandidates/Extentions/ControlValidationTranslation.cs @@ -0,0 +1,61 @@ +using System; +using System.ComponentModel; +using System.Linq.Expressions; + +namespace ASP +{ + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public static class ControlValidationTranslation + { + static bool TranslateValidators = Config.Get("Translate.Validators", defaultValue: false); + static string[] ValidationTextAttributes = new[] { "data-val-length", "data-val-required", "data-val-email" }; + + internal static IHtmlContent Translate(IHtmlContent source) + { + if (!TranslateValidators) return source; + + var doc = new HtmlAgilityPack.HtmlDocument(); + doc.LoadHtml(source.ToString()); + + foreach (var node in doc.DocumentNode.Descendants()) + foreach (var att in node.Attributes.OrEmpty()) + if (att.Name.IsAnyOf(ValidationTextAttributes)) + att.Value = Translator.Translate(att.Value); + + return new HtmlString(doc.DocumentNode.InnerHtml); + } + + public static IHtmlContent TextBoxFor(this HtmlHelper htmlHelper, + Expression> expression, object htmlAttributes = null) + { + var result = InputExtensions.TextBoxFor(htmlHelper, expression, htmlAttributes); + + return Translate(result); + } + + public static IHtmlContent PasswordFor(this HtmlHelper htmlHelper, + Expression> expression, object htmlAttributes = null) + { + var result = InputExtensions.PasswordFor(htmlHelper, expression, htmlAttributes); + + return Translate(result); + } + + public static IHtmlContent RadioButtonFor(this HtmlHelper htmlHelper, + Expression> expression, object htmlAttributes = null) + { + var result = InputExtensions.RadioButtonFor(htmlHelper, expression, htmlAttributes); + + return Translate(result); + } + + public static IHtmlContent CheckBoxFor(this HtmlHelper htmlHelper, + Expression> expression, object htmlAttributes = null) + { + var result = InputExtensions.CheckBoxFor(htmlHelper, expression, htmlAttributes); + + return Translate(result); + } + } +} \ No newline at end of file diff --git a/Mvc.RemovalCandidates/Extentions/Extensions.HtmlHelpers_Partially.cs b/Mvc.RemovalCandidates/Extentions/Extensions.HtmlHelpers_Partially.cs new file mode 100644 index 000000000..43fa22bb8 --- /dev/null +++ b/Mvc.RemovalCandidates/Extentions/Extensions.HtmlHelpers_Partially.cs @@ -0,0 +1,64 @@ +using System; + +namespace Olive.Mvc +{ + partial class MSharpMvcExtensions + { + // ***************************************************************** + // The following methods remove because in .net core the request + // context does not accessible from the htmlHelper and ... + // ***************************************************************** + public static UrlHelper GetUrlHelper(this HtmlHelper html) => new UrlHelper(html.ViewContext.RequestContext); + + // ***************************************************************** + // The Following method adds overload to existing methods in the old + // .net and in the core version these methods has been removed. + // ***************************************************************** + + /// + /// Renders the specified partial view as an HTML-encoded string. + /// + /// The HTML helper instance that this method extends. + /// The name of the partial view to render. + /// The view model for the partial view. + /// The partial view that is rendered as an HTML-encoded string. + public static HtmlString Partial(this HtmlHelper html, string partialViewName, T model, bool skipAjaxPost) where T : IViewModel + { + var request = HttpContext.Current.Request; + if (skipAjaxPost && request.IsAjaxCall() && request.IsPost()) return HtmlString.Empty; + + if (model == null) + { + model = (html.ViewContext.Controller as Controller).Bind(); + + if (model == null) + throw new Exception("The model object passed to Partial() cannot be null."); + } + + return html.Partial(partialViewName, model); + } + + public static void RenderAction(this HtmlHelper html, string action = "Index") + { + html.RenderAction(action, typeof(TController).Name.TrimEnd("Controller")); + } + + /// + /// Invokes the Index action method of the specified controller and returns the result as an HTML string. + /// An anonymous object containing query string / route values to pass. + /// + public static HtmlString Action(this HtmlHelper html, object queryParameters) + { + return Action(html, "Index", queryParameters); + } + + /// + /// Invokes the specified child action method of the specified controller and returns the result as an HTML string. + /// An anonymous object containing query string / route values to pass. + /// + public static HtmlString Action(this HtmlHelper html, string action = "Index", object queryParameters = null) + { + return html.Action(action, typeof(TController).Name.TrimEnd("Controller"), queryParameters); + } + } +} \ No newline at end of file diff --git a/Mvc.RemovalCandidates/Mvc.RemovalCandidates.csproj b/Mvc.RemovalCandidates/Mvc.RemovalCandidates.csproj new file mode 100644 index 000000000..27113fed5 --- /dev/null +++ b/Mvc.RemovalCandidates/Mvc.RemovalCandidates.csproj @@ -0,0 +1,12 @@ + + + + netcoreapp2.0 + + + + ..\@Assemblies\ + ..\@Assemblies\netcoreapp2.0\Mvc.RemovalCandidates.xml + + + diff --git a/Mvc.RemovalCandidates/Utilities/HttpApplication.cs b/Mvc.RemovalCandidates/Utilities/HttpApplication.cs new file mode 100644 index 000000000..d77a6f5b0 --- /dev/null +++ b/Mvc.RemovalCandidates/Utilities/HttpApplication.cs @@ -0,0 +1,67 @@ +//namespace MSharp.Framework.Mvc +//{ +// using System; +// using System.Security.Claims; +// using System.Security.Principal; +// using System.Web; +// using System.Web.Mvc; +// using System.Web.Routing; +// using MSharp.Framework.UI; + +// /// +// /// Provides a base http application class for MSharp MVC applications. +// /// +// public abstract class HttpApplication : BaseHttpApplication +// { +// /// +// /// Sets up the standard providers and configures the application. +// /// +// protected virtual void Application_Start() +// { +// GlobalFilters.Filters.Add(new HandleErrorAttribute()); +// GlobalFilters.Filters.Add(new JsonHandlerAttribute()); + +// MvcHandler.DisableMvcResponseHeader = true; + +// RegisterRoutes(); + +// RegisterBinders(); + +// Document.SecureVirtualRoot = "/file/download?"; +// } + +// /// +// /// Registers the standard Routes. +// /// +// protected virtual void RegisterRoutes() +// { +// RouteTable.Routes.MapMvcAttributeRoutes(); +// RouteTable.Routes.MapRoute("Default", "{controller}/{action}"); +// RegisterNotFoundRoute(); + + +// } + + +// /// +// /// Retrieves the actual user object from the principal info. +// /// +// protected IPrincipal RetrieveActualUser(IPrincipal principal) +// where TUser : IEntity, IPrincipal +// where TAnonymousUser : IEntity, IPrincipal, new() +// { +// if (principal == null) return new TAnonymousUser(); +// if (!principal.Identity.IsAuthenticated) return new TAnonymousUser(); + +// var asIdentity = principal.Identity as ClaimsIdentity; +// if (asIdentity != null) +// { +// var result = Database.GetOrDefault(asIdentity.Name) as IPrincipal; +// return result ?? new TAnonymousUser(); +// } + +// throw new IdentityNotMappedException("I do not recognise " + principal.Identity.GetType().FullName + " identity for authentication."); +// } + +// } +//} \ No newline at end of file diff --git a/Olive.Entities.Data.MySql/MySqlDataProvider.Criteria.cs b/Olive.Entities.Data.MySql/MySqlDataProvider.Criteria.cs new file mode 100644 index 000000000..097c62abb --- /dev/null +++ b/Olive.Entities.Data.MySql/MySqlDataProvider.Criteria.cs @@ -0,0 +1,162 @@ +namespace Olive.Entities.Data +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + class MySqlCriterionGenerator + { + DatabaseQuery Query; + Type EntityType; + + public MySqlCriterionGenerator(DatabaseQuery query) + { + Query = query; + EntityType = query.EntityType; + } + + public string Generate(ICriterion criterion) + { + if (criterion == null) return "(1 = 1)"; + if (criterion is BinaryCriterion binary) return Generate(binary); + else if (criterion is DirectDatabaseCriterion direct) return Generate(direct); + else if (criterion.PropertyName.Contains(".")) return ToSubQuerySql(criterion); + else return ToSqlOn(criterion, EntityType); + } + + string Generate(BinaryCriterion criterion) + { + return $"({Generate(criterion.Left)} {criterion.Operator} {Generate(criterion.Right)})"; + } + + string Generate(DirectDatabaseCriterion criterion) + { + // Add the params: + if (criterion.Parameters != null) + foreach (var x in criterion.Parameters) Query.Parameters.Add(x.Key, x.Value); + + if (criterion.PropertyName.IsEmpty() || criterion.PropertyName == "N/A") + return criterion.SqlCriteria; + + return criterion.SqlCriteria.Replace($"{{{{{criterion.PropertyName}}}}}", + Query.Column(criterion.PropertyName)); + } + + string ToNestedSubQuerySql(ICriterion criterion, string[] parts) + { + var subQueries = new List(); + var type = EntityType; + + subQueries.Add(Query.Provider.MapSubquery(parts[0] + ".*")); + + for (var i = 0; i < parts.Length - 2; i++) + { + type = type.GetProperty(parts[i]).PropertyType; + + var dataProvider = Database.Instance.GetProvider(type); + var mapping = dataProvider.MapSubquery(parts[i + 1] + ".*"); + if (mapping.Lacks(" WHERE ")) continue; + + var subquerySplitted = mapping.Split(" WHERE ", StringSplitOptions.RemoveEmptyEntries); + var whereClauseSplitted = subquerySplitted[1].Split('='); + var tablePrefix = whereClauseSplitted.Last().Split('.')[0]; + + var aliasName = tablePrefix.Trim(']').Trim(']').Split('_').ExceptLast().ToString("_") + "_"; + + var recordIdentifier = whereClauseSplitted[1].Replace(tablePrefix, aliasName); + + mapping = subquerySplitted[0] + " WHERE " + whereClauseSplitted[0] + " = " + recordIdentifier; + + subQueries.Add(mapping); + } + + var subCriterion = new Criterion(parts[parts.Length - 1], criterion.FilterFunction, criterion.Value); + type = type.GetProperty(parts[parts.Length - 2]).PropertyType; + var criteria = new StringBuilder(); + var parenthesis = ""; + for (var i = 0; i < subQueries.Count; i++) + { + criteria.AppendLine($" EXISTS ({subQueries[i].WithSuffix(" AND ")}"); + parenthesis += ")"; + } + + criteria.Append(ToSqlOn(subCriterion, type)); + criteria.Append(parenthesis); + + return criteria.ToString(); + } + + string ToSubQuerySql(ICriterion criterion) + { + var parts = criterion.PropertyName.Split('.'); + + if (parts.Count() > 2) return ToNestedSubQuerySql(criterion, parts); + + var type = EntityType.GetProperty(parts[0])?.PropertyType; + if (type == null) throw new Exception($"{EntityType.Name} does not have a public property named {parts[0]}."); + + var subquery = Query.Provider.MapSubquery(parts[0] + ".*"); + var subCriterion = new Criterion(parts[1], criterion.FilterFunction, criterion.Value); + return "EXISTS ({0}{1})".FormatWith(subquery, ToSqlOn(subCriterion, type).WithPrefix(" AND ")); + } + + string ToSqlOn(ICriterion criterion, Type type) + { + string column; + + var key = criterion.PropertyName; + + if (key.EndsWith("Id") && key.Length > 2) + { + var association = type.GetProperty(key.TrimEnd("Id")); + + if (association != null && !association.Defines() && + association.PropertyType.IsA()) + key = key.TrimEnd("Id"); + } + + column = Query.Column(key); + + var value = criterion.Value; + var function = criterion.FilterFunction; + + if (value == null) + return "{0} IS {1} NULL".FormatWith(column, "NOT".OnlyWhen(function != FilterFunction.Is)); + + var valueData = value; + if (function == FilterFunction.Contains || function == FilterFunction.NotContains) valueData = "%{0}%".FormatWith(value); + else if (function == FilterFunction.BeginsWith) valueData = "{0}%".FormatWith(value); + else if (function == FilterFunction.EndsWith) valueData = "%{0}".FormatWith(value); + else if (function == FilterFunction.In) + { + if ((value as string) == "()") return "1 = 0 /*" + column + " IN ([empty])*/"; + else return column + " " + function.GetDatabaseOperator() + " " + value; + } + + var parameterName = GetUniqueParameterName(column); + + Query.Parameters.Add(parameterName, valueData); + + var critera = $"{column} {function.GetDatabaseOperator()} @{parameterName}"; + var includeNulls = function == FilterFunction.IsNot; + return includeNulls ? $"( {critera} OR {column} {FilterFunction.Null.GetDatabaseOperator()} )" : critera; + } + + string GetUniqueParameterName(string column) + { + var result = column.Remove("[").Remove("]").Replace(".", "_"); + + if (Query.Parameters.ContainsKey(result)) + { + for (var i = 2; ; i++) + { + var name = result + "_" + i; + if (Query.Parameters.LacksKey(name)) return name; + } + } + + return result; + } + } +} diff --git a/Olive.Entities.Data.MySql/MySqlDataProvider.cs b/Olive.Entities.Data.MySql/MySqlDataProvider.cs new file mode 100644 index 000000000..c24d56971 --- /dev/null +++ b/Olive.Entities.Data.MySql/MySqlDataProvider.cs @@ -0,0 +1,155 @@ +namespace Olive.Entities.Data +{ + using MySql.Data.MySqlClient; + using System; + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + + public abstract partial class MySqlDataProvider : DataProvider + { + public override IDataParameter GenerateParameter(KeyValuePair data) + { + var value = data.Value; + + var result = new MySqlParameter { Value = value, ParameterName = data.Key.Remove(" ") }; + + if (value is DateTime) + { + result.DbType = DbType.DateTime2; + result.MySqlDbType = MySqlDbType.DateTime; + } + + return result; + } + + public abstract string GetFields(); + + public abstract string GetTables(); + + public abstract IEntity Parse(IDataReader reader); + + public override async Task Get(object id) + { + var command = $"SELECT {GetFields()} FROM {GetTables()} WHERE {MapColumn("ID")} = @ID"; + + using (var reader = await ExecuteReader(command, CommandType.Text, CreateParameter("ID", id))) + { + var result = new List(); + + if (reader.Read()) return Parse(reader); + else throw new DataException($"There is no record with the the ID of '{id}'."); + } + } + + protected async Task ExecuteGetListReader(IDatabaseQuery query) + { + var command = GenerateSelectCommand(query); + return await ExecuteReader(command, CommandType.Text, GenerateParameters(query.Parameters)); + } + + public override async Task> GetList(IDatabaseQuery query) + { + using (var reader = await ExecuteGetListReader(query)) + { + var result = new List(); + while (reader.Read()) result.Add(Parse(reader)); + return result; + } + } + + public override async Task Count(IDatabaseQuery query) + { + var command = GenerateCountCommand(query); + return (int)await ExecuteScalar(command, CommandType.Text, GenerateParameters(query.Parameters)); + } + + public override Task Aggregate(IDatabaseQuery query, AggregateFunction function, string propertyName) + { + var command = GenerateAggregateQuery(query, function, propertyName); + return ExecuteScalar(command, CommandType.Text, GenerateParameters(query.Parameters)); + } + + public string GenerateAggregateQuery(IDatabaseQuery query, AggregateFunction function, string propertyName) + { + var sqlFunction = function.ToString(); + + var columnValueExpression = MapColumn(propertyName); + + if (function == AggregateFunction.Average) + { + sqlFunction = "AVG"; + + var propertyType = query.EntityType.GetProperty(propertyName).PropertyType; + + if (propertyType == typeof(int) || propertyType == typeof(int?)) + columnValueExpression = $"CAST({columnValueExpression} AS decimal)"; + } + + return $"SELECT {sqlFunction}({columnValueExpression}) FROM {GetTables()}" + + GenerateWhere((DatabaseQuery)query); + } + + public string GenerateSelectCommand(IDatabaseQuery iquery) + { + var query = (DatabaseQuery)iquery; + + if (query.PageSize.HasValue && query.OrderByParts.None()) + throw new ArgumentException("PageSize cannot be used without OrderBy."); + + var r = new StringBuilder("SELECT"); + + //r.Append(query.TakeTop.ToStringOrEmpty().WithPrefix(" TOP ")); + r.AppendLine($" {GetFields()} FROM {GetTables()}"); + r.AppendLine(GenerateWhere(query)); + r.AppendLine(GenerateSort(query).WithPrefix(" ORDER BY ")); + r.AppendLine(query.TakeTop.ToStringOrEmpty().WithPrefix(" LIMIT ")); + + return r.ToString(); + } + + public string GenerateCountCommand(IDatabaseQuery iquery) + { + var query = (DatabaseQuery)iquery; + + if (query.PageSize.HasValue) + throw new ArgumentException("PageSize cannot be used for Count()."); + + if (query.TakeTop.HasValue) + throw new ArgumentException("TakeTop cannot be used for Count()."); + + return $"SELECT Count(*) FROM {GetTables()} {GenerateWhere(query)}"; + } + + string GenerateWhere(DatabaseQuery query) + { + var r = new StringBuilder(); + + if (SoftDeleteAttribute.RequiresSoftdeleteQuery(query.EntityType)) + query.Criteria.Add(new Criterion("IsMarkedSoftDeleted", false)); + + r.Append($" WHERE { query.Column("ID")} IS NOT NULL"); + + var whereGenerator = new MySqlCriterionGenerator(query); + foreach (var c in query.Criteria) + r.Append(whereGenerator.Generate(c).WithPrefix(" AND ")); + + return r.ToString(); + } + + string GenerateSort(DatabaseQuery query) + { + var parts = new List(); + + parts.AddRange(query.OrderByParts.Select(p => query.Column(p.Property) + " DESC".OnlyWhen(p.Descending))); + + var offset = string.Empty; + if (query.PageSize > 0) + offset = $" OFFSET {query.PageStartIndex} ROWS FETCH NEXT {query.PageSize} ROWS ONLY"; + + return parts.ToString(", ") + offset; + } + } +} diff --git a/Olive.Entities.Data.MySql/Olive.Entities.Data.MySql.csproj b/Olive.Entities.Data.MySql/Olive.Entities.Data.MySql.csproj new file mode 100644 index 000000000..a61fe8d39 --- /dev/null +++ b/Olive.Entities.Data.MySql/Olive.Entities.Data.MySql.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp2.0 + + + + ..\@Assemblies\ + ..\@Assemblies\netcoreapp2.0\Olive.Entities.Data.MySql.xml + + + + + + + + + + + + + + + diff --git a/Olive.Entities.Data.MySql/Package.nuspec b/Olive.Entities.Data.MySql/Package.nuspec new file mode 100644 index 000000000..939ebaaeb --- /dev/null +++ b/Olive.Entities.Data.MySql/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Entities.Data.MySql + 1.0.3 + Olive Entities Data MySql + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Olive.Entities.Data.SqlServer/Olive.Entities.Data.SqlServer.csproj b/Olive.Entities.Data.SqlServer/Olive.Entities.Data.SqlServer.csproj new file mode 100644 index 000000000..f6074615d --- /dev/null +++ b/Olive.Entities.Data.SqlServer/Olive.Entities.Data.SqlServer.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp2.0 + Olive.Entities.Data + + + + NU1605 + 1701;1702;1705;1591;1573 + ..\@Assemblies + ..\@Assemblies\netcoreapp2.0\Olive.Entities.Data.SqlServer.xml + + + + + + + + + + + diff --git a/Olive.Entities.Data.SqlServer/Package.nuspec b/Olive.Entities.Data.SqlServer/Package.nuspec new file mode 100644 index 000000000..d9e32078b --- /dev/null +++ b/Olive.Entities.Data.SqlServer/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Entities.Data.SqlServer + 1.0.3 + Olive Entities Data SqlServer + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Olive.Entities.Data.SqlServer/SqlDataProvider.Criteria.cs b/Olive.Entities.Data.SqlServer/SqlDataProvider.Criteria.cs new file mode 100644 index 000000000..6c25682b6 --- /dev/null +++ b/Olive.Entities.Data.SqlServer/SqlDataProvider.Criteria.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Olive.Entities.Data +{ + class SqlCriterionGenerator + { + DatabaseQuery Query; + Type EntityType; + + public SqlCriterionGenerator(DatabaseQuery query) + { + Query = query; + EntityType = query.EntityType; + } + + public string Generate(ICriterion criterion) + { + if (criterion == null) return "(1 = 1)"; + if (criterion is BinaryCriterion binary) return Generate(binary); + else if (criterion is DirectDatabaseCriterion direct) return Generate(direct); + else if (criterion.PropertyName.Contains(".")) return ToSubQuerySql(criterion); + else return ToSqlOn(criterion, EntityType); + } + + string Generate(BinaryCriterion criterion) + { + return $"({Generate(criterion.Left)} {criterion.Operator} {Generate(criterion.Right)})"; + } + + string Generate(DirectDatabaseCriterion criterion) + { + // Add the params: + if (criterion.Parameters != null) + foreach (var x in criterion.Parameters) Query.Parameters[x.Key] = x.Value; + + if (criterion.PropertyName.IsEmpty() || criterion.PropertyName == "N/A") + return criterion.SqlCriteria; + + return criterion.SqlCriteria.Replace($"{{{{{criterion.PropertyName}}}}}", + Query.Column(criterion.PropertyName)); + } + + string ToNestedSubQuerySql(ICriterion criterion, string[] parts) + { + var subQueries = new List(); + var type = EntityType; + + subQueries.Add(Query.Provider.MapSubquery(parts[0] + ".*")); + + for (var i = 0; i < parts.Length - 2; i++) + { + type = type.GetProperty(parts[i]).PropertyType; + + var dataProvider = Database.Instance.GetProvider(type); + var mapping = dataProvider.MapSubquery(parts[i + 1] + ".*"); + if (mapping.Lacks(" WHERE ")) continue; + + var subquerySplitted = mapping.Split(" WHERE ", StringSplitOptions.RemoveEmptyEntries); + var whereClauseSplitted = subquerySplitted[1].Split('='); + var tablePrefix = whereClauseSplitted.Last().Split('.')[0]; + + var aliasName = tablePrefix.Trim(']').Trim(']').Split('_').ExceptLast().ToString("_") + "_"; + + var recordIdentifier = whereClauseSplitted[1].Replace(tablePrefix, aliasName); + + mapping = subquerySplitted[0] + " WHERE " + whereClauseSplitted[0] + " = " + recordIdentifier; + + subQueries.Add(mapping); + } + + var subCriterion = new Criterion(parts[parts.Length - 1], criterion.FilterFunction, criterion.Value); + type = type.GetProperty(parts[parts.Length - 2]).PropertyType; + var criteria = new StringBuilder(); + var parenthesis = ""; + for (var i = 0; i < subQueries.Count; i++) + { + criteria.AppendLine($" EXISTS ({subQueries[i].WithSuffix(" AND ")}"); + parenthesis += ")"; + } + + criteria.Append(ToSqlOn(subCriterion, type)); + criteria.Append(parenthesis); + + return criteria.ToString(); + } + + string ToSubQuerySql(ICriterion criterion) + { + var parts = criterion.PropertyName.Split('.'); + + if (parts.Count() > 2) return ToNestedSubQuerySql(criterion, parts); + + var type = EntityType.GetProperty(parts[0])?.PropertyType; + if (type == null) throw new Exception($"{EntityType.Name} does not have a public property named {parts[0]}."); + + var subquery = Query.Provider.MapSubquery(parts[0] + ".*"); + var subCriterion = new Criterion(parts[1], criterion.FilterFunction, criterion.Value); + return "EXISTS ({0}{1})".FormatWith(subquery, ToSqlOn(subCriterion, type).WithPrefix(" AND ")); + } + + string ToSqlOn(ICriterion criterion, Type type) + { + string column; + + var key = criterion.PropertyName; + + if (key.EndsWith("Id") && key.Length > 2) + { + var association = type.GetProperty(key.TrimEnd("Id")); + + if (association != null && !association.Defines() && + association.PropertyType.IsA()) + key = key.TrimEnd("Id"); + } + + column = Query.Column(key); + + var value = criterion.Value; + var function = criterion.FilterFunction; + + if (value == null) + return "{0} IS {1} NULL".FormatWith(column, "NOT".OnlyWhen(function != FilterFunction.Is)); + + var valueData = value; + if (function == FilterFunction.Contains || function == FilterFunction.NotContains) valueData = "%{0}%".FormatWith(value); + else if (function == FilterFunction.BeginsWith) valueData = "{0}%".FormatWith(value); + else if (function == FilterFunction.EndsWith) valueData = "%{0}".FormatWith(value); + else if (function == FilterFunction.In) + { + if ((value as string) == "()") return "1 = 0 /*" + column + " IN ([empty])*/"; + else return column + " " + function.GetDatabaseOperator() + " " + value; + } + + var parameterName = GetUniqueParameterName(column); + + Query.Parameters.Add(parameterName, valueData); + + var critera = $"{column} {function.GetDatabaseOperator()} @{parameterName}"; + var includeNulls = function == FilterFunction.IsNot; + return includeNulls ? $"( {critera} OR {column} {FilterFunction.Null.GetDatabaseOperator()} )" : critera; + } + + string GetUniqueParameterName(string column) + { + var result = column.Remove("[").Remove("]").Replace(".", "_"); + + if (Query.Parameters.ContainsKey(result)) + { + for (var i = 2; ; i++) + { + var name = result + "_" + i; + if (Query.Parameters.LacksKey(name)) return name; + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data.SqlServer/SqlDataProvider.cs b/Olive.Entities.Data.SqlServer/SqlDataProvider.cs new file mode 100644 index 000000000..306f8359c --- /dev/null +++ b/Olive.Entities.Data.SqlServer/SqlDataProvider.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlClient; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Olive.Entities.Data +{ + /// + /// Provides a DataProvider for accessing data from the database using ADO.NET based on the SqlClient provider. + /// + public abstract partial class SqlDataProvider : DataProvider + { + public override IDataParameter GenerateParameter(KeyValuePair data) + { + var value = data.Value; + + var result = new SqlParameter { Value = value, ParameterName = data.Key.Remove(" ") }; + + if (value is DateTime) + { + result.DbType = DbType.DateTime2; + result.SqlDbType = SqlDbType.DateTime2; + } + + return result; + } + + public abstract string GetFields(); + + public abstract string GetTables(); + + public abstract IEntity Parse(IDataReader reader); + + public override async Task Get(object id) + { + var command = $"SELECT {GetFields()} FROM {GetTables()} WHERE {MapColumn("ID")} = @ID"; + + using (var reader = await ExecuteReader(command, CommandType.Text, CreateParameter("ID", id))) + { + var result = new List(); + + if (reader.Read()) return Parse(reader); + else throw new DataException($"There is no record with the the ID of '{id}'."); + } + } + + protected async Task ExecuteGetListReader(IDatabaseQuery query) + { + var command = GenerateSelectCommand(query); + return await ExecuteReader(command, CommandType.Text, GenerateParameters(query.Parameters)); + } + + public override async Task> GetList(IDatabaseQuery query) + { + using (var reader = await ExecuteGetListReader(query)) + { + var result = new List(); + while (reader.Read()) result.Add(Parse(reader)); + return result; + } + } + + public override DirectDatabaseCriterion GetAssociationInclusionCriteria(IDatabaseQuery query, + PropertyInfo association) + { + var whereClause = GenerateAssociationLoadingCriteria(query, association); + return new DirectDatabaseCriterion(whereClause) { Parameters = query.Parameters }; + } + + public override async Task Count(IDatabaseQuery query) + { + var command = GenerateCountCommand(query); + return (int)await ExecuteScalar(command, CommandType.Text, GenerateParameters(query.Parameters)); + } + + public override Task Aggregate(IDatabaseQuery query, AggregateFunction function, string propertyName) + { + var command = GenerateAggregateQuery(query, function, propertyName); + return ExecuteScalar(command, CommandType.Text, GenerateParameters(query.Parameters)); + } + + public string GenerateAggregateQuery(IDatabaseQuery query, AggregateFunction function, string propertyName) + { + var sqlFunction = function.ToString(); + + var columnValueExpression = MapColumn(propertyName); + + if (function == AggregateFunction.Average) + { + sqlFunction = "AVG"; + + var propertyType = query.EntityType.GetProperty(propertyName).PropertyType; + + if (propertyType == typeof(int) || propertyType == typeof(int?)) + columnValueExpression = $"CAST({columnValueExpression} AS decimal)"; + } + + return $"SELECT {sqlFunction}({columnValueExpression}) FROM {GetTables()}" + + GenerateWhere((DatabaseQuery)query); + } + + string GenerateAssociationLoadingCriteria(IDatabaseQuery iquery, PropertyInfo association) + { + var query = (DatabaseQuery)iquery; + + if (query.PageSize.HasValue && query.OrderByParts.None()) + throw new ArgumentException("PageSize cannot be used without OrderBy."); + + var r = new StringBuilder(); + + r.Append($"ID IN ("); + r.Append("SELECT "); + r.Append(query.TakeTop.ToStringOrEmpty().WithPrefix(" TOP ")); + r.AppendLine($" {association.Name} FROM {GetTables()}"); + r.AppendLine(GenerateWhere(query)); + r.AppendLine((query.PageSize.HasValue ? GenerateSort(query) : string.Empty).WithPrefix(" ORDER BY ")); + r.Append(")"); + + return r.ToString(); + } + + public string GenerateSelectCommand(IDatabaseQuery iquery) + { + var query = (DatabaseQuery)iquery; + + if (query.PageSize.HasValue && query.OrderByParts.None()) + throw new ArgumentException("PageSize cannot be used without OrderBy."); + + var r = new StringBuilder("SELECT"); + + r.Append(query.TakeTop.ToStringOrEmpty().WithPrefix(" TOP ")); + r.AppendLine($" {GetFields()} FROM {GetTables()}"); + r.AppendLine(GenerateWhere(query)); + r.AppendLine(GenerateSort(query).WithPrefix(" ORDER BY ")); + + return r.ToString(); + } + + public string GenerateCountCommand(IDatabaseQuery iquery) + { + var query = (DatabaseQuery)iquery; + + if (query.PageSize.HasValue) + throw new ArgumentException("PageSize cannot be used for Count()."); + + if (query.TakeTop.HasValue) + throw new ArgumentException("TakeTop cannot be used for Count()."); + + return $"SELECT Count(*) FROM {GetTables()} {GenerateWhere(query)}"; + } + + string GenerateWhere(DatabaseQuery query) + { + var r = new StringBuilder(); + + if (SoftDeleteAttribute.RequiresSoftdeleteQuery(query.EntityType)) + query.Criteria.Add(new Criterion("IsMarkedSoftDeleted", false)); + + r.Append($" WHERE { query.Column("ID")} IS NOT NULL"); + + var whereGenerator = new SqlCriterionGenerator(query); + foreach (var c in query.Criteria) + r.Append(whereGenerator.Generate(c).WithPrefix(" AND ")); + + return r.ToString(); + } + + string GenerateSort(DatabaseQuery query) + { + var parts = new List(); + + parts.AddRange(query.OrderByParts.Select(p => query.Column(p.Property) + " DESC".OnlyWhen(p.Descending))); + + var offset = string.Empty; + if (query.PageSize > 0) + offset = $" OFFSET {query.PageStartIndex} ROWS FETCH NEXT {query.PageSize} ROWS ONLY"; + + return parts.ToString(", ") + offset; + } + } +} diff --git a/Olive.Entities.Data.SqlServer/SqlServerManager.cs b/Olive.Entities.Data.SqlServer/SqlServerManager.cs new file mode 100644 index 000000000..563f262fd --- /dev/null +++ b/Olive.Entities.Data.SqlServer/SqlServerManager.cs @@ -0,0 +1,113 @@ +using System; +using System.Data; +using System.Data.SqlClient; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Olive.Entities.Data +{ + public class SqlServerManager + { + readonly string ConnectionString; + + public SqlServerManager() => ConnectionString = Config.GetConnectionString("AppDatabase"); + + public SqlServerManager(string connectionString) => ConnectionString = connectionString; + + /// + /// Executes a specified SQL command. + /// + public async Task ExecuteSql(string sql) + { + var lines = new Regex(@"^\s*GO\s*$", RegexOptions.Multiline).Split(sql); + + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + + using (var cmd = connection.CreateCommand()) + { + cmd.CommandType = CommandType.Text; + + foreach (var line in lines.Trim()) + { + cmd.CommandText = line; + + try { await cmd.ExecuteNonQueryAsync(); } + catch (Exception ex) { throw EnrichError(ex, line); } + } + } + } + } + + Exception EnrichError(Exception ex, string command) => + throw new Exception($"Could not execute SQL command: \r\n-----------------------\r\n{command.Trim()}\r\n-----------------------\r\n Because:\r\n\r\n{ex.Message}"); + + public async Task DetachDatabase(string databaseName) + { + string script = @" +ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; +ALTER DATABASE [{0}] SET MULTI_USER; +exec sp_detach_db '{0}'".FormatWith(databaseName); + + try + { + await ExecuteSql(script); + } + catch (Exception ex) + { + throw new Exception( + $"Could not detach database '{databaseName}' becuase '{ex.Message}'", ex); + } + } + + public async Task DeleteDatabase(string databaseName) + { + string script = @" +IF EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name = N'{0}') +BEGIN + ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + ALTER DATABASE [{0}] SET MULTI_USER; + DROP DATABASE [{0}]; +END".FormatWith(databaseName); + + try + { + await ExecuteSql(script); + } + catch (Exception ex) + { + throw new Exception("Could not drop database '" + databaseName + "'.", ex); + } + } + + public SqlServerManager CloneFor(string databaseName) + { + var builder = new SqlConnectionStringBuilder(ConnectionString) + { + InitialCatalog = databaseName + }; + + return new SqlServerManager(builder.ToString()); + } + + public async Task DatabaseExists(string databaseName) + { + var script = $"SELECT count(name) FROM master.dbo.sysdatabases WHERE name = N'{databaseName}'"; + + using (var connection = new SqlConnection(ConnectionString)) + { + await connection.OpenAsync(); + + using (var cmd = connection.CreateCommand()) + { + cmd.CommandType = CommandType.Text; + cmd.CommandText = script; + + try { return (int)await cmd.ExecuteScalarAsync() > 0; } + catch (Exception ex) { throw EnrichError(ex, script); } + } + } + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/ConcurrencyDataException.cs b/Olive.Entities.Data/API/ConcurrencyDataException.cs new file mode 100644 index 000000000..b7ec059a1 --- /dev/null +++ b/Olive.Entities.Data/API/ConcurrencyDataException.cs @@ -0,0 +1,9 @@ +namespace Olive.Entities.Data +{ + public class ConcurrencyException : ValidationException + { + public ConcurrencyException() { } + + public ConcurrencyException(string message) : base(message) { } + } +} diff --git a/Olive.Entities.Data/API/Database/Database.Count.cs b/Olive.Entities.Data/API/Database/Database.Count.cs new file mode 100644 index 000000000..b56870290 --- /dev/null +++ b/Olive.Entities.Data/API/Database/Database.Count.cs @@ -0,0 +1,23 @@ +namespace Olive.Entities.Data +{ + using System; + using System.Linq.Expressions; + using System.Threading.Tasks; + + public partial class Database + { + /// + /// Gets a list of entities of the given type from the database. + /// + public Task Count() where T : IEntity +=> Of().Count(); + + /// + /// Gets a list of entities of the given type from the database. + /// + public Task Count(Expression> criteria) where T : IEntity + { + return Of().Where(criteria).Count(); + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Database.Delete.cs b/Olive.Entities.Data/API/Database/Database.Delete.cs new file mode 100644 index 000000000..273f1fcd3 --- /dev/null +++ b/Olive.Entities.Data/API/Database/Database.Delete.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using System.Transactions; + +namespace Olive.Entities.Data +{ + partial class Database + { + /// + /// Deletes the specified record from the data repository. + /// + public async Task Delete(IEntity instance) => await Delete(instance, DeleteBehaviour.Default); + + async Task DoDelete(Entity entity, DeleteBehaviour behaviour) + { + // Raise deleting event + if (!IsSet(behaviour, DeleteBehaviour.BypassDeleting)) + { + var deletingArgs = new System.ComponentModel.CancelEventArgs(); + await EntityManager.RaiseOnDeleting(entity, deletingArgs); + + if (deletingArgs.Cancel) + { + Cache.Current.Remove(entity); + return; + } + } + + if (SoftDeleteAttribute.IsEnabled(entity.GetType()) && !SoftDeleteAttribute.Context.ShouldByPassSoftDelete()) + { + // Soft delete: + EntityManager.MarkSoftDeleted(entity); + await GetProvider(entity).Save(entity); + } + else + { + await GetProvider(entity).Delete(entity); + } + } + + /// + /// Deletes the specified record from the data repository. + /// + public async Task Delete(IEntity instance, DeleteBehaviour behaviour) + { + if (instance == null) + throw new ArgumentNullException(nameof(instance)); + + var entity = instance as Entity; + + if (entity == null) + throw new ArgumentException($"The type of the specified object to delete does not inherit from {typeof(Entity).FullName} class."); + + await EnlistOrCreateTransaction(async () => await DoDelete(entity, behaviour)); + + Cache.Current.Remove(entity); + if (Transaction.Current != null) + Transaction.Current.TransactionCompleted += (s, e) => { Cache.Current.Remove(entity); }; + + if (DbTransactionScope.Root != null) + DbTransactionScope.Root.OnTransactionCompleted(() => Cache.Current.Remove(entity)); + + if (!IsSet(behaviour, DeleteBehaviour.BypassLogging)) + if (!(entity is IApplicationEvent) && Config.Get("Log.Record:Application:Events", defaultValue: true)) + await ApplicationEventManager.RecordDelete(entity); + + await OnUpdated(entity); + + if (!IsSet(behaviour, DeleteBehaviour.BypassDeleted)) + await EntityManager.RaiseOnDeleted(entity); + } + + /// + /// Deletes the specified instances from the data repository. + /// The operation will be done in a transaction. + /// + public async Task Delete(IEnumerable instances) where T : IEntity + { + if (instances == null) + throw new ArgumentNullException(nameof(instances)); + + if (instances.None()) return; + + await EnlistOrCreateTransaction(async () => + { + foreach (T obj in instances.ToArray()) await Delete(obj); + }); + } + + /// + /// Deletes all objects of the specified type. + /// + /// + public async Task DeleteAll() where T : IEntity => await Delete(await GetList()); + + /// + /// Deletes all objects of the specified type matching the given criteria. + /// + public async Task DeleteAll(Expression> criteria) where T : IEntity => await Delete(await GetList(criteria)); + + /// + /// Updates all records in the database with the specified change. + /// + public async Task UpdateAll(Action change) where T : IEntity + { + var records = await GetList(); + await Update(records, change); + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Database.Find.cs b/Olive.Entities.Data/API/Database/Database.Find.cs new file mode 100644 index 000000000..5e44d45cb --- /dev/null +++ b/Olive.Entities.Data/API/Database/Database.Find.cs @@ -0,0 +1,18 @@ +namespace Olive.Entities.Data +{ + using System; + using System.Linq.Expressions; + using System.Threading.Tasks; + + partial class Database + { + /// + /// Finds an object with the specified type matching the specified criteria. + /// If not found, it returns null. + /// + public Task FirstOrDefault(Expression> criteria) where T : IEntity + { + return Of().Where(criteria).FirstOrDefault(); + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Database.Get.cs b/Olive.Entities.Data/API/Database/Database.Get.cs new file mode 100644 index 000000000..8f78e439b --- /dev/null +++ b/Olive.Entities.Data/API/Database/Database.Get.cs @@ -0,0 +1,160 @@ +using System; +using System.Threading.Tasks; + +namespace Olive.Entities.Data +{ + partial class Database + { + internal async Task GetConcrete(object entityID, Type concreteType) + { + var result = Cache.Current.Get(concreteType, entityID.ToString()); + if (result != null) return result; + + var timestamp = Cache.GetQueryTimestamp(); + + result = await FromDatabase(entityID, concreteType); + + // Don't cache the result if it is fetched in a transaction. + if (result != null) TryCache(result, timestamp); + + return result; + } + + internal void TryCache(IEntity item, DateTime? queryTime) + { + if (AnyOpenTransaction()) return; + if (queryTime != null && Cache.Current.IsUpdatedSince(item, queryTime.Value)) return; + Cache.Current.Add(item); + } + + async Task FromDatabase(object entityID, Type concreteType) + { + var result = await GetProvider(concreteType).Get(entityID); + + if (result != null) await EntityManager.RaiseOnLoaded(result as Entity); + + return result; + } + + /// + /// Gets an Entity of the given type with the given Id from the database. + /// If it can't find the object, an exception will be thrown. + /// + /// The primary key value of the object to load in string format. + public async Task Get(string entityId) where T : IEntity + { + if (entityId.IsEmpty()) return default(T); + + return (T)await Get(entityId, typeof(T)); + } + + /// + /// Get an entity with the given type and ID from the database. + /// If it can't find the object, an exception will be thrown. + /// + /// The type of the object to get + /// The primary key value of the object to load. + public async Task Get(Guid id) where T : IEntity + { + if (id == Guid.Empty) + throw new ArgumentException($"Could not load the {typeof(T).Name} because the given objectID is empty."); + + return (T)await Get(id, typeof(T)); + } + + /// + /// Get an entity with the given type and ID from the database. + /// If it can't find the object, an exception will be thrown. + /// + /// The type of the object to get + /// The primary key value of the object to load. + public async Task Get(Guid? id) where T : IEntity + { + if (id.HasValue) return await Get(id.Value); + else return default(T); + } + + /// + /// Get an entity with the given type and ID from the database. + /// If it can't find the object, an exception will be thrown. + /// + /// The type of the object to get + /// The primary key value of the object to load. + public async Task Get(int? id) where T : IEntity + { + if (id == null) return default(T); + return (T)await Get(id.Value, typeof(T)); + } + + public async Task Get(int id) where T : IEntity => (T)await Get(id, typeof(T)); + + /// + /// Get an entity with the given type and ID from the database. + /// If it can't find the object, an exception will be thrown. + /// + /// The primary key value of the object to load. + public async Task> Get(Guid entityID, Type objectType) => await Get((object)entityID, objectType) as IEntity; + + /// + /// Get an entity with the given type and ID from the database. + /// If it can't find the object, an exception will be thrown. + /// + /// The primary key value of the object to load. + public async Task Get(object entityID, Type objectType) + { + if (objectType == null) return null; + + IEntity result = null; + + if (NeedsTypeResolution(objectType)) + { + foreach (var provider in ResolveDataProviders(objectType)) + { + try + { + result = Cache.Current.Get(entityID.ToString()); + if (result != null) return result; + + result = await provider.Get(entityID); + if (result != null) break; + } + catch + { + continue; + } + } + } + else + { + result = await GetConcrete(entityID, objectType); + } + + if (result != null) + return result; + else + throw new ArgumentException($"Could not load the {objectType.FullName} instance with the ID of {entityID}."); + } + + /// + /// Get an entity with the given type and ID from the database. + /// If the key does not exist, it will return null, rather than throwing an exception. + /// + /// The type of the object to get + /// The primary key value of the object to load. + public async Task GetOrDefault(object id) where T : IEntity => (T)await GetOrDefault(id, typeof(T)); + + /// + /// Get an entity with the given type and ID from the database. + /// If the key does not exist, it will return null, rather than throwing an exception. + /// + /// The type of the object to get + /// The primary key value of the object to load. + public async Task GetOrDefault(object id, Type type) + { + if (id.ToStringOrEmpty().IsEmpty()) return null; + + try { return await Get(id, type); } + catch { return null; } + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Database.GetList.cs b/Olive.Entities.Data/API/Database/Database.GetList.cs new file mode 100644 index 000000000..f4fcbb9db --- /dev/null +++ b/Olive.Entities.Data/API/Database/Database.GetList.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace Olive.Entities.Data +{ + partial class Database + { + /// + /// Gets a list of entities of the given type from the database. + /// + public Task> GetList(Expression> criteria = null) where T : IEntity + { + return Of().Where(criteria).GetList(); + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Database.ProviderManagement.cs b/Olive.Entities.Data/API/Database/Database.ProviderManagement.cs new file mode 100644 index 000000000..f9a821ae0 --- /dev/null +++ b/Olive.Entities.Data/API/Database/Database.ProviderManagement.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Transactions; + +namespace Olive.Entities.Data +{ + partial class Database + { + static readonly Database instance = new Database(); + + Database() + { + AssemblyProviderFactories = new Dictionary(); + TypeProviderFactories = new Dictionary(); + + // Load from configuration: + var configSection = Config.Bind("DataProviderModel"); + + if (configSection != null) + { + if (configSection.Providers != null) + foreach (var factoryInfo in configSection.Providers) + RegisterDataProviderFactory(factoryInfo); + } + } + + public static Database Instance => instance; + + #region Updated event + /// + /// It's raised when any record is saved or deleted in the system. + /// + public AsyncEvent Updated { get; } = new AsyncEvent(); + + Task OnUpdated(IEntity entity) => Updated.Raise(entity); + + #endregion + + object DataProviderSyncLock = new object(); + public void RegisterDataProviderFactory(DataProviderFactoryInfo factoryInfo) + { + if (factoryInfo == null) throw new ArgumentNullException(nameof(factoryInfo)); + + lock (DataProviderSyncLock) + { + var type = factoryInfo.GetMappedType(); + var assembly = factoryInfo.GetAssembly(); + + // var providerFactoryType = Type.GetType(factoryInfo.ProviderFactoryType); HAS A PROIBLEM WITH VERSIONING + var providerFactoryType = assembly.GetTypes().FirstOrDefault(t => t.AssemblyQualifiedName == factoryInfo.ProviderFactoryType); + if (providerFactoryType == null) providerFactoryType = assembly.GetType(factoryInfo.ProviderFactoryType); + if (providerFactoryType == null) providerFactoryType = Type.GetType(factoryInfo.ProviderFactoryType); + + if (providerFactoryType == null) + throw new Exception("Could not find the type " + factoryInfo.ProviderFactoryType + " as specified in configuration."); + + var providerFactory = (IDataProviderFactory)Activator.CreateInstance(providerFactoryType, factoryInfo); + + if (type != null) + { + TypeProviderFactories[type] = providerFactory; + } + else if (assembly != null && providerFactory != null) + { + AssemblyProviderFactories[assembly] = providerFactory; + } + + EntityFinder.ResetCache(); + } + } + + public Dictionary AssemblyProviderFactories { get; internal set; } + Dictionary TypeProviderFactories; + + /// + /// Gets the assemblies for which a data provider factory has been registered in the current domain. + /// + public IEnumerable GetRegisteredAssemblies() + { + return TypeProviderFactories.Keys.Select(t => t.GetTypeInfo().Assembly).Concat(AssemblyProviderFactories.Keys).Distinct().ToArray(); + } + + public IDataProvider GetProvider() where T : IEntity => GetProvider(typeof(T)); + + public IDataAccess GetAccess(Type type) => GetProvider(type).Access; + + public IDataAccess GetAccess() where TEntity : IEntity => GetProvider().Access; + + public IDataAccess GetAccess(string connectionString = null) + { + if (connectionString.IsEmpty()) connectionString = DataAccess.GetCurrentConnectionString(); + + var factory = TypeProviderFactories.Values + .Concat(AssemblyProviderFactories.Values) + .FirstOrDefault(x => x.ConnectionString == connectionString); + + if (factory == null) + throw new Exception("No data provider factory's connection string matched the specified connection string."); + + return factory.GetAccess(); + } + + public IDataProvider GetProvider(IEntity item) => GetProvider(item.GetType()); + + public IDataProvider GetProvider(Type type) + { + if (TypeProviderFactories.ContainsKey(type)) + return TypeProviderFactories[type].GetProvider(type); + + // Strange bug: + if (AssemblyProviderFactories.Any(x => x.Key == null)) + AssemblyProviderFactories = new Dictionary(); + + if (!AssemblyProviderFactories.ContainsKey(type.GetTypeInfo().Assembly)) + throw new InvalidOperationException("There is no registered 'data provider' for the assembly: " + type.GetTypeInfo().Assembly.FullName); + + return AssemblyProviderFactories[type.GetTypeInfo().Assembly].GetProvider(type); + } + + /// + /// Creates a transaction scope. + /// + public ITransactionScope CreateTransactionScope(DbTransactionScopeOption option = DbTransactionScopeOption.Required) + { + var isolationLevel = Config.Get("Default.Transaction.IsolationLevel", System.Data.IsolationLevel.Serializable); + + var typeName = Config.Get("Default.TransactionScope.Type"); + + if (typeName.HasValue()) + { + Type type = null; // this is a workaround. + var dummy = typeof(DbTransactionScope); + if (dummy.AssemblyQualifiedName.StartsWith(typeName)) + type = dummy; + else + type = Type.GetType(typeName); + + if (type == null) throw new Exception("Cannot load type: " + typeName); + + return (ITransactionScope)type.CreateInstance(isolationLevel, option); + } + + // Fall back to TransactionScope: + var oldOption = option.ToString().To(); + return new TransactionScopeWrapper(isolationLevel.ToString().To().CreateScope(oldOption)); + } + + List ResolveDataProviders(Type baseType) + { + var factories = AssemblyProviderFactories.Where(f => f.Value.SupportsPolymorphism() && f.Key.References(baseType.GetTypeInfo().Assembly)).ToList(); + + var result = new List(); + + foreach (var f in factories) + result.Add(f.Value.GetProvider(baseType)); + + foreach (var type in EntityFinder.FindPossibleTypes(baseType, mustFind: factories.None())) + result.Add(GetProvider(type)); + + return result; + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Database.Save.List.cs b/Olive.Entities.Data/API/Database/Database.Save.List.cs new file mode 100644 index 000000000..c62640efe --- /dev/null +++ b/Olive.Entities.Data/API/Database/Database.Save.List.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Olive.Entities.Data +{ + partial class Database + { + /// + /// Saves the specified records in the data repository. + /// The operation will run in a Transaction. + /// + public async Task> Save(T[] records) where T : IEntity => await Save(records as IEnumerable); + + /// + /// Saves the specified records in the data repository. + /// The operation will run in a Transaction. + /// + public async Task> Save(IEnumerable records) where T : IEntity => await Save(records, SaveBehaviour.Default); + + /// + /// Saves the specified records in the data repository. + /// The operation will run in a Transaction. + /// + public async Task> Save(IEnumerable records, SaveBehaviour behaviour) where T : IEntity + { + if (records == null) + throw new ArgumentNullException(nameof(records)); + + if (records.None()) return records; + + await EnlistOrCreateTransaction(async () => + { + foreach (var record in records) + await Save(record as Entity, behaviour); + }); + + return records; + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Database.Save.cs b/Olive.Entities.Data/API/Database/Database.Save.cs new file mode 100644 index 000000000..99ef5b6b4 --- /dev/null +++ b/Olive.Entities.Data/API/Database/Database.Save.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Transactions; + +namespace Olive.Entities.Data +{ + partial class Database + { + bool ENFORCE_SAVE_TRANSACTION = Config.Get("Database:Save.Enforce.Transaction", defaultValue: false); + + static ConcurrentDictionary StringKeySyncLocks = new ConcurrentDictionary(); + + public static AsyncLock GetSyncLock(string key) => StringKeySyncLocks.GetOrAdd(key, f => new AsyncLock()); + + /// + /// Inserts or updates an object in the database. + /// + public async Task Save(T entity) where T : IEntity + { + await Save(entity as IEntity, SaveBehaviour.Default); + return entity; + } + + /// + /// Inserts or updates an object in the database. + /// + public async Task Save(IEntity entity, SaveBehaviour behaviour) + { + if (entity == null) throw new ArgumentNullException(nameof(entity)); + + Func save = async () => await DoSave(entity, behaviour); + + Func doSave = async () => + { + if (entity.IsNew) await save(); + else using (await GetSyncLock(entity.GetType().FullName + entity.GetId()).Lock()) await save(); + }; + + if (ENFORCE_SAVE_TRANSACTION) await EnlistOrCreateTransaction(doSave); + else await doSave(); + } + + async Task DoSave(IEntity entity, SaveBehaviour behaviour) + { + var mode = entity.IsNew ? SaveMode.Insert : SaveMode.Update; + + var asEntity = entity as Entity; + if (mode == SaveMode.Update && (asEntity._ClonedFrom?.IsStale == true) && AnyOpenTransaction()) + { + throw new InvalidOperationException("This " + entity.GetType().Name + " instance in memory is out-of-date. " + + "A clone of it is already updated in the transaction. It is not allowed to update the same instance multiple times in a transaction, because then the earlier updates would be overwriten by the older state of the instance in memory. \r\n\r\n" + + @"BAD: +Database.Update(myObject, x=> x.P1 = ...); // Note: this could also be nested inside another method that's called here instead. +Database.Update(myObject, x=> x.P2 = ...); + +GOOD: +Database.Update(myObject, x=> x.P1 = ...); +myObject = Database.Reload(myObject); +Database.Update(myObject, x=> x.P2 = ...);"); + } + + if (EntityManager.IsImmutable(entity)) + throw new ArgumentException("An immutable record must be cloned before any modifications can be applied on it. " + + $"Type={entity.GetType().FullName}, Id={entity.GetId()}."); + + var dataProvider = GetProvider(entity); + + if (!IsSet(behaviour, SaveBehaviour.BypassValidation)) + { + await EntityManager.RaiseOnValidating(entity as Entity, EventArgs.Empty); + await entity.Validate(); + } + else if (!dataProvider.SupportValidationBypassing()) + { + throw new ArgumentException(dataProvider.GetType().Name + " does not support bypassing validation."); + } + + #region Raise saving event + + if (!IsSet(behaviour, SaveBehaviour.BypassSaving)) + { + var savingArgs = new System.ComponentModel.CancelEventArgs(); + await EntityManager.RaiseOnSaving(entity, savingArgs); + + if (savingArgs.Cancel) + { + Cache.Current.Remove(entity); + return; + } + } + + #endregion + + if (!IsSet(behaviour, SaveBehaviour.BypassLogging) && !(entity is IApplicationEvent) && + Config.Get("Log.Record:Application:Events", defaultValue: true)) + await ApplicationEventManager.RecordSave(entity, mode); + + await dataProvider.Save(entity); + Cache.Current.UpdateRowVersion(entity); + + if (mode == SaveMode.Update && asEntity?._ClonedFrom != null && AnyOpenTransaction()) + { + asEntity._ClonedFrom.IsStale = true; + asEntity.IsStale = false; + } + + if (mode == SaveMode.Insert) + EntityManager.SetSaved(entity); + + Cache.Current.Remove(entity); + + if (Transaction.Current != null) + Transaction.Current.TransactionCompleted += (s, e) => { Cache.Current.Remove(entity); }; + + if (DbTransactionScope.Root != null) + DbTransactionScope.Root.OnTransactionCompleted(() => Cache.Current.Remove(entity)); + + if (!(entity is IApplicationEvent)) + await OnUpdated(entity); + + if (!IsSet(behaviour, SaveBehaviour.BypassSaved)) + await EntityManager.RaiseOnSaved(entity, new SaveEventArgs(mode)); + + // OnSaved event handler might have read the object again and put it in the cache, which would + // create invalid CachedReference objects. + Cache.Current.Remove(entity); + } + + /// + /// Saves the specified records in the data repository. + /// The operation will run in a Transaction. + /// + public async Task> Save(List records) where T : IEntity => await Save(records as IEnumerable); + + /* ===================== Update ========================*/ + + /// + /// Runs an update command on a list of given objects and persists the updated objects in database. + /// It returns the updated instances. + /// + /// The objects to be updated in database. + /// Update action. For example: o=>o.Property = "Value" + public async Task> Update(IEnumerable items, Action action) where T : IEntity => + await Update(items, action, SaveBehaviour.Default); + + /// + /// Runs an update command on a list of given objects and persists the updated objects in database. + /// It returns the updated instances. + /// + /// The objects to be updated in database. + /// Update action. For example: o=>o.Property = "Value" + public async Task> Update(IEnumerable items, Action action, SaveBehaviour behaviour) where T : IEntity + { + var result = new List(); + + await EnlistOrCreateTransaction(async () => + { + foreach (var item in items) + result.Add(await Update(item, action, behaviour)); + }); + + return result; + } + + /// + /// Runs an update command on a given object's clone and persists the updated object in database. It returns the updated instance. + /// + /// The object to be updated in database. + /// Update action. For example: o=>o.Property = "Value" + public async Task Update(T item, Action action) where T : IEntity => await Update(item, action, SaveBehaviour.Default); + + /// + /// Runs an update command on a given object's clone and persists the updated object in database. It returns the updated instance. + /// + /// The object to be updated in database. + /// Update action. For example: o=>o.Property = "Value" + public async Task Update(T item, Action action, SaveBehaviour behaviour) where T : IEntity + { + if (item == null) + throw new ArgumentNullException(nameof(item)); + + if (action == null) + throw new ArgumentNullException(nameof(action)); + + if (item.IsNew) + throw new InvalidOperationException("New instances cannot be updated using the Update method."); + + if (!(item is Entity)) + throw new ArgumentException($"Database.Update() method accepts a type inheriting from {typeof(Entity).FullName}. So {typeof(T).FullName} is not supported."); + + if ((item as Entity)._ClonedFrom?.IsStale == true && AnyOpenTransaction()) + // No need for an error. We can just get the fresh version here. + item = await Reload(item); + + if (EntityManager.IsImmutable(item as Entity)) + { + var clone = (T)((IEntity)item).Clone(); + + action(clone); + + await Save(clone as Entity, behaviour); + + if (!AnyOpenTransaction()) + action(item); + + return clone; + } + else + { + action(item); + await Save(item, behaviour); + + return item; + } + } + + /// + /// Inserts the specified objects in bulk. None of the object events will be triggered. + /// + public async Task BulkInsert(Entity[] objects, int batchSize = 10, bool bypassValidation = false) + { + if (!bypassValidation) + await objects.ValidateAll(); + + var objectTypes = objects.GroupBy(o => o.GetType()).ToArray(); + + try + { + foreach (var group in objectTypes) + await GetProvider(group.Key).BulkInsert(group.ToArray(), batchSize); + + foreach (var type in objectTypes) + Cache.Current.Remove(type.Key); + } + catch + { + await Refresh(); + throw; + } + } + + /// + /// Updates the specified objects in bulk. None of the object events will be triggered. + /// + public async Task BulkUpdate(Entity[] objects, int batchSize = 10, bool bypassValidation = false) + { + if (!bypassValidation) + await objects.ValidateAll(); + + var objectTypes = objects.GroupBy(o => o.GetType()).ToArray(); + + try + { + foreach (var group in objectTypes) + { + var records = group.ToArray(); + await GetProvider(group.Key).BulkUpdate(records, batchSize); + } + + foreach (var type in objectTypes) + Cache.Current.Remove(type.Key); + } + catch + { + await Refresh(); + throw; + } + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Database.cs b/Olive.Entities.Data/API/Database/Database.cs new file mode 100644 index 000000000..b7eb1ea07 --- /dev/null +++ b/Olive.Entities.Data/API/Database/Database.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading.Tasks; +using System.Transactions; + +namespace Olive.Entities.Data +{ + /// + /// Data access code for Application components. + /// + public partial class Database : IDatabase + { + bool IsSet(SaveBehaviour setting, SaveBehaviour behaviour) => (setting & behaviour) == behaviour; + + bool IsSet(DeleteBehaviour setting, DeleteBehaviour behaviour) => (setting & behaviour) == behaviour; + + bool NeedsTypeResolution(Type type) => type.IsInterface || type == typeof(Entity); + + public AsyncEvent CacheRefreshed { get; } = new AsyncEvent(); + /// + /// Clears the cache of all items. + /// + public async Task Refresh() + { + Cache.Current.ClearAll(); + + await CacheRefreshed.Raise(); + } + + public bool AnyOpenTransaction() => Transaction.Current != null || DbTransactionScope.Root != null; + + /// + /// If there is an existing open transaction, it will simply run the specified action in it, Otherwise it will create a new transaction. + /// + public async Task EnlistOrCreateTransaction(Func func) + { + if (AnyOpenTransaction()) await func?.Invoke(); + else + { + using (var scope = CreateTransactionScope()) + { + await func?.Invoke(); + + scope.Complete(); + } + } + } + + /// + /// Returns the first record of the specified type of which ToString() would return the specified text . + /// + public async Task Parse(string toString, bool caseSensitive = false) where T : IEntity + { + // TODO: I have replaced StringComparison.InvariantCulture with StringComparison.Ordinal + // It is possible to switch it back in next verions of .Net Core. + var comparison = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + + foreach (var instance in await GetList()) + { + string objectString; + try { objectString = instance.ToString(); } + catch (Exception ex) + { + throw new Exception("Database.Parse() failed. Calling ToString() throw an exception on the {0} object with ID of '{1}'" + .FormatWith(typeof(T).Name, instance.GetId(), ex)); + } + + if (toString == null && objectString == null) return instance; + + if (toString == null || objectString == null) continue; + + if (objectString.Equals(toString, comparison)) return instance; + } + + return default(T); + } + + /// + /// Gets the total number of objects in cache. + /// + public int CountAllObjectsInCache() => Cache.Current.CountAllObjects(); + + public async Task> ReadManyToManyRelation(IEntity instance, string property) + { + if (instance == null) throw new ArgumentNullException(nameof(instance)); + + return await GetProvider(instance).ReadManyToManyRelation(instance, property); + } + + /// + /// Gets a reloaded instance from the database to get a synced copy. + /// + public async Task Reload(T instance) where T : IEntity + { + if (instance == null) throw new ArgumentNullException(nameof(instance)); + + Cache.Current.Remove(instance); + + return (T)await Get(instance.GetId(), instance.GetType()); + } + + /// + /// Determines if there is any object in the database of the specified type. + /// + public async Task Any() where T : IEntity => await Of().Count() > 0; + + /// + /// Determines if there is any object in the database of the specified type matching a given criteria. + /// + public async Task Any(Expression> criteria) where T : IEntity => + await Of().Where(criteria).Count() > 0; + + /// + /// Determines whether there is no object of the specified type in the database. + /// + public async Task None() where T : IEntity => !await Any(); + + /// + /// Determines whether none of the objects in the database match a given criteria. + /// + public async Task None(Expression> criteria) where T : IEntity => !await Any(criteria); + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Query/AssociationInclusion.cs b/Olive.Entities.Data/API/Database/Query/AssociationInclusion.cs new file mode 100644 index 000000000..49901ba41 --- /dev/null +++ b/Olive.Entities.Data/API/Database/Query/AssociationInclusion.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Olive.Entities.Data +{ + /// + /// It provides a tree for the association properties + /// + public class AssociationInclusion + { + public PropertyInfo Association { get; set; } + List IncludedNestedAssociations = new List(); + + public static AssociationInclusion Create(PropertyInfo association) => + new AssociationInclusion { Association = association }; + + public void IncludeNestedAssociation(string nestedAssociation) + => IncludedNestedAssociations.Add(nestedAssociation); + + public async Task LoadAssociations(DatabaseQuery query, IEnumerable mainObjects) + { + var associatedObjects = LoadTheAssociatedObjects(query); + + var groupedObjects = GroupTheMainObjects(mainObjects); + + var cachedField = query.EntityType.GetField("cached" + Association.Name, + BindingFlags.NonPublic | BindingFlags.Instance); + + if (cachedField != null) + foreach (var associatedObject in await associatedObjects) + foreach (var mainEntity in groupedObjects[associatedObject.GetId()]) + BindToCachedField(cachedField, associatedObject, mainEntity); + } + + void BindToCachedField(FieldInfo cachedField, IEntity associatedObject, IEntity mainEntity) + { + var cachedRef = cachedField.GetValue(mainEntity); + + var bindMethod = cachedRef.GetType().GetMethod("Bind", + BindingFlags.NonPublic | BindingFlags.Instance); + + bindMethod?.Invoke(cachedRef, new[] { associatedObject }); + } + + Dictionary GroupTheMainObjects(IEnumerable mainObjects) + { + var idProperty = Association.DeclaringType.GetProperty(Association.Name + "Id"); + + var groupedResult = mainObjects.GroupBy(item => idProperty.GetValue(item)) + .ToDictionary(i => i.Key, i => i.ToArray()); + return groupedResult; + } + + Task> LoadTheAssociatedObjects(DatabaseQuery query) + { + return Database.Instance.Of(Association.PropertyType) + .Where(query.Provider.GetAssociationInclusionCriteria(query, Association)) + .Include(IncludedNestedAssociations) + .GetList(); + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Query/DatabaseQuery.Execution.cs b/Olive.Entities.Data/API/Database/Query/DatabaseQuery.Execution.cs new file mode 100644 index 000000000..59bd22a3b --- /dev/null +++ b/Olive.Entities.Data/API/Database/Query/DatabaseQuery.Execution.cs @@ -0,0 +1,137 @@ +namespace Olive.Entities.Data +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Threading.Tasks; + + partial class DatabaseQuery + { + bool IsCacheable() + { + if (PageSize.HasValue) return false; + + if (Criteria.Except(typeof(DirectDatabaseCriterion)).Any(c => c.PropertyName.Contains("."))) + return false; // This doesn't work with cache expiration rules. + + if (Criteria.OfType().Any(x => !x.IsCacheSafe)) + return false; + + // Do not cache a polymorphic call: + if (NeedsTypeResolution()) return false; + + return true; + } + + string GetCacheKey() + { + var r = new StringBuilder(); + r.Append(EntityType.GetCachedAssemblyQualifiedName()); + + r.Append(':'); + + foreach (var c in Criteria) + { + r.Append(c.ToString()); + r.Append('|'); + } + + if (TakeTop.HasValue) r.Append("|N:" + TakeTop); + + r.Append(OrderByParts.Select(x => x.ToString()).ToString(",").WithPrefix("|S:")); + + return r.ToString(); + } + + public async Task> GetList() + { + if (!IsCacheable()) return await LoadFromDatabase(); + + var cacheKey = GetCacheKey(); + + var result = Cache.Current.GetList(EntityType, cacheKey)?.Cast(); + if (result != null) + { + await LoadIncludedAssociations(result); + return result; + } + + result = await LoadFromDatabaseAndCache(); + + // If there is no transaction open, cache it: + if (!Database.Instance.AnyOpenTransaction()) + Cache.Current.AddList(EntityType, cacheKey, result); + + return result; + } + + async Task> LoadFromDatabase() + { + List result; + if (NeedsTypeResolution()) + { + var queries = ResolveDataProviders().Select(p => p.GetList(this)); + result = (await queries.AwaitAll()).SelectMany(x => x).ToList(); + } + else + result = (await Provider.GetList(this)).ToList(); + + if (OrderByParts.None()) + { + // TODO: If the entity is sortable by a single DB column, then automatically add that to the DB call. + result.Sort(); + } + + await LoadIncludedAssociations(result); + + return result; + } + + async Task LoadIncludedAssociations(IEnumerable mainResult) + { + foreach (var associationHeirarchy in Include) + { + await associationHeirarchy.LoadAssociations(this, mainResult); + //await new AssociationEagerLoadService(mainResult, associationHeirarchy.Association, associationHeirarchy.SubAssociations, this).Run(); + } + } + + async Task> LoadFromDatabaseAndCache() + { + var timestamp = Cache.GetQueryTimestamp(); + + var result = new List(); + + foreach (var item in await LoadFromDatabase()) + { + var inCache = Cache.Current.Get(item.GetType(), item.GetId().ToString()); + if (inCache != null) result.Add(inCache); + else + { + await EntityManager.RaiseOnLoaded(item); + Database.Instance.TryCache(item, timestamp); + result.Add(item); + } + } + + return result; + } + + public Task Count() => Provider.Count(this); + + public async Task FirstOrDefault() + { + TakeTop = 1; + return (await Provider.GetList(this)).FirstOrDefault(); + } + } + + partial class DatabaseQuery + { + public new async Task> GetList() => (await base.GetList()).Cast(); + + public new async Task FirstOrDefault() => (TEntity)(await base.FirstOrDefault()); + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Query/DatabaseQuery.OrderBy.cs b/Olive.Entities.Data/API/Database/Query/DatabaseQuery.OrderBy.cs new file mode 100644 index 000000000..8d8de9007 --- /dev/null +++ b/Olive.Entities.Data/API/Database/Query/DatabaseQuery.OrderBy.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; + +namespace Olive.Entities.Data +{ + partial class DatabaseQuery + { + internal const bool DESC = true; + public List OrderByParts = new List(); + + public class OrderByPart + { + public string Property; + public bool Descending; + + public override string ToString() => Property + (DESC ? ".DESC" : null); + } + + IDatabaseQuery IDatabaseQuery.OrderBy(string property, bool descending) + { + if (OrderByParts.Any()) + throw new Exception("There is already an OrderBy part added. Use ThenBy()."); + + return this.ThenBy(property, descending); + } + + IDatabaseQuery IDatabaseQuery.ThenBy(string property, bool descending) + { + OrderByParts.Add(new OrderByPart { Property = property, Descending = descending }); + return this; + } + } + + partial class DatabaseQuery + { + public IDatabaseQuery ThenBy(Expression> property, bool descending = false) + { + var propertyExpression = (property.Body as UnaryExpression)?.Operand as MemberExpression; + if (propertyExpression == null || !(propertyExpression.Expression is ParameterExpression)) + throw new Exception($"Unsupported OrderBy expression. The only supported format is \"x => x.Property\". You provided: {property}"); + return this.ThenBy(propertyExpression.Member.Name, descending); + } + + public IDatabaseQuery OrderByDescending(Expression> property) => OrderBy(property, DESC); + + public IDatabaseQuery OrderBy(Expression> property, bool descending = false) + { + if (OrderByParts.Any()) + throw new Exception("There is already an OrderBy part added. Use ThenBy()."); + + return ThenBy(property, descending); + } + + public IDatabaseQuery ThenByDescending(Expression> property) => ThenBy(property, DESC); + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Query/DatabaseQuery.Polymorphism.cs b/Olive.Entities.Data/API/Database/Query/DatabaseQuery.Polymorphism.cs new file mode 100644 index 000000000..994a8405e --- /dev/null +++ b/Olive.Entities.Data/API/Database/Query/DatabaseQuery.Polymorphism.cs @@ -0,0 +1,28 @@ +namespace Olive.Entities.Data +{ + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + partial class DatabaseQuery + { + List ResolveDataProviders() + { + var factories = Database.Instance.AssemblyProviderFactories + .Where(f => f.Value.SupportsPolymorphism()) + .Where(f => f.Key.References(EntityType.GetTypeInfo().Assembly)).ToList(); + + var result = new List(); + + foreach (var f in factories) + result.Add(f.Value.GetProvider(EntityType)); + + foreach (var type in EntityFinder.FindPossibleTypes(EntityType, mustFind: factories.None())) + result.Add(Database.Instance.GetProvider(type)); + + return result; + } + + bool NeedsTypeResolution() => EntityType.IsInterface || EntityType == typeof(Entity); + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Query/DatabaseQuery.TEntity.Aggregate.cs b/Olive.Entities.Data/API/Database/Query/DatabaseQuery.TEntity.Aggregate.cs new file mode 100644 index 000000000..ebce7f946 --- /dev/null +++ b/Olive.Entities.Data/API/Database/Query/DatabaseQuery.TEntity.Aggregate.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace Olive.Entities.Data +{ + partial class DatabaseQuery + { + /// + /// Gets a list of entities of the given type from the database with the specified type matching the specified criteria. + /// If no criteria is specified, the count of all instances will be returned. + /// + public Task Aggregate(AggregateFunction function, string propertyName) + { + return Provider.Aggregate(this, function, propertyName); + } + } + + partial class DatabaseQuery + { + /// + /// Gets a list of entities of the given type from the database with the specified type matching the specified criteria. + /// If no criteria is specified, the count of all instances will be returned. + /// + public async Task Aggregate(AggregateFunction function, + Expression> property) where TOutput : struct + { + var result = await Aggregate(function, property.GetPropertyPath()); + return result.ToStringOrEmpty().TryParseAs(); + } + + public async Task Max(Expression> property) where TProperty : struct => + await Aggregate(AggregateFunction.Max, property); + + public async Task Min(Expression> property) where TProperty : struct => + await Aggregate(AggregateFunction.Min, property); + + public async Task Sum(Expression> property) where TProperty : struct => + await Aggregate(AggregateFunction.Sum, property); + + public async Task Average(Expression> property) + where TProperty : struct => + await Aggregate(AggregateFunction.Average, property); + + public async Task Average(Expression> property) + where TProperty : struct => + await Aggregate(AggregateFunction.Average, property); + + public async Task Average(Expression> property) + where TProperty : struct => + await Aggregate(AggregateFunction.Average, property); + + public async Task Average(Expression> property) => + await Aggregate(AggregateFunction.Average, property); + + public async Task Average(Expression> property) => + await Aggregate(AggregateFunction.Average, property); + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Query/DatabaseQuery.TEntity.cs b/Olive.Entities.Data/API/Database/Query/DatabaseQuery.TEntity.cs new file mode 100644 index 000000000..e1b8b181c --- /dev/null +++ b/Olive.Entities.Data/API/Database/Query/DatabaseQuery.TEntity.cs @@ -0,0 +1,41 @@ +namespace Olive.Entities.Data +{ + using System; + using System.Collections.Generic; + using System.Linq.Expressions; + using System.Reflection; + + public partial class DatabaseQuery : DatabaseQuery, IDatabaseQuery + where TEntity : IEntity + { + public DatabaseQuery() : base(typeof(TEntity)) { } + + IDatabaseQuery IDatabaseQuery.Where(Expression> criteria) + { + if (criteria == null) return this; + Criteria.AddRange(CriteriaExtractor.Parse(criteria)); + return this; + } + + IDatabaseQuery IDatabaseQuery.Include(Expression> property) + { + ((IDatabaseQuery)this).Include(property.GetPropertyPath()); + return this; + } + + IDatabaseQuery IDatabaseQuery.Top(int rows) + { + TakeTop = rows; + return this; + } + + IDatabaseQuery IDatabaseQuery.OrderBy(string property) + => this.OrderBy(property, descending: false); + + IDatabaseQuery IDatabaseQuery.Where(params ICriterion[] criteria) + { + Criteria.AddRange(criteria); + return this; + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Query/DatabaseQuery.cs b/Olive.Entities.Data/API/Database/Query/DatabaseQuery.cs new file mode 100644 index 000000000..66e7a8ccb --- /dev/null +++ b/Olive.Entities.Data/API/Database/Query/DatabaseQuery.cs @@ -0,0 +1,87 @@ +namespace Olive.Entities.Data +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + + partial class Database + { + public IDatabaseQuery Of(Type type) => new DatabaseQuery(type); + + public IDatabaseQuery Of() where TEntity : IEntity => new DatabaseQuery(); + } + + public partial class DatabaseQuery : IDatabaseQuery + { + Dictionary include = new Dictionary(); + + public IDataProvider Provider { get; } + public Type EntityType { get; private set; } + public List Criteria { get; } = new List(); + public IEnumerable Include => + include.Values.ToArray(); + public Dictionary Parameters { get; } = new Dictionary(); + + public int PageStartIndex { get; set; } + public int? PageSize { get; set; } + public int? TakeTop { get; set; } + + internal DatabaseQuery(Type entityType) + { + if (entityType == null) throw new ArgumentNullException(nameof(entityType)); + + if (!entityType.IsA()) + throw new ArgumentException(entityType.Name + " is not an IEntity."); + + EntityType = entityType; + Provider = Database.Instance.GetProvider(entityType); + } + + public string Column(string propertyName) => Provider.MapColumn(propertyName); + + IDatabaseQuery IDatabaseQuery.Where(params ICriterion[] criteria) + { + Criteria.AddRange(criteria); + return this; + } + + IDatabaseQuery IDatabaseQuery.Include(string associations) + { + var immediateAssociation = associations.Split('.').First(); + var nestedAssociations = associations.Split('.').ExceptFirst().ToString("."); + + var property = EntityType.GetProperty(immediateAssociation) + ?? throw new Exception(EntityType.Name + " does not have a property named " + immediateAssociation); + + if (!property.PropertyType.IsA()) + throw new Exception(EntityType.Name + "." + immediateAssociation + " is not an Entity type."); + + if (!include.ContainsKey(immediateAssociation)) + include.Add(immediateAssociation, AssociationInclusion.Create(property)); + + if (nestedAssociations.HasValue()) + include[immediateAssociation].IncludeNestedAssociation(nestedAssociations); + + // TODO: Support one-to-many too + return this; + } + + IDatabaseQuery IDatabaseQuery.Include(IEnumerable associations) + { + foreach (var item in associations) + ((IDatabaseQuery)this).Include(item); + + return this; + } + + IDatabaseQuery IDatabaseQuery.Top(int rows) + { + TakeTop = rows; + return this; + } + + IDatabaseQuery IDatabaseQuery.OrderBy(string property) => this.OrderBy(property, descending: false); + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/Database/Query/DatabaseQueryExtensions.cs b/Olive.Entities.Data/API/Database/Query/DatabaseQueryExtensions.cs new file mode 100644 index 000000000..73b27a716 --- /dev/null +++ b/Olive.Entities.Data/API/Database/Query/DatabaseQueryExtensions.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Olive.Entities.Data; +using System.Linq; + +namespace Olive.Entities +{ + public static partial class DatabaseQueryExtensions + { + public static T Page(this T query, int startIndex, int pageSize) + where T : IDatabaseQuery + { + if (pageSize < 1) + throw new ArgumentException("Invalid PagingQueryOption specified. PageSize should be a positive number."); + + query.PageSize = pageSize; + query.PageStartIndex = startIndex; + return query; + } + + public static T OrderBy(this T query, string property, bool descending = false) + where T : DatabaseQuery + { + return (T)((IDatabaseQuery)query).OrderBy(property, descending); + } + + public static T OrderByDescending(this T query, string property) + where T : DatabaseQuery + { + return query.OrderBy(property, descending: true); + } + + public static T ThenBy(this T query, string property, bool descending = false) + where T : DatabaseQuery + { + return (T)((IDatabaseQuery)query).ThenBy(property, descending); + } + + public static T ThenByDescending(this T query, string property) + where T : DatabaseQuery + { + return query.ThenBy(property, descending: true); + } + + public static T Where(this T query, string sqlCriteria) + where T : DatabaseQuery + { + query.Criteria.Add(new DirectDatabaseCriterion(sqlCriteria)); + return query; + } + + public static T Where(this T query, IEnumerable criteria) + where T : IDatabaseQuery + { + query.Where(criteria.ToArray()); + return query; + } + + public static T Where(this T query, IEnumerable criteria) + where T : IDatabaseQuery + { + query.Where(criteria.Cast().ToArray()); + return query; + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/API/DatabaseContext.cs b/Olive.Entities.Data/API/DatabaseContext.cs new file mode 100644 index 000000000..c48f27614 --- /dev/null +++ b/Olive.Entities.Data/API/DatabaseContext.cs @@ -0,0 +1,36 @@ +using System; + +namespace Olive.Entities.Data +{ + public class DatabaseContext : IDisposable + { + #region ConnectionString + /// + /// Gets or sets the ConnectionString of this DatabaseContext. + /// + public string ConnectionString { get; set; } + + public int? CommandTimeout { get; set; } + + #endregion + + DatabaseContext Parent; + + public DatabaseContext(string connectionString) + { + ConnectionString = connectionString; + + if (Current != null) Parent = Current; + + Current = this; + } + + public static DatabaseContext Current + { + get => CallContext.GetData(nameof(Current)); + set => CallContext.SetData(nameof(Current), value); + } + + public void Dispose() => Current = Parent; + } +} diff --git a/Olive.Entities.Data/Ado.Net/DataAccess.cs b/Olive.Entities.Data/Ado.Net/DataAccess.cs new file mode 100644 index 000000000..7f7b54a1f --- /dev/null +++ b/Olive.Entities.Data/Ado.Net/DataAccess.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Threading.Tasks; + +namespace Olive.Entities.Data +{ + public abstract class DataAccess + { + public static string GetCurrentConnectionString() + { + string result; + + if (DatabaseContext.Current != null) result = DatabaseContext.Current.ConnectionString; + else result = Config.GetConnectionString("AppDatabase"); + + if (result.IsEmpty()) + throw new Exception("No 'AppDatabase' connection string is specified in the application config file."); + + return result; + } + } + + /// + /// ADO.NET Facade for submitting single method commands. + /// + public class DataAccess : DataAccess, IDataAccess + where TConnection : DbConnection, new() + { + /// + /// Creates a connection object. + /// + public async Task GetOrCreateConnection() + { + var result = await (DbTransactionScope.Root?.GetDbConnection() ?? Task.FromResult(null)); + if (result != null) return (TConnection)result; + else return await CreateConnection(); + } + + /// + /// Creates a new DB Connection to database with the given connection string. + /// + public async Task CreateConnection(string connectionString = null) + { + var result = new TConnection + { + ConnectionString = connectionString.Or(GetCurrentConnectionString()) + }; + + await result.OpenAsync(); + return result; + } + + void CloseConnection(IDbConnection connection) + { + if (DbTransactionScope.Root == null) + { + if (connection.State != ConnectionState.Closed) + connection.Close(); + } + } + + async Task CreateCommand(CommandType type, string commandText, params IDataParameter[] @params) => + await CreateCommand(type, commandText, default(TConnection), @params); + + async Task CreateCommand(CommandType type, string commandText, IDbConnection connection, params IDataParameter[] @params) + { + if (connection == null) connection = await GetOrCreateConnection(); + + var command = (DbCommand)connection.CreateCommand(); + command.CommandText = commandText; + command.CommandType = type; + + command.Transaction = await (DbTransactionScope.Root?.GetDbTransaction() + ?? Task.FromResult(command.Transaction)); + + command.CommandTimeout = DatabaseContext.Current?.CommandTimeout ?? (Config.TryGet("Sql.Command.TimeOut")) ?? command.CommandTimeout; + + foreach (var param in @params) + command.Parameters.Add(param); + + return command; + } + + DataAccessProfiler.Watch StartWatch(string command) + { + if (DataAccessProfiler.IsEnabled) return DataAccessProfiler.Start(command); + else return null; + } + + /// + /// Executes the specified command text as nonquery. + /// + public async Task ExecuteNonQuery(string command, CommandType commandType = CommandType.Text, params IDataParameter[] @params) + { + var dbCommand = await CreateCommand(commandType, command, @params); + + var watch = StartWatch(command); + + try + { + var result = await dbCommand.ExecuteNonQueryAsync(); + DatabaseStateChangeCommand.Raise(command, commandType, @params); + return result; + } + catch (Exception ex) + { + throw new Exception("Error in running Non-Query SQL command.", ex).AddData("Command", command) + .AddData("Parameters", @params.Get(l => l.Select(p => p.ParameterName + "=" + p.Value).ToString(" | "))) + .AddData("ConnectionString", dbCommand.Connection.ConnectionString); + } + finally + { + dbCommand.Parameters.Clear(); + + CloseConnection(dbCommand.Connection); + + if (watch != null) DataAccessProfiler.Complete(watch); + } + } + + /// + /// Executes the specified command text against the database connection of the context and builds an IDataReader. + /// Make sure you close the data reader after finishing the work. + /// + public async Task ExecuteReader(string command, CommandType commandType = CommandType.Text, params IDataParameter[] @params) + { + var watch = StartWatch(command); + + var dbCommand = await CreateCommand(commandType, command, @params); + + try + { + if (DbTransactionScope.Root != null) return await dbCommand.ExecuteReaderAsync(); + else return await dbCommand.ExecuteReaderAsync(CommandBehavior.CloseConnection); + } + catch (Exception ex) + { + throw new Exception("Error in running SQL Query.", ex).AddData("Command", command) + .AddData("Parameters", @params.Get(l => l.Select(p => p.ParameterName + "=" + p.Value).ToString(" | "))) + .AddData("ConnectionString", dbCommand.Connection.ConnectionString); + } + finally + { + dbCommand.Parameters.Clear(); + if (watch != null) DataAccessProfiler.Complete(watch); + } + } + + /// + /// Executes the specified command text against the database connection of the context and returns the single value of the type specified. + /// + public async Task ExecuteScalar(string commandText, CommandType commandType = CommandType.Text, params IDataParameter[] @params) => + (T)await ExecuteScalar(commandText, commandType, @params); + + /// + /// Executes the specified command text against the database connection of the context and returns the single value. + /// + public async Task ExecuteScalar(string command, CommandType commandType = CommandType.Text, params IDataParameter[] @params) + { + var watch = StartWatch(command); + var dbCommand = await CreateCommand(commandType, command, @params); + + try + { + var result = await dbCommand.ExecuteScalarAsync(); + + if (!command.ToLowerOrEmpty().StartsWith("select ")) + DatabaseStateChangeCommand.Raise(command, commandType, @params); + + return result; + } + catch (Exception ex) + { + throw new Exception("Error in running Scalar SQL Command.", ex).AddData("Command", command) + .AddData("Parameters", @params.Get(l => l.Select(p => p.ParameterName + "=" + p.Value).ToString(" | "))) + .AddData("ConnectionString", dbCommand.Connection.ConnectionString); + } + finally + { + dbCommand.Parameters.Clear(); + CloseConnection(dbCommand.Connection); + if (watch != null) DataAccessProfiler.Complete(watch); + } + } + + /// + /// Executes a database query and returns the result as a data set. + /// + public async Task ExecuteQuery(string databaseQuery, + CommandType commandType = CommandType.Text, + params IDataParameter[] @params) + { + using (var reader = await ExecuteReader(databaseQuery, commandType, @params)) + { + var table = new DataTable(); + table.Load(reader); + return table; + } + } + + /// + /// Executes the specified command text as nonquery. + /// + public async Task ExecuteBulkNonQueries(CommandType commandType, List> commands) + { + var connection = await GetOrCreateConnection(); + var result = 0; + + try + { + foreach (var c in commands) + { + var watch = StartWatch(c.Key); + + DbCommand dbCommand = null; + try + { + dbCommand = await CreateCommand(commandType, c.Key, connection, c.Value); + result += await dbCommand.ExecuteNonQueryAsync(); + + DatabaseStateChangeCommand.Raise(c.Key, commandType, c.Value); + } + catch (Exception ex) + { + throw new Exception("Error in executing SQL command.", ex).AddData("Command", c.Key) + .AddData("Parameters", c.Value.Get(l => l.Select(p => p.ParameterName + "=" + p.Value).ToString(" | "))); + } + finally + { + dbCommand?.Parameters.Clear(); + + if (watch != null) DataAccessProfiler.Complete(watch); + } + } + + return result; + } + catch (Exception ex) + { + throw new Exception("Error in running Non-Query SQL commands.", ex).AddData("ConnectionString", connection.ConnectionString); + } + finally + { + CloseConnection(connection); + } + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/Ado.Net/DataAccessProfiler.cs b/Olive.Entities.Data/Ado.Net/DataAccessProfiler.cs new file mode 100644 index 000000000..bafa66dc1 --- /dev/null +++ b/Olive.Entities.Data/Ado.Net/DataAccessProfiler.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace Olive.Entities.Data +{ + /// + /// Provides SQL profiling services. + /// + public class DataAccessProfiler + { + internal static bool IsEnabled = Config.Get("Database.Profile.Enabled", defaultValue: false); + + static ConcurrentBag Watches = new ConcurrentBag(); + + static object SyncLock = new object(); + + public static void Reset() => Watches = new ConcurrentBag(); + + internal static Watch Start(string command) => new Watch(command); + + internal static void Complete(Watch watch) + { + watch.Duration = DateTime.Now.Subtract(watch.Start); + + Watches.Add(watch); + } + + internal class Watch + { + internal string Command; + internal DateTime Start; + internal TimeSpan Duration; + + public Watch(string command) + { + Command = command.ToLines().ToString(" "); + Start = DateTime.Now; + } + } + + public class ReportRow + { + public string Command { get; internal set; } + public int Calls { get; internal set; } + public double Total { get; internal set; } + public double Average { get; internal set; } + public double Median { get; internal set; } + public double Longest { get; internal set; } + } + + /// + /// To invoice this you can send a request to the application using http://...?Web.Test.Command=Sql.Profile&Mode=Snapshot + /// + /// Determines whether the current log data should be removed (false) or kept for future combined future generated (true). + public static ReportRow[] GenerateReport(bool snapshot = false) + { + var items = Watches.ToArray().GroupBy(x => x.Command); + + if (!snapshot) Reset(); + + return items.Select(item => new ReportRow + { + Command = item.Key, + Calls = item.Count(), + Total = item.Sum(x => x.Duration).TotalMilliseconds.Round(1), + Average = item.Select(x => (x.Duration.TotalMilliseconds)).Average().Round(1), + Median = item.Select(x => (int)(x.Duration.TotalMilliseconds * 100)).Median() * 0.01, + Longest = item.Max(x => x.Duration).TotalMilliseconds.Round(1) + }).ToArray(); + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/Ado.Net/DataProvider.cs b/Olive.Entities.Data/Ado.Net/DataProvider.cs new file mode 100644 index 000000000..50a04a23e --- /dev/null +++ b/Olive.Entities.Data/Ado.Net/DataProvider.cs @@ -0,0 +1,282 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Olive.Entities.Data +{ + //#region Standard Providers + + ///// + ///// Provides a DataProvider for accessing data from the database using ADO.NET based on the OleDb provider. + ///// + //public abstract class OleDbDataProvider : DataProvider { } + + ///// + ///// Provides a DataProvider for accessing data from the database using ADO.NET based on the ODBC provider. + ///// + //public abstract class OdbcDataProvider : DataProvider { } + + //#endregion + + /// + /// Provides a DataProvider for accessing data from the database using ADO.NET. + /// + public abstract class DataProvider : IDataProvider + where TConnection : DbConnection, new() + where TDataParameter : IDbDataParameter, new() + { + public IDataAccess Access { get; } = new DataAccess(); + + protected DataProvider() => connectionStringKey = GetDefaultConnectionStringKey(); + + public abstract string MapColumn(string propertyName); + + public virtual string MapSubquery(string path) + { + throw new NotSupportedException($"{GetType().Name} does not provide a sub-query mapping for '{path}'."); + } + + static string[] ExtractIdsSeparator = new[] { "", "" }; + + string connectionStringKey, connectionString; + + static string GetDefaultConnectionStringKey() => "AppDatabase"; + + public virtual async Task BulkInsert(IEntity[] entities, int batchSize) + { + foreach (var item in entities) + await Entity.Database.Save(item, SaveBehaviour.BypassAll); + } + + public async Task BulkUpdate(IEntity[] entities, int batchSize) + { + foreach (var item in entities) + await Entity.Database.Save(item, SaveBehaviour.BypassAll); + } + + public abstract Task Count(IDatabaseQuery query); + + public static List ExtractIds(string idsXml) => + idsXml.Split(ExtractIdsSeparator, StringSplitOptions.RemoveEmptyEntries).ToList(); + + public bool SupportValidationBypassing() => true; + + /// + /// Executes the specified command text as nonquery. + /// + public async Task ExecuteNonQuery(string command) => await ExecuteNonQuery(command, CommandType.Text); + + /// + /// Executes the specified command text as nonquery. + /// + public async Task ExecuteNonQuery(string command, CommandType commandType, params IDataParameter[] @params) + { + using (new DatabaseContext(ConnectionString)) + // System.Diagnostics.Trace.WriteLine(command); + return await Access.ExecuteNonQuery(command, commandType, @params); + } + + /// + /// Executes the specified command text against the database connection of the context and builds an IDataReader. Make sure you close the data reader after finishing the work. + /// + public async Task ExecuteReader(string command, CommandType commandType, params IDataParameter[] @params) + { + using (new DatabaseContext(ConnectionString)) + // System.Diagnostics.Trace.WriteLine(command); + return await Access.ExecuteReader(command, commandType, @params); + } + + /// + /// Executes the specified command text against the database connection of the context and returns the single value. + /// + public async Task ExecuteScalar(string command) => await ExecuteScalar(command, CommandType.Text); + + /// + /// Executes the specified command text against the database connection of the context and returns the single value. + /// + public async Task ExecuteScalar(string command, CommandType commandType, params IDataParameter[] @params) + { + using (new DatabaseContext(ConnectionString)) + // System.Diagnostics.Trace.WriteLine(command); + return await Access.ExecuteScalar(command, commandType, @params); + } + + public IDictionary> GetUpdatedValues(IEntity original, IEntity updated) + { + if (original == null) throw new ArgumentNullException(nameof(original)); + + var result = new Dictionary>(); + + var type = original.GetType(); + var propertyNames = type.GetProperties().Distinct().Select(p => p.Name.Trim()).ToArray(); + + Func getProperty = name => type.GetProperties().Except(p => p.IsSpecialName || p.GetGetMethod().IsStatic).Where(p => p.GetSetMethod() != null && p.GetGetMethod().IsPublic).OrderByDescending(x => x.DeclaringType == type).FirstOrDefault(p => p.Name == name); + + var dataProperties = propertyNames.Select(getProperty).ExceptNull() + .Except(CalculatedAttribute.IsCalculated) + .Where(LogEventsAttribute.ShouldLog) + .ToArray(); + + foreach (var p in dataProperties) + { + var propertyType = p.PropertyType; + // Get the original value: + string originalValue, updatedValue = null; + if (propertyType == typeof(IList)) + { + try + { + originalValue = (p.GetValue(original) as IList).ToString(","); + if (updated != null) + updatedValue = (p.GetValue(updated) as IList).ToString(","); + } + catch + { + continue; + } + } + else if (propertyType.IsGenericType && !propertyType.IsNullable()) + { + try + { + originalValue = (p.GetValue(original) as IEnumerable).ToString(", "); + if (updated != null) + updatedValue = (p.GetValue(updated) as IEnumerable).ToString(", "); + } + catch + { + continue; + } + } + else + { + try + { + originalValue = $"{p.GetValue(original)}"; + if (updated != null) + updatedValue = $"{p.GetValue(updated)}"; + } + catch + { + continue; + } + } + + if (updated == null || originalValue != updatedValue) + if (result.LacksKey(p.Name)) + result.Add(p.Name, new Tuple(originalValue, updatedValue)); + } + + return result; + } + + /// + /// Creates a data parameter with the specified name and value. + /// + public IDataParameter CreateParameter(string parameterName, object value) + { + if (value == null) value = DBNull.Value; + + return new TDataParameter { ParameterName = parameterName.Remove(" "), Value = value }; + } + + /// + /// Creates a data parameter with the specified name and value and type. + /// + public IDataParameter CreateParameter(string parameterName, object value, DbType columnType) + { + if (value == null) value = DBNull.Value; + + return new TDataParameter { ParameterName = parameterName.Remove(" "), Value = value, DbType = columnType }; + } + + /// + /// Deletes the specified record. + /// + public abstract Task Delete(IEntity record); + + /// + /// Gets the specified record by its type and ID. + /// + public abstract Task Get(object objectID); + + /// + /// Gets the list of specified records. + /// + public abstract Task> GetList(IDatabaseQuery query); + + /// + /// Returns a direct database criterion used to eager load associated objects. + /// + public abstract DirectDatabaseCriterion GetAssociationInclusionCriteria(IDatabaseQuery query, + PropertyInfo association); + + /// + /// Reads the many to many relation. + /// + public abstract Task> ReadManyToManyRelation(IEntity instance, string property); + + /// + /// Saves the specified record. + /// + public abstract Task Save(IEntity record); + + /// + /// Generates data provider specific parameters for the specified data items. + /// + public IDataParameter[] GenerateParameters(Dictionary parametersData) => + parametersData.Select(GenerateParameter).ToArray(); + + /// + /// Generates a data provider specific parameter for the specified data. + /// + public virtual IDataParameter GenerateParameter(KeyValuePair data) => + new TDataParameter { Value = data.Value, ParameterName = data.Key.Remove(" ") }; + + public abstract Task Aggregate(IDatabaseQuery query, AggregateFunction function, string propertyName); + + #region Connection String + + /// + /// Gets or sets the connection string key used for this data provider. + /// + public string ConnectionStringKey + { + get => connectionStringKey; + set + { + if (value.HasValue()) LoadConnectionString(value); + + connectionStringKey = value; + } + } + + void LoadConnectionString(string key) => connectionString = Config.GetConnectionString(key); + + /// + /// Gets or sets the connection string key used for this data provider. + /// + public string ConnectionString + { + get + { + if (connectionString.HasValue()) return connectionString; + + if (connectionStringKey.HasValue()) + LoadConnectionString(connectionStringKey); + + return connectionString; + } + set + { + connectionString = value; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/Ado.Net/DatabaseStateChangeCommand.cs b/Olive.Entities.Data/Ado.Net/DatabaseStateChangeCommand.cs new file mode 100644 index 000000000..19aada629 --- /dev/null +++ b/Olive.Entities.Data/Ado.Net/DatabaseStateChangeCommand.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Text; + +namespace Olive.Entities.Data +{ + public class DatabaseStateChangeCommand + { + public static event Action ExecutedChangeCommand; + + public string CommandText { get; private set; } + public CommandType CommandType { get; private set; } + public IDataParameter[] Params { get; private set; } + + internal static void Raise(string command, CommandType type, IDataParameter[] @params) + { + if (ExecutedChangeCommand == null) return; + + var item = new DatabaseStateChangeCommand + { + CommandText = command, + CommandType = type, + Params = @params + }; + + ExecutedChangeCommand?.Invoke(item); + } + } +} diff --git a/Olive.Entities.Data/Ado.Net/DbTransactionScope.cs b/Olive.Entities.Data/Ado.Net/DbTransactionScope.cs new file mode 100644 index 000000000..116f574f7 --- /dev/null +++ b/Olive.Entities.Data/Ado.Net/DbTransactionScope.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Threading.Tasks; + +namespace Olive.Entities.Data +{ + // TODO: If it's a Suppress, then simply in the GetDbTransaction return null. + // And test to see if the command will pass in case where other commands in a transaction in the same connection exist, + // and are rolled back. + + public class DbTransactionScope : ITransactionScope + { + IsolationLevel IsolationLevel; + DbTransactionScopeOption ScopeOption; + + public static DbTransactionScope Root + { + get => CallContext.GetData(nameof(Root)); + set => CallContext.SetData(nameof(Root), value); + } + + public static DbTransactionScope Current + { + get => CallContext.GetData(nameof(Current)); + set => CallContext.SetData(nameof(Current), value); + } + + public static DbTransactionScope Parent + { + get => CallContext.GetData(nameof(Parent)); + set => CallContext.SetData(nameof(Parent), value); + } + + // Per unique connection string, one record is added to this. + Dictionary> Connections = new Dictionary>(); + + bool IsCompleted, IsAborted; + + public DbTransactionScope() : this(GetDefaultIsolationLevel()) { } + + public DbTransactionScope(DbTransactionScopeOption scopeOption) : this(GetDefaultIsolationLevel(), scopeOption) { } + + public DbTransactionScope(IsolationLevel isolationLevel, DbTransactionScopeOption scopeOption = DbTransactionScopeOption.Required) + { + IsolationLevel = isolationLevel; + ScopeOption = scopeOption; + + Parent = Root; + Current = this; + + if (Root == null) Root = this; + } + + public Guid ID { get; } = Guid.NewGuid(); + + #region TransactionCompletedEvent + + event EventHandler TransactionCompleted; + + /// + /// Attaches an event handler to be invoked when the current (root) transaction is completed. + /// + public void OnTransactionCompleted(Action eventHandler) => Root.TransactionCompleted += (s, e) => eventHandler?.Invoke(); + + #endregion + + static IsolationLevel GetDefaultIsolationLevel() => + Config.Get("Default.Transaction.IsolationLevel", IsolationLevel.ReadUncommitted); + + internal async Task GetDbTransaction() + { + var connectionString = DataAccess.GetCurrentConnectionString(); + + await Setup(connectionString); + + return Connections[connectionString].Item2; + } + + internal async Task GetDbConnection() + { + var connectionString = DataAccess.GetCurrentConnectionString(); + + await Setup(connectionString); + + return Connections[connectionString].Item1; + } + + async Task Setup(string connectionString) + { + if (Connections.LacksKey(connectionString)) + { + var access = Database.Instance.GetAccess(connectionString); + + var connection = (DbConnection)await access.CreateConnection(); + var transaction = connection.BeginTransaction(IsolationLevel); + + Connections.Add(connectionString, Tuple.Create(connection, transaction)); + } + } + + public void Dispose() + { + if (IsAborted) return; + + if (this == Root) // Root + { + Root = null; + + if (IsCompleted) + { + // Happy scenario: + Connections.Do(x => x.Value.Item1.Close()); + } + else // Root is not completed. + { + IsAborted = true; + + Connections.Do(x => x.Value.Item2.Rollback()); + Connections.Do(x => x.Value.Item2.Dispose()); + Connections.Do(x => x.Value.Item1.Close()); + } + } + else + { + Current = Parent; + + if (IsCompleted) + { + // A Sub-transaction has been happily completed. + // Just wait for the parent. + } + else + { + // A sub transaction is not completed. + Root?.Dispose(); + } + } + } + + public void Complete() + { + if (IsAborted) + throw new Exception("This transaction is already aborted, probably due to a nested transaction not being completed."); + + IsCompleted = true; + + if (Root == this) + { + // I'm the root: + foreach (var item in Connections) + item.Value.Item2.Commit(); + + TransactionCompleted?.Invoke(this, EventArgs.Empty); + } + else + { + // Ignore, and wait for the parent Completion. + } + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/Ado.Net/ITransactionScope.cs b/Olive.Entities.Data/Ado.Net/ITransactionScope.cs new file mode 100644 index 000000000..152d45eeb --- /dev/null +++ b/Olive.Entities.Data/Ado.Net/ITransactionScope.cs @@ -0,0 +1,27 @@ +using System; +using System.ComponentModel; +using System.Transactions; + +namespace Olive.Entities.Data +{ + //public interface ITransactionScope : IDisposable + //{ + // void Complete(); + + // Guid ID { get; } + //} + + [EditorBrowsable(EditorBrowsableState.Never)] + public class TransactionScopeWrapper : ITransactionScope + { + TransactionScope Scope; + + public Guid ID { get; } = Guid.NewGuid(); + + public TransactionScopeWrapper(TransactionScope scope) { Scope = scope; } + + public void Complete() => Scope.Complete(); + + public void Dispose() => Scope.Dispose(); + } +} diff --git a/Olive.Entities.Data/Ado.Net/InterfaceDataProvider.cs b/Olive.Entities.Data/Ado.Net/InterfaceDataProvider.cs new file mode 100644 index 000000000..fd84e781c --- /dev/null +++ b/Olive.Entities.Data/Ado.Net/InterfaceDataProvider.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Olive.Entities.Data +{ + /// + /// Provides data access for Interface types. + /// + public class InterfaceDataProvider : IDataProvider + where TImplementationDataProvider : IDataProvider + { + Type InterfaceType; + static ConcurrentDictionary> ImplementationsCache = new ConcurrentDictionary>(); + + public InterfaceDataProvider(Type interfaceType) => InterfaceType = interfaceType; + + List GetImplementers() => ImplementationsCache.GetOrAdd(InterfaceType, FindImplementers); + + static List FindImplementers(Type interfaceType) + { + var result = new List(); + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies().Where(a => a.References(interfaceType.Assembly))) + { + try + { + foreach (var type in assembly.GetTypes()) + { + if (type == interfaceType) continue; + if (type.IsInterface) continue; + + if (type.Implements(interfaceType)) + result.Add(type); + } + } + catch + { + // Can't load assembly + } + } + + // For any type, if it's parent is in the list, exclude it: + + var typesWithParentsIn = result.Where(x => result.Contains(x.BaseType)).ToArray(); + + foreach (var item in typesWithParentsIn) + result.Remove(item); + + return result; + } + + List FindProviders() + { + var implementers = GetImplementers(); + return implementers.Select(x => Database.Instance.GetProvider(x)).ToList(); + } + + public async Task Count(IDatabaseQuery query) + { + var providers = FindProviders(); + var results = await providers.Select(x => x.Count(query)).AwaitAll(); + return results.Sum(); + } + + public async Task> GetList(IDatabaseQuery query) + { + if (query.TakeTop.HasValue) + throw new Exception("Top() criteria is not allowed when querying based on Interfaces."); + + if (((DatabaseQuery)query).OrderByParts.Any()) + throw new Exception("OrderBy() is not allowed when querying based on Interfaces."); + + var providers = FindProviders(); + var results = await providers.Select(x => x.GetList(query)).AwaitAll(); + return results.SelectMany(x => x); + } + + public DirectDatabaseCriterion GetAssociationInclusionCriteria(IDatabaseQuery query, + PropertyInfo association) + { + throw new InvalidOperationException("Oops! GetAssociationInclusionCriteria() is not meant to be ever called on " + GetType().Name); + } + + public Task Aggregate(IDatabaseQuery query, AggregateFunction function, string propertyName) => + throw new NotSupportedException("Database.Aggregate doesn't work on interfaces."); + + public async Task Get(object objectID) + { + foreach (var actual in GetImplementers()) + { + try + { + if (await Entity.Database.Get(objectID, actual) is Entity result) return result; + } + catch { continue; } + } + + throw new Exception($"There is no {InterfaceType.Name} record with the ID of '{objectID}'"); + } + + public Task> ReadManyToManyRelation(IEntity instance, string property) => + throw new NotSupportedException("IDataProvider.ReadManyToManyRelation() is not supported for Interfaces"); + + public Task Save(IEntity record) => + throw new NotSupportedException("IDataProvider.Save() is irrelevant to Interfaces"); + + public Task Delete(IEntity record) => + throw new NotSupportedException("IDataProvider.Delete() is irrelevant to Interfaces"); + + public string MapColumn(string propertyName) => + throw new NotSupportedException("IDataProvider.Delete() is irrelevant to Interfaces"); + + public IDictionary> GetUpdatedValues(IEntity original, IEntity updated) => + throw new NotSupportedException("GetUpdatedValues() is irrelevant to Interfaces"); + + public Task ExecuteNonQuery(string command) => + throw new NotSupportedException("ExecuteNonQuery() is irrelevant to Interfaces"); + + public Task ExecuteScalar(string command) => + throw new NotSupportedException("ExecuteScalar() is irrelevant to Interfaces"); + + public bool SupportValidationBypassing() => + throw new NotSupportedException("SupportValidationBypassing() is irrelevant to Interfaces"); + + public Task BulkInsert(IEntity[] entities, int batchSize) => + throw new NotSupportedException("BulkInsert() is irrelevant to Interfaces"); + + public Task BulkUpdate(IEntity[] entities, int batchSize) => + throw new NotSupportedException("BulkInsert() is irrelevant to Interfaces"); + + public IDataAccess Access => + throw new NotSupportedException("Access is irrelevant to Interfaces"); + + public string MapSubquery(string path) => + throw new NotSupportedException("MapSubquery() is irrelevant to Interfaces"); + + public string ConnectionString { get; set; } + + public string ConnectionStringKey { get; set; } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/Ado.Net/SubqueryMapping.cs b/Olive.Entities.Data/Ado.Net/SubqueryMapping.cs new file mode 100644 index 000000000..5d577a52b --- /dev/null +++ b/Olive.Entities.Data/Ado.Net/SubqueryMapping.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Olive.Entities.Data +{ + public class SubqueryMapping + { + public string Path, Subquery; + public Dictionary Details; + + public SubqueryMapping(string path, string prefix, Dictionary destinationPropertyMappings) + { + Path = path; + Details = destinationPropertyMappings.ToDictionary(x => x.Key, x => + { + if (x.Value.StartsWith("[")) + return x.Value.Insert(1, prefix); + else return prefix + x.Value; + }); + } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/Engine/Cache.cs b/Olive.Entities.Data/Engine/Cache.cs new file mode 100644 index 000000000..546d96f63 --- /dev/null +++ b/Olive.Entities.Data/Engine/Cache.cs @@ -0,0 +1,302 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Olive.Entities.Data +{ + /// + /// Provides a cache of objects retrieved from the database. + /// + public class Cache + { + object SyncLock = new object(); + public static Cache Instance = new Cache(); + Dictionary> Types = new Dictionary>(); + Dictionary> Lists = new Dictionary>(); + + // Note: This feature can prevent a rare concurrency issue in highly concurrent applications. + // But it comes at the cost of performance degradation. If your application doesn't have extremely concurrent processing + // with multiple threads reading and updating records at the same time, you can disable it in web.config to improve performance. + internal static bool IsConcurrencyAware = Config.Get("Database:Concurrency.Aware.Cache", defaultValue: true); + + internal static DateTime? GetQueryTimestamp() => IsConcurrencyAware ? DateTime.UtcNow : default(DateTime?); + + #region Row Version + + // Note: This is to solve the following concurrency issue: + // In highly concurrent systems the following scenario can happen. + // A GET call loads a record from DB. + // It then adds it to the cache. + // If that record is updated in between the two steps above, then bad data is added to the cache. + + internal ConcurrentDictionary> RowVersionCache + = new ConcurrentDictionary>(); + + public virtual bool IsUpdatedSince(IEntity instance, DateTime since) + { + var type = instance.GetType(); + if (!CanCache(type)) return false; + + var cache = RowVersionCache.GetOrDefault(type); + + return cache?.GetOrDefault(instance.GetId().ToString()) > since.Ticks; + } + + public virtual void UpdateRowVersion(IEntity entity) + { + var type = entity.GetType(); + if (!CanCache(type)) return; + + var cache = RowVersionCache.GetOrAdd(type, t => new ConcurrentDictionary()); + cache[entity.GetId().ToString()] = DateTime.UtcNow.Ticks; + } + + #endregion + + #region IsEnabled property + + static bool IsCachingEnabled = Config.Get("Database:Cache.Enabled", defaultValue: true); + + public static bool CanCache(Type type) => CacheObjectsAttribute.IsEnabled(type) ?? IsCachingEnabled; + + #endregion + + /// + /// Gets the current cache. + /// + public static Cache Current => Instance; + + Dictionary GetEntities(Type type) + { + var result = Types.TryGet(type); + + if (result == null) + { + lock (SyncLock) + { + result = Types.TryGet(type); + + if (result == null) + { + result = new Dictionary(); + Types.Add(type, result); + } + } + } + + return result; + } + + Dictionary GetLists(Type type, bool autoCreate = true) + { + var result = Lists.TryGet(type); + + if (result == null && autoCreate) + { + lock (SyncLock) + { + result = Lists.TryGet(type); + if (result == null) + { + result = new Dictionary(); + Lists.Add(type, result); + } + } + } + + return result; + } + + /// + /// Gets an entity from cache. Returns null if not found. + /// + public virtual IEntity Get(string id) + { + try + { + foreach (var type in Types.Keys.ToArray().Where(t => t.IsA())) + { + var result = Get(type, id); + if (result != null) return result; + } + } + catch { } + + return null; + } + + /// + /// Gets an entity from cache. Returns null if not found. + /// + public virtual TEntity Get(object id) where TEntity : IEntity => (TEntity)Get(typeof(TEntity), id.ToStringOrEmpty()); + + /// + /// Gets an entity from cache. Returns null if not found. + /// + public virtual IEntity Get(Type entityType, string id) + { + if (!CanCache(entityType)) return null; + + var entities = GetEntities(entityType); + + if (entities.ContainsKey(id)) + { + try + { + return entities[id]; + } + catch (KeyNotFoundException) + { + // A threading issue. + return Get(entityType, id); + } + } + else + { + foreach (var type in entityType.Assembly.GetSubTypes(entityType)) + { + var result = Get(type, id); + if (result != null) return result; + } + + return null; + } + } + + /// + /// Adds a given entity to the cache. + /// + public virtual void Add(IEntity entity) + { + if (!CanCache(entity.GetType())) return; + + var entities = GetEntities(entity.GetType()); + + lock (entities) + { + var id = entity.GetId().ToString(); + if (entities.ContainsKey(id)) + { + entities.GetOrDefault(id).Perform(x => x.InvalidateCachedReferences()); + entities.Remove(id); + } + + entities.Add(id, entity); + + ExpireLists(entity.GetType()); + } + } + + /// + /// Removes a given entity from the cache. + /// + public virtual void Remove(IEntity entity) + { + entity.InvalidateCachedReferences(); + + if (!(entity is IApplicationEvent)) + foreach (var type in CacheDependentAttribute.GetDependentTypes(entity.GetType())) + Remove(type, invalidateCachedReferences: true); + + if (!CanCache(entity.GetType())) return; + + var entities = GetEntities(entity.GetType()); + + lock (entities) + { + var id = entity.GetId().ToString(); + + if (entities.ContainsKey(id)) entities.Remove(id); + + ExpireLists(entity.GetType()); + } + + if (this != Current) Current.Remove(entity); + } + + /// + /// Removes all entities of a given types from the cache. + /// + public virtual void Remove(Type type, bool invalidateCachedReferences = false) + { + if (!CanCache(type)) return; + + lock (SyncLock) + { + foreach (var inherited in Types.Keys.Where(t => t.BaseType == type).ToList()) + Remove(inherited, invalidateCachedReferences); + } + + if (Types.ContainsKey(type)) + { + lock (SyncLock) + { + if (Types.ContainsKey(type)) + { + var entities = Types[type]; + lock (entities) + { + Types.Remove(type); + ExpireLists(type); + + if (invalidateCachedReferences) + entities.Do(e => e.Value.InvalidateCachedReferences()); + } + } + } + } + + if (this != Current) + Current.Remove(type, invalidateCachedReferences); + } + + public virtual void ExpireLists(Type type) + { + if (!CanCache(type)) return; + + for (var parentType = type; parentType != typeof(Entity); parentType = parentType.BaseType) + { + var lists = GetLists(parentType, autoCreate: false); + + if (lists != null) lock (lists) lists.Clear(); + } + + if (this != Current) Current.ExpireLists(type); + } + + public virtual IEnumerable GetList(Type type, string key) + { + if (!CanCache(type)) return null; + + var lists = GetLists(type); + lock (lists) + { + if (lists.ContainsKey(key)) return lists[key]; + else return null; + } + } + + public virtual void AddList(Type type, string key, IEnumerable list) + { + if (!CanCache(type)) return; + + var lists = GetLists(type); + + lock (lists) lists[key] = list; + } + + public virtual void ClearAll() + { + lock (SyncLock) + { + RowVersionCache = new ConcurrentDictionary>(); + Types.Clear(); + Lists.Clear(); + } + } + + internal int CountAllObjects() => Types.Sum(t => t.Value.Count); + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/Engine/DataProviderFactoryInfo.cs b/Olive.Entities.Data/Engine/DataProviderFactoryInfo.cs new file mode 100644 index 000000000..1b308774c --- /dev/null +++ b/Olive.Entities.Data/Engine/DataProviderFactoryInfo.cs @@ -0,0 +1,120 @@ +using System; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; + +namespace Olive.Entities.Data +{ + public class DataProviderFactoryInfo + { + string mappingDirectory; + + public string MappingResource { get; set; } + + public string AssemblyName { get; set; } + public string TypeName { get; set; } + public string ProviderFactoryType { get; set; } + + public string MappingDirectory + { + get => mappingDirectory; + set + { + if (value == null) + mappingDirectory = string.Empty; + + else if (value.StartsWith("\\\\") || value.Contains(":")) + // Absolute path: + mappingDirectory = value; + + else + { + mappingDirectory = AppDomain.CurrentDomain.BaseDirectory + "/" + value + "/"; + mappingDirectory = mappingDirectory.Replace("/", "\\"); + + mappingDirectory = mappingDirectory.KeepReplacing(@"\\", @"\"); + } + } + } + + public string ConnectionStringKey { get; set; } + + public string ConnectionString { get; set; } + + public Assembly Assembly { get; set; } + public Type Type { get; set; } + + public Assembly GetAssembly() + { + if (Assembly == null) + Assembly = AppDomain.CurrentDomain.LoadAssembly(AssemblyName); + + return Assembly; + } + + public Type GetMappedType() + { + if (Type != null) return Type; + + if (TypeName.HasValue()) Type = GetAssembly().GetType(TypeName); + + return Type; + } + + public async Task LoadMappingXml() + { + if (MappingResource.IsEmpty()) + throw new Exception("No MappingResource is specified for this data provider factory."); + + if (MappingResource.Contains("/") || MappingResource.Contains("\\")) + { + // Physical file: + var path = AppDomain.CurrentDomain.BaseDirectory + MappingResource; + path = path.Replace("/", "\\").Replace("\\\\", "\\"); + + if (path.StartsWith("\\")) path = "\\" + path; + + if (!File.Exists(path)) + throw new FileNotFoundException("Could not find the data mapping xml at : " + path); + + return await File.ReadAllTextAsync(path); + } + else + { + // Embedded resource: + foreach (var resourceName in GetAssembly().GetManifestResourceNames()) + { + if (resourceName.ToLower() == MappingResource.ToLower() || resourceName.ToLower().EndsWith("." + MappingResource.ToLower())) + return await LoadMappingText(resourceName); + + // using (var resource = GetAssembly().GetManifestResourceStream(resourceName)) + // { + // using (var reader = new StreamReader(resource)) + // { + // return reader.ReadToEnd(); + // } + // } + } + + throw new Exception($"Could not build a data provider factory for {GetAssembly().FullName} because: " + + $"Could not load the manifest resource {MappingResource} from the assembly{GetAssembly().FullName}."); + } + } + + public async Task LoadMappingText(string resourceName) + { + try + { + using (var resource = GetAssembly().GetManifestResourceStream(resourceName)) + { + using (var reader = new StreamReader(resource)) + return await reader.ReadToEndAsync(); + } + } + catch (Exception ex) + { + throw new Exception("Could not load the manifest resource text for '{0}'".FormatWith(resourceName), ex); + } + } + } +} diff --git a/Olive.Entities.Data/Engine/DataProviderModelConfigurationSection.cs b/Olive.Entities.Data/Engine/DataProviderModelConfigurationSection.cs new file mode 100644 index 000000000..6ce5e8ce0 --- /dev/null +++ b/Olive.Entities.Data/Engine/DataProviderModelConfigurationSection.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace Olive.Entities.Data +{ + public class DataProviderModelConfigurationSection + { + public List Providers { get; set; } + + /// + /// Gets or sets the SyncFilePath of this DataProviderModelConfigurationSection. + /// + public string SyncFilePath { get; set; } + + /// + /// Gets or sets the SyncFilePath of this DataProviderModelConfigurationSection. + /// + public string FileDependancyPath { get; set; } + } +} \ No newline at end of file diff --git a/Olive.Entities.Data/Extensions/@Misc.cs b/Olive.Entities.Data/Extensions/@Misc.cs new file mode 100644 index 000000000..7cd6613fd --- /dev/null +++ b/Olive.Entities.Data/Extensions/@Misc.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlTypes; +using System.IO; +using System.Linq; + +namespace Olive.Entities.Data +{ + public static partial class OliveExtensions + { + /// + /// Returns a MS T-SQL-safe DateTime value for use in queries (i.e. prevents date values earlier than 1/1/1753). + /// + public static DateTime GetSqlSafeValue(this DateTime value) => + value < SqlDateTime.MinValue.Value ? SqlDateTime.MinValue.Value : value; + + /// + /// Gets a virtual URL to this file. If the file is not in the current website folder it throws an exception. + /// + public static string ToVirtualPath(this FileInfo file) + { + if (!file.FullName.StartsWith(AppDomain.CurrentDomain.BaseDirectory, StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException($"The file {file.FullName} is not in the current website folder."); + + var path = "/" + file.FullName.Substring(AppDomain.CurrentDomain.BaseDirectory.Length).TrimStart("\\").TrimStart("/"); + return path.Replace("\\", "/"); + } + + /// + /// Returns a DataTable with columns based on the public properties of type T and the rows + /// populated with the values in those properties for each item in this IEnumerable. + /// + /// Optional name for the DataTable (defaults to the plural of the name of type T). + public static DataTable ToDataTable(this IEnumerable items, string tableName = null) + { + var properties = typeof(T).GetProperties(); + + var dataTable = new DataTable(tableName.Or(typeof(T).Name.ToPlural())); + + foreach (var property in properties) + dataTable.Columns.Add(property.Name); + + foreach (T item in items) + { + var row = dataTable.NewRow(); + + foreach (var property in properties) + row[property.Name] = property.GetValue(item); + + dataTable.Rows.Add(row); + } + + return dataTable; + } + + public static int? GetResultsToFetch(this IEnumerable options) => + options.OfType().FirstOrDefault()?.Number; + } +} diff --git a/Olive.Entities.Data/Extensions/DataTable.cs b/Olive.Entities.Data/Extensions/DataTable.cs new file mode 100644 index 000000000..423effd1b --- /dev/null +++ b/Olive.Entities.Data/Extensions/DataTable.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Olive.Entities.Data +{ + partial class OliveExtensions + { + /// + /// Casts this data table's records into a list of typed objects. + /// + public static IEnumerable CastTo(this DataTable dataTable) where T : new() => + CastTo(dataTable, null); + + /// + /// Casts this data table's records into a list of typed objects. + /// An anonymouse object containing property mapping information. + /// e.g.: new {Property1 = "Property name in CSV", Property2 = "...", set_Property1 = new Func<string, object>(text => Client.Parse(value)) } + /// + public static IEnumerable CastTo(this DataTable dataTable, object propertyMappings) where T : new() => + CastAsDictionary(dataTable, propertyMappings).Select(i => i.Key).ToList(); + + /// + /// Casts this data table's records into a list of typed objects. + /// An anonymouse object containing property mapping information. + /// e.g.: new {Property1 = "Property name in CSV", Property2 = "...", set_Property1 = new Func<string, object>(text => Client.Parse(value)) } + /// + public static Dictionary CastAsDictionary(this DataTable data, object propertyMappings) where T : new() + { + if (propertyMappings != null) + foreach (var p in propertyMappings.GetType().GetProperties()) + { + if (p.PropertyType == typeof(string)) continue; + + if (p.PropertyType == typeof(Func)) + { + if (!p.Name.StartsWith("set_")) + throw new ArgumentException("Property convertors must start with 'set_{property name}'"); + + continue; + } + + throw new ArgumentException($"Unrecognized value for the property {p.PropertyType} of the specified propertyMappings"); + } + + var mappings = FindPropertyMappings(typeof(T), data.Columns, propertyMappings); + + var convertors = new Dictionary>(); + if (propertyMappings != null) + convertors = propertyMappings.GetType().GetProperties().Where(p => p.PropertyType == typeof(Func)) + .ToDictionary(p => p.Name.Substring(4), p => (Func)p.GetValue(propertyMappings)); + + var result = new Dictionary(); + + foreach (DataRow record in data.Rows) + { + var item = ParseObject(record, mappings, convertors); + result.Add(item, record); + } + + return result; + } + + /// + /// Finds the property mappings for the specified target type, CSV column names and user declared mappings. + /// + static Dictionary FindPropertyMappings(Type targetType, DataColumnCollection columns, object declaredMappings) + { + var result = new Dictionary(); + + if (declaredMappings != null) + { + foreach (var property in declaredMappings.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)) + { + if (property.Name.StartsWith("set_")) + { + if (!result.ContainsKey(property.Name.TrimStart("set_"))) + result.Add(property.Name.TrimStart("set_"), null); + continue; + } + + // Validate property name: + var propertyInTarget = targetType.GetProperty(property.Name); + if (propertyInTarget == null) + throw new Exception(targetType.FullName + " does not have a property named " + property.Name); + + if (!propertyInTarget.CanWrite) + throw new Exception("{0}.{1} property is read-only.".FormatWith(targetType.FullName, property.Name)); + + var mappedName = (string)property.GetValue(declaredMappings); + result[property.Name] = mappedName; + } + } + + var columnNames = columns.Cast().Select(c => c.ColumnName).ToArray(); + + foreach (var property in targetType.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)) + { + if (!property.CanWrite) + continue; + + if (result.ContainsKey(property.Name) && result[property.Name] != null) + continue; // Already added in explicit mappings. + + // Otherwise, if a column with that name is available, then that's it: + var potential = columnNames.Where(c => c.Replace(" ", "").ToLower() == property.Name.ToLower()); + if (potential.IsSingle()) + { + result[property.Name] = potential.Single(); + } + else if (potential.Any()) + { + throw new Exception("The specified data contains multiple potential matches for the property '{0}'. The potentially matched columns found: {1}. You must use explicit mappings in this case." + .FormatWith(property.Name, potential.Select(c => $"'{c}'").ToString(", "))); + } + } + + return result; + } + + /// + /// Creates an object of the specified type with the specified data and property mappings. + /// + static T ParseObject(DataRow dataContainer, Dictionary propertyMappings, Dictionary> convertors) + { + var result = Activator.CreateInstance(); + + foreach (var mapping in propertyMappings) + { + var property = result.GetType().GetProperty(mapping.Key); + + string data; + + if (mapping.Value == null) + // The setter for this property is identified, while no property mapping is specified. + data = null; + else + data = dataContainer[mapping.Value]?.ToString().TrimOrNull(); + + try + { + object dataToSet; + + if (convertors.ContainsKey(mapping.Key)) + dataToSet = convertors[mapping.Key](data); + else + dataToSet = data.To(property.PropertyType); + + property.SetValue(result, dataToSet); + } + catch (Exception ex) + { + throw new Exception("Could not set the value of the property '{0}' from the value of '{1}'.".FormatWith(mapping.Key, data), ex); + } + } + + return result; + } + + /// + /// Gets the CSV data equivalent to this data table. + /// + public static string ToCSV(this DataTable table) + { + var result = new StringBuilder(); + for (int i = 0; i < table.Columns.Count; i++) + { + result.Append(table.Columns[i].ColumnName); + result.Append(i == table.Columns.Count - 1 ? "\n" : ","); + } + + foreach (DataRow row in table.Rows) + { + for (int i = 0; i < table.Columns.Count; i++) + { + result.Append(row[i].ToString()); + result.Append(i == table.Columns.Count - 1 ? "\n" : ","); + } + } + + return result.ToString(); + } + + /// + /// Gets the rows of this data table in a LINQ-able format.. + /// + public static IEnumerable GetRows(this DataTable dataTable) => dataTable.Rows.Cast(); + } +} diff --git a/Olive.Entities.Data/Extensions/TransactionExtensions.cs b/Olive.Entities.Data/Extensions/TransactionExtensions.cs new file mode 100644 index 000000000..77c0b2ca0 --- /dev/null +++ b/Olive.Entities.Data/Extensions/TransactionExtensions.cs @@ -0,0 +1,29 @@ +using System.Transactions; + +namespace Olive.Entities.Data +{ + /// + /// Provides extension methods for transaction classes. + /// + public static class TransactionExtensions + { + /// + /// Creates a new transaction scope with this isolation level. + /// + public static TransactionScope CreateScope(this IsolationLevel isolationLevel) => + CreateScope(isolationLevel, TransactionScopeOption.Required); + + /// + /// Creates a new transaction scope with this isolation level. + /// public static TransactionScope CreateScope(this IsolationLevel isolationLevel, TransactionScopeOption scopeOption) + public static TransactionScope CreateScope(this IsolationLevel isolationLevel, TransactionScopeOption scopeOption) + { + var options = new TransactionOptions + { + IsolationLevel = isolationLevel, + Timeout = TransactionManager.DefaultTimeout + }; + return new TransactionScope(scopeOption, options); + } + } +} diff --git a/Olive.Entities.Data/Olive.Entities.Data.csproj b/Olive.Entities.Data/Olive.Entities.Data.csproj new file mode 100644 index 000000000..8143b4e3c --- /dev/null +++ b/Olive.Entities.Data/Olive.Entities.Data.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp2.0 + Olive.Entities.Data + Olive.Entities.Data + + + + ..\@Assemblies\ + ..\@Assemblies\netcoreapp2.0\Olive.Entities.Data.xml + 1701;1702;1705;1591;1573 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Olive.Entities.Data/Package.nuspec b/Olive.Entities.Data/Package.nuspec new file mode 100644 index 000000000..91a8798dc --- /dev/null +++ b/Olive.Entities.Data/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Entities.Data + 1.0.7 + Olive Entities Data + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Olive.Entities/Attributes/AutoNumberAttribute.cs b/Olive.Entities/Attributes/AutoNumberAttribute.cs new file mode 100644 index 000000000..e534ee833 --- /dev/null +++ b/Olive.Entities/Attributes/AutoNumberAttribute.cs @@ -0,0 +1,20 @@ +using System; +using System.Reflection; + +namespace Olive.Entities +{ + /// + /// When applied to a property, indicates that such property is AutoNumber (or Identity in SQL Server). + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class AutoNumberAttribute : Attribute + { + /// + /// Determines if a given property is auto number. + /// + public static bool IsAutoNumber(PropertyInfo property) + { + return property.GetCustomAttribute(inherit: false) != null; + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Attributes/CacheDependantAttribute.cs b/Olive.Entities/Attributes/CacheDependantAttribute.cs new file mode 100644 index 000000000..35c7b93e6 --- /dev/null +++ b/Olive.Entities/Attributes/CacheDependantAttribute.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Olive.Entities +{ + [AttributeUsage(AttributeTargets.Property)] + public sealed class CacheDependentAttribute : Attribute + { + /// + /// Gets the dependent type. + /// + public Type DependentType { get; private set; } + + /// + /// Creates a new CacheDependantAttribute instance. + /// + public CacheDependentAttribute(Type dependentType) => + DependentType = dependentType ?? throw new ArgumentNullException(nameof(dependentType)); + + static ConcurrentDictionary Cache = new ConcurrentDictionary(); + + /// + /// Gets a list of types that depend on a given entity. + /// + public static IEnumerable GetDependentTypes(Type entityType) + { + if (entityType == null) + throw new ArgumentNullException(nameof(entityType)); + + return Cache.GetOrAdd(entityType, FindDependentTypes); + } + + /// + /// Finds a list of types that depend on a given entity. + /// + static Type[] FindDependentTypes(Type entityType) + { + return (from type in entityType.Assembly.GetTypes() + from p in type.GetProperties() + let att = p.GetCustomAttribute() + where att != null && att.DependentType.IsAssignableFrom(entityType) + select type).Distinct().ToArray(); + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Attributes/CacheObjectsAttribute.cs b/Olive.Entities/Attributes/CacheObjectsAttribute.cs new file mode 100644 index 000000000..17873aba5 --- /dev/null +++ b/Olive.Entities/Attributes/CacheObjectsAttribute.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Olive.Entities +{ + /// + /// Specifies if a type is cacheable. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] + public sealed class CacheObjectsAttribute : Attribute + { + static object DetectAndCacheShouldBeStaticMethod = new object(); + + static Dictionary Cache = new Dictionary(); + + static object SyncLock = new object(); + + bool Enabled; + + /// + /// Creates a new CacheObjectsAttribute instance. + /// + public CacheObjectsAttribute(bool enabled) => Enabled = enabled; + + /// + /// Determines if caching is enabled for a given type. + /// + public static bool? IsEnabled(Type type) + { + if (Cache.TryGetValue(type, out bool? result)) return result; + + return DetectAndCache(type); + } + + static bool? DetectAndCache(Type type) + { + lock (DetectAndCacheShouldBeStaticMethod) + { + var usage = type.GetCustomAttributes(inherit: true).FirstOrDefault(); + + var result = default(bool?); + + if (usage != null) result = usage.Enabled; + + try { return Cache[type] = result; } + catch { return result; } + } + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Attributes/CalculatedAttribute.cs b/Olive.Entities/Attributes/CalculatedAttribute.cs new file mode 100644 index 000000000..40be5ce76 --- /dev/null +++ b/Olive.Entities/Attributes/CalculatedAttribute.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Olive.Entities +{ + /// + /// When applied to a property, indicates that such property does not exist in the database. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class CalculatedAttribute : Attribute + { + static object DetectAndCacheShouldBeStaticMethod = new object(); + + static Dictionary Cache = new Dictionary(); + + /// + /// Determines if a given property is calculated. + /// + public static bool IsCalculated(PropertyInfo property) + { + if (Cache.TryGetValue(property, out bool result)) return result; + + return DetectAndCache(property); + } + + static bool DetectAndCache(PropertyInfo property) + { + lock (DetectAndCacheShouldBeStaticMethod) + { + var result = property.IsDefined(typeof(CalculatedAttribute), inherit: true); + + try { return Cache[property] = result; } + catch { return result; } + } + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Attributes/DateOnlyAttribute.cs b/Olive.Entities/Attributes/DateOnlyAttribute.cs new file mode 100644 index 000000000..92cf62fc6 --- /dev/null +++ b/Olive.Entities/Attributes/DateOnlyAttribute.cs @@ -0,0 +1,11 @@ +namespace Olive.Entities +{ + using System; + + /// + /// When applied to a property of type DateTime or Nullable[DateTime] it specifies that values are for Date only, + /// and the time part is meant to be disregarded. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Parameter)] + public class DateOnlyAttribute : Attribute { } +} \ No newline at end of file diff --git a/Olive.Entities/Attributes/IdByDatabaseAttribute.cs b/Olive.Entities/Attributes/IdByDatabaseAttribute.cs new file mode 100644 index 000000000..fefff3f93 --- /dev/null +++ b/Olive.Entities/Attributes/IdByDatabaseAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Olive.Entities +{ + [AttributeUsage(AttributeTargets.Class)] + public class IdByDatabaseAttribute : Attribute + { + } +} diff --git a/Olive.Entities/Attributes/InitializerAttribute.cs b/Olive.Entities/Attributes/InitializerAttribute.cs new file mode 100644 index 000000000..37d42ce00 --- /dev/null +++ b/Olive.Entities/Attributes/InitializerAttribute.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace Olive.Entities +{ + /// + /// + /// + [AttributeUsage(AttributeTargets.Class)] + public class InitializerAttribute : Attribute + { + public string InitializerMethodName { get; } + + public InitializerAttribute(string initializerMethodName = "Initialize") => + InitializerMethodName = initializerMethodName; + + public static void InvokeInitializeMethod(Assembly assembly) where T : InitializerAttribute + { + var initializers = assembly.SelectTypesByAttribute(inherit: false); + + if (initializers.Count() == 1) + { + var initializer = initializers.First(); + var initializerAttribute = initializer.GetCustomAttribute(inherit: false); + + (initializer.GetMethod(initializerAttribute.InitializerMethodName) ?? + throw new Exception($"The initailizer class does not have the {initializerAttribute.InitializerMethodName} method or it is not a static method.") + ).Invoke(null, null); + } + + else if (initializers.Count() == 0) + throw new Exception("The given assembly has no initializer."); + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Attributes/LogEventsAttribute.cs b/Olive.Entities/Attributes/LogEventsAttribute.cs new file mode 100644 index 000000000..66a9cb301 --- /dev/null +++ b/Olive.Entities/Attributes/LogEventsAttribute.cs @@ -0,0 +1,31 @@ +using System; +using System.Reflection; + +namespace Olive.Entities +{ + /// + /// When applied to a class, indicates whether data access events should be logged for instances of that type. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Property)] + public sealed class LogEventsAttribute : Attribute + { + const bool DEFAULT_UNCONFIGURED = true; + + public bool Log { get; private set; } + + /// + /// Creates a new LogEventsAttribute instance. + /// + public LogEventsAttribute(bool shouldLog) => Log = shouldLog; + + public static bool ShouldLog(Type type) + { + var definedAttribute = type.GetCustomAttribute(inherit: true); + + return definedAttribute?.Log ?? DEFAULT_UNCONFIGURED; + } + + public static bool ShouldLog(PropertyInfo property) => + property.GetCustomAttribute(inherit: true)?.Log ?? true; + } +} diff --git a/Olive.Entities/Attributes/ManyToManyAttribute.cs b/Olive.Entities/Attributes/ManyToManyAttribute.cs new file mode 100644 index 000000000..eec2efb29 --- /dev/null +++ b/Olive.Entities/Attributes/ManyToManyAttribute.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Olive.Entities +{ + [AttributeUsage(AttributeTargets.Property)] + public sealed class ManyToManyAttribute : Attribute + { + static ConcurrentDictionary, PropertyInfo[]> Cache = new ConcurrentDictionary, PropertyInfo[]>(); + + /// + /// Gets or sets the Lazy of this ManyToManyAttribute. + /// + public bool Lazy { get; set; } + + /// + /// Gets a list of types that depend on a given entity. + /// + public static IEnumerable GetManyToManyProperties(Type type) => GetManyToManyProperties(type, lazy: null); + + /// + /// Gets a list of types that depend on a given entity. + /// + public static IEnumerable GetManyToManyProperties(Type type, bool? lazy) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + + var key = Tuple.Create(type, lazy); + + return Cache.GetOrAdd(key, x => FindManyToManyProperties(x.Item1, x.Item2)); + } + + /// + /// Returns a list of types that depend on a given entity. + /// + static PropertyInfo[] FindManyToManyProperties(Type type, bool? lazy) + { + return (from p in type.GetProperties() + let att = p.GetCustomAttribute(inherit: true) + where att != null + where lazy == null || att.Lazy == lazy + select p).Distinct().ToArray(); + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Attributes/PersistentAttribute.cs b/Olive.Entities/Attributes/PersistentAttribute.cs new file mode 100644 index 000000000..99870a7e8 --- /dev/null +++ b/Olive.Entities/Attributes/PersistentAttribute.cs @@ -0,0 +1,34 @@ +using System; + +namespace Olive.Entities +{ + /// + /// When applied to a class, defines its Application data accessor type. + /// + public sealed class PersistentAttribute : Attribute + { + bool IsPersistent; + public PersistentAttribute(bool isPersistent) + { + IsPersistent = isPersistent; + } + + internal static bool IsTypePersistent(Type type) + { + if (type.GetInterface(typeof(IEntity).FullName) == null) + return false; + + if (type.IsDefined(typeof(PersistentAttribute), inherit: true)) + { + foreach (PersistentAttribute attribute in type.GetCustomAttributes(typeof(PersistentAttribute), inherit: true)) + { + if (attribute.IsPersistent == false) + return false; + } + } + + // Default unconfigured value is true: + return true; + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Attributes/SecureFileAttribute.cs b/Olive.Entities/Attributes/SecureFileAttribute.cs new file mode 100644 index 000000000..aa2a80b30 --- /dev/null +++ b/Olive.Entities/Attributes/SecureFileAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Olive.Entities +{ + [AttributeUsage(AttributeTargets.Property)] + public class SecureFileAttribute : Attribute + { + } +} diff --git a/Olive.Entities/Attributes/SoftDeleteAttribute.cs b/Olive.Entities/Attributes/SoftDeleteAttribute.cs new file mode 100644 index 000000000..1759ed4bf --- /dev/null +++ b/Olive.Entities/Attributes/SoftDeleteAttribute.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; + +namespace Olive.Entities +{ + [AttributeUsage(AttributeTargets.Class)] + public sealed class SoftDeleteAttribute : Attribute + { + static object DetectAndCacheShouldBeStaticMethod = new object(); + + static Dictionary Cache = new Dictionary(); + + /// + /// Determines if soft delete is enabled for a given type. + /// + public static bool IsEnabled(Type type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + if (Cache.TryGetValue(type, out bool result)) return result; + + return DetectAndCache(type); + } + + static bool DetectAndCache(Type type) + { + lock (DetectAndCacheShouldBeStaticMethod) + { + var result = type.IsDefined(typeof(SoftDeleteAttribute), inherit: true); + + try { return Cache[type] = result; } + catch { return result; } + } + } + + public static bool RequiresSoftdeleteQuery() => RequiresSoftdeleteQuery(typeof(T)); + + public static bool RequiresSoftdeleteQuery(Type type) + { + if (!IsEnabled(type)) return false; + + return !Context.ShouldByPassSoftDelete(); + } + + /// + /// Provides support for bypassing softdelete rule. + /// + public class Context : IDisposable + { + bool BypassSoftdelete; + + Context ParentContext; + + public static Context Current + { + get => CallContext.GetData(nameof(Current)); + set => CallContext.SetData(nameof(Current), value); + } + + /// + /// Creates a new Context instance. + /// + public Context(bool bypassSoftdelete) + { + BypassSoftdelete = bypassSoftdelete; + + // Get from current thread: + + if (Current != null) + ParentContext = Current; + Current = this; + } + + public void Dispose() => Current = ParentContext; + + /// + /// Determines if SoftDelete check should the bypassed in the current context. + /// + public static bool ShouldByPassSoftDelete() + { + if (Current == null) return false; + else return Current.BypassSoftdelete; + } + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Attributes/TransientEntityAttribute.cs b/Olive.Entities/Attributes/TransientEntityAttribute.cs new file mode 100644 index 000000000..08bef4966 --- /dev/null +++ b/Olive.Entities/Attributes/TransientEntityAttribute.cs @@ -0,0 +1,16 @@ +using System; +using System.Reflection; + +namespace Olive.Entities +{ + [AttributeUsage(AttributeTargets.Class)] + public sealed class TransientEntityAttribute : Attribute + { + public static bool IsTransient(Type type) + { + if (type == null) throw new ArgumentNullException(nameof(type)); + + return type.GetCustomAttribute(inherit: true) != null; + } + } +} diff --git a/Olive.Entities/Attributes/UserInfoAccessorInitializerAttribute.cs b/Olive.Entities/Attributes/UserInfoAccessorInitializerAttribute.cs new file mode 100644 index 000000000..3aeb5fdf5 --- /dev/null +++ b/Olive.Entities/Attributes/UserInfoAccessorInitializerAttribute.cs @@ -0,0 +1,7 @@ +namespace Olive.Entities +{ + public class UserInfoAccessorInitializerAttribute : InitializerAttribute + { + public UserInfoAccessorInitializerAttribute(string initializerMethodName = "Initialize") : base(initializerMethodName) { } + } +} diff --git a/Olive.Entities/Auditing/ApplicationEventManager.cs b/Olive.Entities/Auditing/ApplicationEventManager.cs new file mode 100644 index 0000000000000000000000000000000000000000..16eec9abf6c6f1095b614dd78724ef171c4ee7ed GIT binary patch literal 7694 zcmeHMU2oG!6ur+Y@gJ-{B@*)ffNhc3Qn8V?fM|Ect8q**v`Lh>uu;{&&h9yLdzl%3 zCBbc2BosNe$K#oE@0|NJ{`bqZtfZ8gTu35kvc_E{guBWLS%Umw;DqC0Img{f zKH|`~Da#CPSLnBLf_|JHyo!=L^@yv8bN} z#~f`J7?Z~R|E z`vv-pJ7lhpIb^j|6C)YImofat8WPbPs21(MDd!k{jvcdTuZ2vo0?G^eE|t1pg9Y?M zk3c_D*F^n(xMt{+KVzOD`r%5p%XI6cXp^MRuaGBe=q(2>^Vo^J2GZR5#VI0E<~+*? z`t|;Kjs9xU^ww*6jC<>OiZ|(R=|}WkYZ;^MsOGEddd{Gu40cqZ?+~4*9a7X$H!-Sy z+cddd^f8I7xAK^tyF#3#UUHo2qv@g9{zgvaPn>-$qV8w~jKTCOg^N0K#D@>AwF39K zVjPE>3ZL-Z_h_NNW~_RPHxi67bN?F0UgA#IOCweo=e+-?753|)Kcbl?c4Z)=dj-o@ zY)^n&b$A@_We=cU=o#sKxxe843%-zh& zKE9lQt>ix0s5!3j(kz2{0&^s08VNi>i7RF#%HbQAo6enOEYV{vFEAoAO8TE09Ly%C zLF8lm3q1cEv?X$j*4sUBdjen2Ow+PS%~fM|6X7`|rp^nO*LvpjS!@Yiw$QW!X0^;l z$bRMl9II|AoOS9v2-kWWyBOw?+iYP3el?NSk#*a_uD&IXZj8m$JK5OOVQ8PG9o7I3 z5usuv@yy>Q{dU}o`DLE=nPWu;X3i0rSleu;oi^*YC~zO#X~T6Nx(c;Gdz>Tp@G;s) zn(cJDn?1+bfbs0%G}$Fx9Y;6oEVJpa!foBHcbHX%*-#<>!1{9dK=YG{%j$g`n7c|R zbKa0B>8%(eEJEo{12dvFt=asW>hC(@8D!hI! z_w6Lrne)@~n!aKiR(sFkZPZ&jJ;C?4yv-Q7v&TKFW#q3;;B~o6vVfiI-m!WGt0!~k zk#*;eRU|#GRb<+e9>ti<`myfCe{9b|+icU3*XEDtTPRg1!F|Vg=8F6Z5XN_3>#@4C zeI4tmm2O5FbC<6g~NBD;{4z0)DY8OcZepy!&<2Ml}x`4kkS6j znXT>6z0A#s_!Xc&_rtFn^+1MC@zcEV=Ro!YyL?$37T)P9zB@1Qx#wmtRO=03IenVEMkns0q+ z&z*LEZ$9^}!SO7Ev4z)OkI~B>{!63BqWh-*{nQ{k?#>O`ljgkpHpV{;o7P+aa6FKw(BHsaE#aj*CkcpTf4Go#T-4?n-3+Z$&#+V!!$ecnTSV82VgX7&^q06kAXw%-ozdeq-z zEVwhXpEJ96WHTeziz3W-48t03=LY4ID+p4&wr#e6#r-0rJJ)c2)kBGl10R$YjZ=#2 zE?VFiAJ~)oM)PxfbJpt{GKjpOp{;rKrpus4*EOBxbHkOf?{vEbX}@Q)qt%z)cUK%6 z(+G{O>)0&Z$3DQF;po8NP{-6Nk9iYb%)5^herSd=U-apZ#vPZ?i=G-UdMSC`J+%Ai z+^oBM&8=sKBl73en|E8?cF)_#_6}P9wENU3eriu4;mGcB_uqDRY42cPk@LrP&+X~L zc%II==svekvBA>#t?qX=`&Odu-`nR;Y{Wx*^29#-xI*0)mX4jv-nK!AT>w7d#bzR% zK%IW}r9Fv_%i*8f-4r8HQ7{Ug{92~keVf@O>xn^wmMnVu{%G?*x4%kneP#DA&3>I3 ze0Q!ToF}nG_f0F@HeJ|X%i;)2dn%vzUN^*T45zof6qaN?wLz&iffJT>R)onlHHZGf zpla(nma3-fNLUyp*X8tWba@>uYuOu@!*s5xR!Opy&H<#QQG+$od%D*6hUY{6h>f21 zTzX=6h?t17kT!haq4{n+)s96ANZGkPtw^eFNBq4-M}%`=V=Qf~PmANYMZg0GpY_P} z?!+L#{>*I7W&gY6vn+{IG1@atBThnt+`nTD@Mw>(=5lJ@m^seFkYoh~^&2euX(b&Hkz0sSQ*=6_w|L`2xA+90HQ7kjFx1lFg#8dNB zh?S73``sg>$;`$gULX>ZEb%T7T-ZHm1_X>2<~!`44qhpZm~x!H=wZN{00q8_k@$m6!st`5!KUCLfX%P&6Y+r_(+NhN#6BIEZ7)fxDBp_jgjLPP!WEzaijy8E zJlN}=13~`o80}y7cwZVHoW|kP`^JkSdy6>dYkM=+Sh#R>{f=uqzW$Xxdy|=nY{ny~ zK%rXvEBff^cO}VKDkKZL`Li^O`tL`YiLS+qRU|ro52-HcspM;_yvJjfV5!S|P0K?h zMecuTnpes_!<@>j=23P=r2xD8vB~V+Uf12rt)8#B70`H_*5bV$N-7!nBhR_<%ftr6 zDtIEd-PrTk=Xi$VTDfx-_n&nC(MJwM64(doM(FGl^Kc&fP!TF|e|5PY#vfD`mNx6l zD-W^Tk#}-9vXB-%$7NYqD7QX086qz{Hc9d*gPhvOPczF}*6Hy*ev~-W z^K_SaI7`gv)(u*DmF9JywYdhDq*ie1TCWu7DP4)P(XBW3lk)4aLF3Sp34lFvO{6AU zL*@p|-}ezBZ_n)s*_uoHWY;jHVg{zFkWtTRafM73F8<2?&AN|lRK~inAM$u2Nw*Ey zHmZ|neZ3Ls;k`fFJDvv#%huKFc?X6^i4yV#)HIpn&NT%b>{L9f23V)_5!Z&1KLl zGStbO?nvan+8nj9L<{12>=$vJFdXK5)Q2A0Q$)_H`eA`XBz@3pINS#ck4R7K4PsP} z>(Rwr+Ogw9*_fiSi;2~uX*44O{=)A9rOp2^J)^#Y^$+zh*S$UCyDF#1OQ?ZU*LypWU+eoOq`rg+a(dKTAGZoMtw?QO z?3Y@$YJBX~YB71_fYx|cN5!4Lo*SOZ<>nKtXq>ZSImyjKEcndLCO93WGObMpC=axMyR|wI8)uOYK{fDQ-JJkFS ztul~TZOZ;(|EfYHihGQXv|$O-S~Yz0$`9RDWDhRk->3BeJYFeLPn@b#QBS4%KBVcm zR%?WMgb_y+^Fj?2-J~;7mITc)4HvRddJA-QzVGFb7>Q^Fe}hGSV0<{We@^iuy9?!0 z2@M*9_xQ%Duvqfn+K(W0Jr`_a*Oao7$@;rxT8`V#puIP6ufsjX73;`~r17V&ciSJd-mT*2kk)mi zu!mi;y;W+xLwo_HtRj(j%ZYmPo+$BefK>Gn)kB=B zMA$LE+?v%Q7FC#rv9;G-*ULs~GfKzGGp4cRs@M$2#M%>)ytuBLI9@GU4l~ZWXQ<8m z?>0YmE^sT&i$8*YrHpn=Q=~Dad3Ub!B%kx*Dua^zZfYLSjKHdH6+G)$oqn-haZKv- zI2GG6FA2=)!W>|eTY!bFf;l%HQ9GvUSCj5H)lbDWX&qHRqT?eQm+XYu4?)JpjJ89?69c85#Q9t&` z^;ou;h>X>>%AUQ`;_EyoR41JEFRIE13j%l!Jb{a9VEHWVYy!z6%Tq{p;y5QBbP7iJi1wuqcp}@C)VFwg(dczcvh`r z@ZONG(UKQpZvgoMwu-JOE3e|teZ7{uW256W@OvI_@>|@i!dFo-@|FIt>{I#~zT%C} zpYzYFo_JtYB=DtlNZGG&XlvTEQjz-)ZOm8EJ$jG))L~t3?Mz;G-nCjiyHm8P4vl$j zk{0Lbz_@QXdmncAS^Vt1XyyBZBrc-zlv*5QllTG(Ll5T3pJ7(TY=2K0Uk_QPBiE0A_Nf|b*p)J&Km*qQd4P!5xO1fmv2t)Ced zj-%qP^?Zaf`CP6%Dvsm3*n^e&+%CImUSEPzQm(2I65PT}@1)hjdR{Bc(p=c_nug0z zkJp1!(66f3t=XjMwR|PDU##|uWlfTn(n#aVa_Bm4LF(1*I7(8jEzj3;SqE;_Tgu+C z9{Y&<9zkiRip!|x_O);9*O{A@j_0dOR!URl;6GSg6tC8ccL!?63f70Uf$2)2=T`4T zF0lvWqw&v)NO@!`HE^Yh-fu>X&0zT1{fOG3#e z&XOGz^y9TceHb^#8nKR4D9XI;E=ElwrA@IExxCfhk~{~0wrjHD>qLk$*Xwf#mi#I@ zm1oOyht)6{j`?$sgB`xHUSBz!qL#R&2@a$Z7`Ml2H*_?I-u!_#t zqj+B>Jna-5(ijfyL=m!{3o$d5r8?)NUvb~sogM=*W7-o4c5Zr9!uc5<75HA)J&PcH<=RpAnbjw#2OZjc z)Izqb#wu(0AgYtzvriOn)+`>YlG7?aBeE0!&?4w*V|Yb^y}(e0-CgoRoGQUu2jX8k z25|*m(f5VHS%XkIlS3Lu*iMHxw&UcjLH)+|QM@%QKC$2Yj%N%G4VEp1Q? zz%-ANQ|2bJ5FiWO|F4 z6Gz%*+gjIM=`3;)dX%w8S_cDcXWMdWZuLNhai?;PI0LC$4UL(~L+GgzD}$Zp)C?Y# zsRui@H+F7yAl`>Z=cYUA)KWe2nvmCjkTht3cHzzGrmMn4%+8r4zS62qrSkbgc(xSg z*XGZ>t^ssM#?Q;a6OKEbQ|~@GCR1rq^W2=I7f$ZU>9lLI753ZX9RlUnAJwyU|NpBV zU%DJxQR#Df-vZq)YlrM(9ezOz{g9q5qxE(lG1Z}u_b8{5<4{x0WXCoC2iVpBRMxB= z$w*uq=Wi@()M z!_wxvwBFOQpF5n&l*>;}e@XBvRz|)h)f#yUxH@h#^1Aa;&#`o**S&_vTA;2!x?htM z$>42WbJ=EH(}{7B6)E1;<2<_IN^~@+^z8e_Ce|vQ8n)`1`uB|kK7CbBFdpM$TRhgO zNqHUTdYmBH9P_WS6>#5}Ylog_dpyk*M@2*JeAG)Z=dDa1vAx$!b5Em&o>nC)w;;kf zw~fBmXZQO2h&yK2$@R&@sVl5wE%LboecbC~Q{_LxXKB}&iiWQa8H25qFOYGE>Vas| zy3(y;wI-75=fva~hBtcjpyk#)>>;9Qv&x3s!)$>rqB3`t zXG%Zo61d9Q2QiF4>x*mnKa0M!b(&a>s_Zh+y0$3dX36yd~8W^ zIeL-z)EMdq_`B>)xYNr?D*gBD?sl&;Xp}UJ)B0HJeYg8Bd+&wul(+bu9%?KT9@zug ziB%?juM&Qi=ZP;5?RW3E=Cd#}a+|}mDvf-lrehgop1)%b&$>Suzv0eHgG{^Mp4ne~ zv%{=E&wa~Ik7HM$$611-ydr=6&pu2C*=ZFsLlg?0=)9EZJiSOecBIHas(qfWm~#JfN9InRNpZeT%( z?1HS1M?Se$tP=srHmBz(eA(Ofq4cm;DUBgQbkU9`(7#aG^zR+o?{U@%%laNHuW0R? zO+74lPo3OC59ucG2=}2D^9{6;t=LZWZiUsBxC`P4<5rPSvgzL3u|# zKOI)saDMO?CR==~2q%GipNp#1w)Go_+y*+Gc|zX{t@qkAwT9g0BBw;mr{>$pT0B0O zc7n;$#tr9_%R;p!NT39E&9kmtvu?vX8$-Fw*8-I7 z-X<{fl?9&}PQ>~{NZ>fW?_S0$M(J2O-Y&}}j@D?y3Fz?{X`G#ZO70Mz#_DSJ zA+B{QPm9r8(zVs+GvqXz`xk6%E)n_PZRi4h;29Yer?SQrbcsprF_Cv{i;xv4I<`m7SKrmCEAA@R3*)C29C}gd*l)NKdsa5m{Fcbl``Je z>YUB2{SPz=-R00nr_UZRqvOgq)uf&6@Hx=wiD@0I_UvR|;7kSl!k`Hq_4Jdx;~${f z6eL7q9_z<(4bx#?J_Dv`6Y`f4+s zSNQ`@VTo~JMR-m}J?qlTx9gqsS`LgrK89Zzeoi`LrJTM|VW>&eC9obu5uuC9x+r!B z@7aC;I(s{2lfsHAA}2l%u;wS!`6pA?QM9E}Gwm9WQVJ4b)xU2=@4gR;`l-it_;RX$ zVYIz8Jcsn2n*_j3aue53*(TzioAKcGs`+lu^J;F;&0yt_#S6n-RVnOoY>oIl-^~=P zgh#>hRl~6)2Wa8+XW>JdQI$OFo2tK6)LO#1ygIQohy1Os!RMpj`rg~=6<4j@@|DFU z*r*?S+){%_*yZ$XQ%#uEsi&trzV&E0pKEH4G4=cQ;^=$*xKfZ$!%t6d8!y-SmYm#< zh(_2wzTaF`6pvHISTEFr#IH4h|miZem79loC<&GbBiACHbVlew)IA+gWHv$WTiOL>(JtR%(Lz8^81 zM(v)-*JuT+Qtm#ysYWHT%$FwPtmbmb_AL2!z9)EYQ7f}Rhw!bvp&QV`5m4GL zbg$yWtMwaHT{w%jwqtaP{42VNsg(Mcn)b+!lw!4E<(!_$1G6!#Kp{p|)`r$0Ga*w8 z(8#N=Lp#^w8vMe(J$jHuI1Gihl5au*&i}E0a9$^?mt{HcGWb}#Mh*9Tmo{=)-bHO) zQ`;3=!dNSVe^=E<9t3%9!77P#i|#f&-kXOUdXBB{xW8?x6F=%y#}=jP7D9BCrGTDT za6gqEzT7vjh;`!$R{L=}NF3cc*Qh6PN)9LFNOH+oedmStF~&S`8fpHqYYdP3P`6>5 z;aZzdf@{hy+x)~*zB_@{)yVV|Ia3G2?H&B3HjHoaZ#SQ&5!3iByu^1u@5yP)PQOP6 zxv%M%#;1I|e7~2EEXk1eePh%K=d*D31#_z#w}|>(x+i> Operations = new List>(); + public DateTime Date { get; set; } + + /// + /// Creates a new UndoContext instance. + /// + public UndoContext() + { + Date = LocalTime.Now; + } + + public static async Task Deserialize(XElement change) + { + var result = new UndoContext + { + Date = new DateTime(change.GetValue("@Date")) + }; + + foreach (var op in change.Elements()) + { + var eventId = op.GetValue("@ID"); + var ev = await Entity.Database.Get(eventId); + result.Operations.Add(new KeyValuePair(ev, op.GetValue("@Description"))); + } + + return result; + } + + public string Serialize() + { + if (Operations.None()) return string.Empty; + + var xml = new XElement("Change", new XAttribute("Date", Date.Ticks)); + + foreach (var item in Operations) + xml.Add(new XElement("Op", new XAttribute("ID", item.Key.GetId()), new XAttribute("Description", item.Value))); + + return xml.ToString(); + } + + internal void Append(IApplicationEvent eventInfo, IEntity record) + { + string description; + + if (eventInfo.Event == "Delete") + description = "Deleted {0} '{1}'".FormatWith(record.GetType().Name, record.ToString()); + else if (eventInfo.Event == "Insert") + description = "Inserted {0} '{1}'".FormatWith(record.GetType().Name, record.ToString()); + else if (eventInfo.Event == "Update") + { + description = "Update {0} '{1}':".FormatWith(record.GetType().Name, record.ToString()); + + if (eventInfo.Data.HasValue()) + { + foreach (var child in GetOldDataNode(eventInfo).Elements()) + { + var property = child.Name.LocalName; + + if (property == "IsCodeDirty") continue; + + var oldValue = child.Value; + var newValue = EntityManager.ReadProperty(record, property).ToStringOrEmpty(); + + description += property + " changed from '{0}' to '{1}' | ".FormatWith(oldValue, newValue); + } + } + } + else throw new NotSupportedException(); + + Operations.Insert(0, new KeyValuePair(eventInfo, description.TrimEnd(" | "))); + } + + public async Task Undo() + { + await Entity.Database.EnlistOrCreateTransaction(async () => { foreach (var op in Operations) await Undo(op.Key); }); + } + + async Task Undo(IApplicationEvent operation) + { + Entity item; + + switch (operation.Event) + { + case "Insert": + await Entity.Database.Delete(await operation.LoadItem(), DeleteBehaviour.BypassAll); + break; + case "Delete": + item = await operation.LoadItem() as Entity; + await Entity.Database.Save(item, SaveBehaviour.BypassSaved | SaveBehaviour.BypassSaving); + break; + case "Update": + item = (await operation.LoadItem()).Clone() as Entity; + + foreach (var p in GetOldDataNode(operation).Elements()) + { + var old = p.Value; + var property = item.GetType().GetProperty(p.Name.LocalName); + property.SetValue(item, old.To(property.PropertyType)); + } + + await Entity.Database.Save(item, SaveBehaviour.BypassSaved | SaveBehaviour.BypassSaving); + break; + default: + // Ignore other cases + break; + } + } + + static XElement GetOldDataNode(IApplicationEvent operation) + { + var node = XElement.Parse(operation.Data); + return node.Element("old") ?? node; + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Blob/Blob.cs b/Olive.Entities/Blob/Blob.cs new file mode 100644 index 000000000..207f4f76d --- /dev/null +++ b/Olive.Entities/Blob/Blob.cs @@ -0,0 +1,575 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace Olive.Entities +{ + /// + /// Provides an utility for handling Binary property types. + /// + [JsonConverter(typeof(PessimisticJsonConverter))] + public class Blob : IComparable, IComparable + { + /// + /// In Test projects particularly, having files save themselves on the disk can waste space. + /// To prevent that, apply this setting in the config file. + /// + static bool ShouldSuppressPersistence = Config.Get("Blob.Suppress.Persistence", defaultValue: false); + static bool StoreWithFileName = Config.Get("Blob.Stored.With.File.Name", defaultValue: false); + + const string EMPTY_FILE = "NoFile.Empty"; + public const string DefaultEncryptionKey = "Default_ENC_Key:_This_Better_Be_Calculated_If_Possible"; + public static string SecureVirtualRoot = "/Download.File.aspx?"; + + static string[] UnsafeExtensions = new[] { "aspx", "ascx", "ashx", "axd", "master", "bat", "bas", "asp", "app", "bin","cla","class", "cmd", "com","sitemap","skin", "asa", "cshtml", + "cpl","crt","csc","dll","drv","exe","hta","htm","html", "ini", "ins","js","jse","lnk","mdb","mde","mht","mhtm","mhtml","msc", "msi","msp", "mdb", "ldb","resources", "resx", + "mst","obj", "config","ocx","pgm","pif","scr","sct","shb","shs", "smm", "sys","url","vb","vbe","vbs","vxd","wsc","wsf","wsh" , "php", "asmx", "cs", "jsl", "asax","mdf", + "cdx","idc", "shtm", "shtml", "stm", "browser"}; + + public static ConcurrentDictionary PhysicalFilesRoots = new ConcurrentDictionary(); + + Entity ownerEntity; + public AccessMode FileAccessMode; + bool IsEmptyBlob; + byte[] FileData; + + /// + /// Initializes a new instance of the class. + /// + public Blob() { } + + /// + /// Initializes a new Blob instance with the specified data and file name. + /// + public Blob(byte[] data, string fileName) + { + FileData = data; + this.fileName = fileName.ToSafeFileName(); + } + + /// + /// Initializes a new Blob instance, for the specified file on disk. + /// + [Obsolete("By using this constructor you will async benefit, use the other ones.")] + public Blob(FileInfo file) : this(File.ReadAllBytes(file.FullName), file.Name) { } + + /// + /// Gets the address of the property owning this blob in the format: Type/ID/Property + /// + public string GetOwnerPropertyReference() + { + if (ownerEntity == null || OwnerProperty.IsEmpty()) return null; + return $"{ownerEntity?.GetType().FullName}/{ownerEntity?.GetId()}/{OwnerProperty}"; + } + + public enum AccessMode { Open, Secure } + + public string OwnerProperty { get; private set; } + + string fileName, folderName; + + bool hasValue; // For performance, cache it + + [JsonExposed] + public string FileName + { + get { return fileName.Or(EMPTY_FILE); } + set { fileName = value; } + } + + public string FileExtension + { + get + { + if (fileName.IsEmpty()) return string.Empty; + else + { + var result = Path.GetExtension(fileName) ?? string.Empty; + if (result.Length > 0 && !result.StartsWith(".")) + result = "." + result; + return result; + } + } + } + + /// + /// Gets the data of this blob. + /// + public async Task GetFileData() + { + if (IsEmpty()) return new byte[0]; + + if (FileData != null && FileData.Length > 0) + return FileData; + + FileData = await GetStorageProvider().Load(this); + + return FileData; + } + + public void SetData(byte[] data) + { + if ((data?.Length ?? 0) == 0) + throw new InvalidOperationException("Invalid value passed."); + + FileData = data; + } + + public string FolderName + { + get + { + if (folderName == null) + { + if (ownerEntity == null) return OwnerProperty; + folderName = ownerEntity.GetType().Name + "." + OwnerProperty; + } + + return folderName; + } + set + { + folderName = value; + } + } + + IBlobStorageProvider GetStorageProvider() => BlobStorageProviderFactory.GetProvider(FolderName); + + /// + /// Gets an empty blob object. + /// + public static Blob Empty() => new Blob(null, EMPTY_FILE) { IsEmptyBlob = true }; + + /// + /// Gets all fall-back paths for this Blob + /// + public IEnumerable FallbackPaths => + (ownerEntity as IPickyBlobContainer)?.GetFallbackPaths(this) ?? Enumerable.Empty(); + + /// + /// Gets the Url of this blob. + /// + public override string ToString() => Url(); + + /// + /// Gets the content + /// + /// + public async Task GetContentText() + { + if (IsEmpty()) return string.Empty; + + try + { + using (var mem = new MemoryStream(await GetFileData())) + { + using (var reader = new StreamReader(mem)) + return await reader.ReadToEndAsync(); + } + } + catch (Exception ex) + { + throw new Exception($"The {OwnerProperty} of the {ownerEntity?.GetType().FullName} entity ({ownerEntity?.GetId()}) cannot be converted to text.", ex); + } + } + + /// + /// Gets a Url to this blob. + /// + public string Url(AccessMode mode) + { + if (ownerEntity == null) return null; + + if (ownerEntity is IPickyBlobUrlContainer) + return (ownerEntity as IPickyBlobUrlContainer).GetUrl(this); + + return GetVirtualFolderUrl(mode) + GetFileNameWithoutExtension() + FileExtension + + ("?" + FileName).OnlyWhen(mode == AccessMode.Open); + } + + /// + /// Gets a Url to this blob. + /// + public string Url() => Url(FileAccessMode); + + /// + /// Returns the Url of this blob, or the provided default Url if this is Empty. + /// + public string UrlOr(string defaultUrl) + { + if (IsEmpty()) return defaultUrl; + else return Url(); + } + + /// + /// Gets a cache safe URL to this blob. + /// + public string GetCacheSafeUrl() + { + var result = Url(); + + if (result.IsEmpty()) return result; + + return result + (result.Contains("?") ? "&" : "?") + "RANDOM=" + Guid.NewGuid(); + } + + public string GetVirtualFolderUrl(AccessMode accessMode) + { + if (ownerEntity is IPickyBlobContainer) + { + var result = (ownerEntity as IPickyBlobContainer).GetVirtualFolderPath(this); + if (result.HasValue()) return result.TrimEnd('/') + "/"; + } + + var root = Config.Get("UploadFolder.VirtualRoot").Or("/Documents/"); + + if (accessMode == AccessMode.Secure) root = Config.Get("UploadFolder.VirtualRoot.Secure").Or(SecureVirtualRoot); + + return root + FolderName + "/"; + } + + /// + /// Determines whether this is an empty blob. + /// + public bool IsEmpty() + { + if (hasValue) return false; + + if (IsEmptyBlob) return true; + + if (FileName == EMPTY_FILE) return true; + + if (GetStorageProvider().FileExists(this)) { hasValue = true; return false; } + + if (FileData == null) return true; + + return FileData.None(); + } + + /// + /// Determines whether this blob has any content. + /// + public bool HasValue() => !IsEmpty(); + + /// + /// Creates a clone of this blob. + /// + public async Task Clone() => await Clone(attach: false, @readonly: false); + + public async Task Clone(bool attach, bool @readonly) + { + if (!attach && @readonly) throw new ArgumentException("readonly can be set to true only when attaching."); + + var result = new Blob(await GetFileData(), FileName); + + if (ownerEntity != null && attach) + { + if (!@readonly) Attach(ownerEntity, OwnerProperty, FileAccessMode); + else + { + result.ownerEntity = ownerEntity; + result.OwnerProperty = OwnerProperty; + result.FileAccessMode = FileAccessMode; + } + } + + return result; + } + + /// + /// Attaches this Blob to a specific record's file property. + /// + public Blob Attach(Entity owner, string propertyName) => Attach(owner, propertyName, AccessMode.Open); + + /// + /// Attaches this Blob to a specific record's file property. + /// + public Blob Attach(Entity owner, string propertyName, AccessMode accessMode) + { + ownerEntity = owner; + OwnerProperty = propertyName; + FileAccessMode = accessMode; + + if (owner is GuidEntity) owner.Saving.Handle(Owner_Saving); + else owner.Saved.Handle(Owner_Saved); + + owner.Deleting.Handle(Delete); + return this; + } + + /// + /// Detaches this Blob. + /// + public void Detach() + { + if (ownerEntity == null) return; + + ownerEntity.Saving.RemoveHandler(Owner_Saving); + ownerEntity.Saved.RemoveHandler(Owner_Saved); + ownerEntity.Deleting.RemoveHandler(Delete); + } + + // TODO: Deleting should be async and so on. + + /// + /// Deletes this blob from the disk. + /// + Task Delete(EventArgs e) + { + if (ShouldSuppressPersistence) return Task.CompletedTask; + + if (ownerEntity.GetType().Defines()) return Task.CompletedTask; + + DeleteFromDisk(); + + return Task.CompletedTask; + } + + void DeleteFromDisk() + { + if (ownerEntity == null) throw new InvalidOperationException(); + + GetStorageProvider().Delete(this); + + FileData = null; + } + + async Task Owner_Saving(System.ComponentModel.CancelEventArgs e) + { + if (!ShouldSuppressPersistence) await SaveOnDisk(); + } + + async Task Owner_Saved(SaveEventArgs e) + { + if (!ShouldSuppressPersistence) await SaveOnDisk(); + } + + /// + /// Saves this file on the disk. + /// + public async Task SaveOnDisk() + { + if (FileData != null && FileData.Length > 0) + await GetStorageProvider().Save(this); + + else if (IsEmptyBlob) + DeleteFromDisk(); + } + + /// + /// Gets the mime type based on the file extension. + /// + public string GetMimeType() => $"c:\\{FileName}".AsFile().GetMimeType();// The blob may be in-memory. + + /// Determines if this blob's file extension is for audio or video. + public bool IsMedia() => GetMimeType().StartsWithAny("audio/", "video/"); + + string GetFilesRoot() => GetPhysicalFilesRoot(FileAccessMode).FullName; + + /// + /// Gets the physical path root. + /// + public static DirectoryInfo GetPhysicalFilesRoot(AccessMode accessMode) + { + var result = PhysicalFilesRoots.GetOrAdd(accessMode, + m => + { + var folderConfigKey = accessMode == AccessMode.Secure ? "UploadFolder.Secure" : "UploadFolder"; + var defaultFolder = accessMode == AccessMode.Secure ? "App_Data\\" : "Documents\\"; + + var folder = Config.Get(folderConfigKey).Or(defaultFolder).TrimEnd('\\') + "\\"; + + if (!folder.StartsWith("\\\\") && folder[1] != ':') // Relative address: + folder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folder); + + return folder; + }); + + return new DirectoryInfo(result); + } + + internal string LocalFolder + { + get + { + if (ownerEntity == null) return null; + + if (ownerEntity is IPickyBlobContainer) + { + var result = (ownerEntity as IPickyBlobContainer).GetPhysicalFolderPath(this); + if (result.HasValue()) return result; + } + + var docsFolder = Path.Combine(GetFilesRoot(), FolderName + "\\"); + + return docsFolder; + } + } + + /// + /// This will return the blob object linked to the correct entity. + /// + /// Expected format: Type/Id/Property. + public static Blob FromReference(string reference) + { + var parts = reference.OrEmpty().Split('/'); + if (parts.Length != 3) throw new ArgumentException("Expected format is Type/ID/Property."); + + var type = EntityFinder.GetEntityType(parts.First()); + + if (type == null) + throw new ArgumentException($"The type '{parts.First()}' is not found in the currently loaded assemblies."); + + var id = parts[1]; + var propertyName = parts.Last(); + + var entity = Entity.Database.GetOrDefault(id, type); + if (entity == null) + throw new ArgumentException($"Could not load an instance of '{parts.First()}' with the ID of '{id} from the database."); ; + + var property = type.GetProperty(propertyName); + if (property == null) + throw new Exception($"The type {type.FullName} does not have a property named {propertyName}."); + + return property.GetValue(entity) as Blob; + } + + /// + /// Gets the local physical path of this file. + /// + public string LocalPath + { + get + { + if (ownerEntity == null) return null; + + var result = Path.Combine(LocalFolder, GetFileNameWithoutExtension() + FileExtension); + + if (!Directory.Exists(LocalFolder)) Directory.CreateDirectory(LocalFolder); + + return result; + } + } + + public string GetFileNameWithoutExtension() + { + if (ownerEntity == null) return null; + if (ownerEntity is IntEntity && ownerEntity.IsNew) return null; + + if (ownerEntity is IPickyBlobContainer) + { + var result = (ownerEntity as IPickyBlobContainer).GetFileNameWithoutExtension(this); + + if (result.HasValue()) return result; + } + + if (StoreWithFileName) + return FileName.TrimEnd(FileExtension); + + return ownerEntity?.GetId().ToStringOrEmpty(); + } + + #region Unsafe Files Handling + + /// + /// Gets a list of unsafe file extensions. + /// + public static string[] GetUnsafeExtensions() => UnsafeExtensions; + + /// + /// Determines whether the extension of this file is potentially unsafe. + /// + public bool HasUnsafeExtension() => HasUnsafeFileExtension(FileName); + + public static bool HasUnsafeFileExtension(string fileName) + { + if (fileName.IsEmpty()) return false; + + var extension = Path.GetExtension(fileName).OrEmpty().Where(x => x.IsLetter()).ToArray().ToString("").ToLower(); + + return UnsafeExtensions.Contains(extension); + } + + #endregion + + public override bool Equals(object obj) + { + var other = obj as Blob; + + if (other == null) return false; + else if (ReferenceEquals(this, other)) return true; + else if (IsEmpty() && other.IsEmpty()) return true; + + return false; + } + + public override int GetHashCode() => base.GetHashCode(); + + public static bool operator ==(Blob left, Blob right) + { + if (ReferenceEquals(left, right)) + return true; + + else if (ReferenceEquals(left, null)) + return false; + + else + return left.Equals(right); + } + + public string FileNameWithoutExtension => Path.GetFileNameWithoutExtension(FileName); + + public static bool operator !=(Blob left, Blob right) => !(left == right); + + /// + /// Gets this blob if it has a value, otherwise another specified blob. + /// + public Blob Or(Blob other) + { + if (IsEmpty()) return other; + else return this; + } + + #region IComparable Members + + /// + /// Compares this blob versus a specified other blob. + /// + public int CompareTo(Blob other) + { + if (other == null) + return 1; + + if (IsEmpty()) + { + if (other.IsEmpty()) + return 0; + else return -1; + } + else + { + if (other.IsEmpty()) + return 1; + else + { + var me = FileData?.Length; + var him = other.FileData?.Length; + if (me == him) return 0; + if (me > him) return 1; + else return -1; + } + } + } + + /// + /// Compares this blob versus a specified other blob. + /// + public int CompareTo(object obj) => CompareTo(obj as Blob); + + #endregion + } +} \ No newline at end of file diff --git a/Olive.Entities/Blob/BlobStorageProviderFactory.cs b/Olive.Entities/Blob/BlobStorageProviderFactory.cs new file mode 100644 index 000000000..9e96d2b6f --- /dev/null +++ b/Olive.Entities/Blob/BlobStorageProviderFactory.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Olive.Entities +{ + public class BlobStorageProviderFactory + { + public static IBlobStorageProvider DefaultProvider = new DiskBlobStorageProvider(); + + /// + /// This is to be configured in Global.asax if a different provider is needed for specific files. + /// Example: Olive.Entities.BlobStorageProviderFactory.Add("Customer.Logo", new MySpecialStorageProvider); + /// + public static Dictionary Providers = new Dictionary(); + + /// + /// In the format: {type}.{property} e.g. Customer.Logo. + /// + internal static IBlobStorageProvider GetProvider(string folderName) + { + if (folderName.IsEmpty()) return DefaultProvider; + + return Providers.GetOrDefault(folderName) ?? DefaultProvider; + } + } +} diff --git a/Olive.Entities/Blob/DiskBlobStorageProvider.cs b/Olive.Entities/Blob/DiskBlobStorageProvider.cs new file mode 100644 index 000000000..1705d0394 --- /dev/null +++ b/Olive.Entities/Blob/DiskBlobStorageProvider.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace Olive.Entities +{ + class DiskBlobStorageProvider : IBlobStorageProvider + { + // TODO: It is a quick workaround for Intern which seems to be back in .Net Core 2 + ConcurrentDictionary StringKeyAsyncLock = new ConcurrentDictionary(); + + AsyncLock GetAsyncLock(string key) => StringKeyAsyncLock.GetOrAdd(key, x => new AsyncLock()); + + public async Task Save(Blob blob) + { + var fileDataToSave = await blob.GetFileData(); // Because file data will be lost in delete. + + if (File.Exists(blob.LocalPath)) + { + using (await GetAsyncLock(blob.LocalPath).Lock()) + { + var data = await File.ReadAllBytesAsync(blob.LocalPath); + if (data == null) await Delete(blob); + else if (data.SequenceEqual(await blob.GetFileData())) return; // Nothing changed. + else await Delete(blob); + } + } + + using (await GetAsyncLock(blob.LocalPath).Lock()) + { + await new Func(async () => await File.WriteAllBytesAsync(blob.LocalPath, fileDataToSave)).Invoke(retries: 6, waitBeforeRetries: TimeSpan.FromSeconds(0.5)); + } + } + + public async Task Delete(Blob blob) + { + if (!Directory.Exists(blob.LocalFolder)) Directory.CreateDirectory(blob.LocalFolder); + + var tasks = new List(); + + // Delete old file. TODO: Archive the files instead of deleting. + foreach (var file in Directory.GetFiles(blob.LocalFolder, blob.GetFileNameWithoutExtension() + ".*")) + { + using (await GetAsyncLock(file).Lock()) + { + tasks.Add(new Func(async () => await Task.Factory.StartNew(() => File.Delete(file))) + .Invoke(retries: 6, waitBeforeRetries: TimeSpan.FromSeconds(0.5)) + ); + } + } + + await Task.WhenAll(tasks); + } + + public async Task Load(Blob blob) + { + using (await GetAsyncLock(blob.LocalPath).Lock()) + { + if (File.Exists(blob.LocalPath)) + return await File.ReadAllBytesAsync(blob.LocalPath); + } + + // Look in fall-back paths for file + foreach (var fallbackPath in blob.FallbackPaths) + { + using (await GetAsyncLock(blob.LocalPath).Lock()) + { + if (File.Exists(fallbackPath)) + return await File.ReadAllBytesAsync(fallbackPath); + } + } + + return new byte[0]; + } + + public bool FileExists(Blob blob) + { + if (blob.LocalPath.HasValue() && File.Exists(blob.LocalPath)) + return true; + + // Check for file in fall-back paths + if (blob.FallbackPaths.Any(File.Exists)) + return true; + + return false; + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Blob/IBlobStorageProvider.cs b/Olive.Entities/Blob/IBlobStorageProvider.cs new file mode 100644 index 000000000..56b29309c --- /dev/null +++ b/Olive.Entities/Blob/IBlobStorageProvider.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace Olive.Entities +{ + public interface IBlobStorageProvider + { + Task Save(Blob blob); + Task Delete(Blob blob); + Task Load(Blob blob); + bool FileExists(Blob blob); + } +} + diff --git a/Olive.Entities/Blob/IPickyBlobContainer.cs b/Olive.Entities/Blob/IPickyBlobContainer.cs new file mode 100644 index 000000000..a7233f04b --- /dev/null +++ b/Olive.Entities/Blob/IPickyBlobContainer.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; + +namespace Olive.Entities +{ + /// + /// This interface can be implemented on any entity which has a property of type Blob. + /// + public interface IPickyBlobContainer : IEntity + { + /// + /// Gets the path to the physical folder containing files for the specified blob property. + /// If you don't need to implement this specific method, simply return NULL. + /// + string GetPhysicalFolderPath(Blob blob); + + /// + /// Gets the URL to the virtual folder containing files for the specified blob property. + /// If you don't need to implement this specific method, simply return NULL. + /// + string GetVirtualFolderPath(Blob blob); + + /// + /// Gets the name of the file used for the specified blob property, without extension. + /// If you don't need to implement this specific method, simply return NULL. + /// + string GetFileNameWithoutExtension(Blob blob); + + /// + /// Gets the fallback paths for the specified blob. + /// + IEnumerable GetFallbackPaths(Blob blob); + } +} \ No newline at end of file diff --git a/Olive.Entities/Blob/IPickyBlobUrlContainer.cs b/Olive.Entities/Blob/IPickyBlobUrlContainer.cs new file mode 100644 index 000000000..19bea5529 --- /dev/null +++ b/Olive.Entities/Blob/IPickyBlobUrlContainer.cs @@ -0,0 +1,10 @@ +namespace Olive.Entities +{ + public interface IPickyBlobUrlContainer : IEntity + { + /// + /// Gets the url of the specified blob. + /// + string GetUrl(Blob blob); + } +} \ No newline at end of file diff --git a/Olive.Entities/CachedReference.cs b/Olive.Entities/CachedReference.cs new file mode 100644 index 000000000..50e216070 --- /dev/null +++ b/Olive.Entities/CachedReference.cs @@ -0,0 +1,58 @@ +using System; +using System.Threading.Tasks; + +namespace Olive.Entities +{ + internal interface ICachedReference { void Invalidate(); } + + /// + /// Provides immediate access to retrieved entities. It is aware of deletes and updates. + /// + public class CachedReference : CachedReference where TEntity : GuidEntity { } + + /// + /// Provides immediate access to retrieved entities. It is aware of deletes and updates. + /// + public class CachedReference : ICachedReference where TEntity : Entity where TId : struct + { + TEntity Value; + TId? Id; + + /// + /// Gets the entity record from a specified database call expression. + /// The first time it is loaded, all future calls will be immediately served. + /// + public async Task Get(TId? id) + { + if (!Id.Equals(id)) Value = null; // Different ID from the cache. + Id = id; + + if (Value == null) + { + if (id == null) return null; + + var result = await Entity.Database.Get(id.ToString()); + + if (!Entity.Database.AnyOpenTransaction()) + { + Value = result; + Value.RegisterCachedCopy(this); + } + else return result; + } + + return Value; + } + + void Bind(TEntity entity) + { + Id = entity?.ID ?? throw new ArgumentNullException(nameof(entity)); + Value = entity; + + if (!Entity.Database.AnyOpenTransaction()) + Value.RegisterCachedCopy(this); + } + + void ICachedReference.Invalidate() => Value = null; + } +} \ No newline at end of file diff --git a/Olive.Entities/Database/BinaryCriterion.cs b/Olive.Entities/Database/BinaryCriterion.cs new file mode 100644 index 000000000..5a2e238a4 --- /dev/null +++ b/Olive.Entities/Database/BinaryCriterion.cs @@ -0,0 +1,60 @@ +using System.Linq.Expressions; + +namespace Olive.Entities +{ + public enum BinaryOperator { OR, AND } + + public class BinaryCriterion : Criterion + { + BinaryCriterion() : base("N/A", "N/A") { } + + BinaryCriterion(Criterion left, BinaryOperator opt, Criterion right) + : base("N/A", "N/A") + { + Left = left; + Right = right; + Operator = opt; + } + + public Criterion Left { get; set; } + public Criterion Right { get; set; } + public BinaryOperator Operator { get; set; } + internal bool IsConvertedCompletely { get; set; } = true; + + public BinaryCriterion Or(BinaryCriterion left, BinaryCriterion right) => new BinaryCriterion(left, BinaryOperator.OR, right); + + public BinaryCriterion And(BinaryCriterion left, BinaryCriterion right) => new BinaryCriterion(left, BinaryOperator.AND, right); + + public static BinaryCriterion From(BinaryExpression expression) where T : IEntity => CreateByExpression(expression); + + static Criterion CreateByExpression(Expression expression) where T : IEntity => + CriteriaExtractor.CreateCriterion(expression); + + static BinaryCriterion CreateByExpression(BinaryExpression expression) where T : IEntity + { + Criterion left, right; + + if (expression.Left.NodeType == ExpressionType.OrElse || expression.Left.NodeType == ExpressionType.AndAlso) + left = CreateByExpression(expression.Left as BinaryExpression); + else + left = CreateByExpression(expression.Left); + + if (expression.Right.NodeType == ExpressionType.OrElse || expression.Right.NodeType == ExpressionType.AndAlso) + right = CreateByExpression(expression.Right as BinaryExpression); + else + right = CreateByExpression(expression.Right); + + if (left == null || right == null) + throw new System.Exception("Failed to convert the specified expression to Criterion: " + expression); + + var op = (expression.NodeType == ExpressionType.OrElse ? BinaryOperator.OR : BinaryOperator.AND); + return new BinaryCriterion(left, op, right); + } + + public override string ToString() + { + if (SqlCondition.HasValue()) return SqlCondition; + return $"({Left}-{Operator}-{Right})"; + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Database/Criterion.cs b/Olive.Entities/Database/Criterion.cs new file mode 100644 index 000000000..48506534d --- /dev/null +++ b/Olive.Entities/Database/Criterion.cs @@ -0,0 +1,305 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace Olive.Entities +{ + /// + /// A basic implementation of a database query criterion. + /// + public class Criterion : ICriterion + { + const string NULL_ESCAPE = "[#-NULL-VALUE-#]", COLON_ESCAPE = "[#-SEPERATOR-#]"; + + static readonly MethodInfo StringContainsMethod = typeof(string).GetMethod("Contains"); + static readonly MethodInfo StringContainsExtensionMethod = typeof(OliveExtensions).GetMethod("Contains", new[] { typeof(string), typeof(string), typeof(bool) }); + static readonly MethodInfo StringLacksExtensionMethod = typeof(OliveExtensions).GetMethod("Lacks", new[] { typeof(string), typeof(string), typeof(bool) }); + static readonly MethodInfo StringIsEmptyExtensionMethod = typeof(OliveExtensions).GetMethod("IsEmpty", new[] { typeof(string) }); + static readonly MethodInfo StringHasValueExtensionMethod = typeof(OliveExtensions).GetMethod("HasValue", new[] { typeof(string) }); + static readonly MethodInfo IsAnyOfExtensionMethod = typeof(OliveExtensions).GetMethod("IsAnyOf", new[] { typeof(string), typeof(string[]) }); + static readonly MethodInfo StringStartsWithMethod = typeof(string).GetMethod("StartsWith", new[] { typeof(string) }); + static readonly MethodInfo StringEndsWithMethod = typeof(string).GetMethod("EndsWith", new[] { typeof(string) }); + + /// + /// Initializes a new instance of the class. + /// + public Criterion(string propertyName, object value) : this(propertyName, FilterFunction.Is, value) { } + + /// + /// Initializes a new instance of the class. + /// + public Criterion(string propertyName, FilterFunction function, object value) + { + PropertyName = propertyName; + FilterFunction = function; + Value = value; + } + + /// + /// Initializes a new instance of the class. + /// + public Criterion(string propertyName, FilterFunction function, IEnumerable ids) + : this(propertyName, function, "(" + ids.Select(x => "'" + x.ToString() + "'").ToString(", ") + ")") + { + if (function != FilterFunction.In) + throw new ArgumentException("List of IDs is only supported with 'FilterFunction.In'."); + } + + /// + /// Initializes a new instance of the class. + /// + public Criterion(string propertyName, FilterFunction function, IEnumerable ids) + : this(propertyName, function, "(" + ids.Select(x => "'" + x.ToStringOrEmpty().Replace("'", "''") + "'").ToString(", ") + ")") + { + if (function != FilterFunction.In) + throw new ArgumentException("List of IDs is only supported with 'FilterFunction.In'."); + } + + /// + /// Initializes a new instance of the class. + /// + public Criterion(string propertyName, FilterFunction function, IEnumerable ids) + : this(propertyName, function, "(" + ids.ToString(", ") + ")") + { + if (function != FilterFunction.In) + throw new ArgumentException("List of IDs is only supported with 'FilterFunction.In'."); + } + + /// + /// Initializes a new instance of the class. + /// + public Criterion(string propertyName, string function, object value) + { + PropertyName = propertyName; + FilterFunction = (FilterFunction)Enum.Parse(typeof(FilterFunction), function); + Value = value; + } + + /// + /// Gets or sets the PropertyName of this Condition. + /// + public string PropertyName { get; private set; } + + /// + /// Gets or sets the SqlCondition of this Condition. + /// + public string SqlCondition { get; private set; } + + /// + /// Gets or sets the Filter Option of this Condition. + /// + public FilterFunction FilterFunction { get; set; } + + /// + /// Gets or sets the Value of this Condition. + /// + public object Value { get; set; } + + /// + /// Gets a text representation of the value. + /// + string GetSerializedValue() + { + if (Value is IEntity) + { + return (Value as IEntity).GetId().ToString(); + } + else + { + return Value?.ToStringOrEmpty(); + } + } + + /// + /// Returns a string that represents this instance. + /// + public override string ToString() + { + if (SqlCondition.HasValue()) return SqlCondition; + + var valueText = Value == null ? NULL_ESCAPE : GetSerializedValue().Replace(":", COLON_ESCAPE); + + return string.Join(":", PropertyName, FilterFunction, valueText); + } + + /// + /// Parses the specified condition string. + /// + public static Criterion Parse(string criterionString) + { + if (criterionString.IsEmpty()) + throw new ArgumentNullException(nameof(criterionString)); + + var parts = criterionString.Split(':'); + + var value = parts[2].Replace(COLON_ESCAPE, ":"); + if (value == NULL_ESCAPE) value = null; + + return new Criterion(parts[0], parts[1], value); + } + + object ICriterion.Value => (Value as IEntity)?.GetId() ?? Value; + + public static Criterion FromSql(string sqlCondition) + { + return new DirectDatabaseCriterion(sqlCondition); + } + + public static Criterion From(Expression> criterion) where T : IEntity + { + if (criterion == null) + throw new ArgumentNullException(nameof(criterion)); + + var methodCallExpression = criterion.Body as MethodCallExpression; + if (methodCallExpression != null) return From(methodCallExpression); + + var binaryExpression = criterion.Body as BinaryExpression; + if (binaryExpression != null) return From(binaryExpression); + + return CriteriaExtractor.CreateCriterion(criterion.Body); + } + + public static Criterion From(MethodCallExpression expression, bool throwOnError = true) + { + Expression valueExpression = null; + MemberExpression propertyExpression; + FilterFunction filter; + string sql = null; + + if (expression.Method == StringContainsMethod) + { + propertyExpression = expression.Object as MemberExpression; + valueExpression = expression.Arguments[0]; + filter = FilterFunction.Contains; + } + else if (expression.Method == StringContainsExtensionMethod) + { + propertyExpression = expression.Arguments[0] as MemberExpression; + valueExpression = expression.Arguments[1]; + filter = FilterFunction.Contains; + } + else if (expression.Method == StringIsEmptyExtensionMethod) + { + propertyExpression = expression.Arguments[0] as MemberExpression; + + sql = "(${{#PROPERTY#}} IS NULL OR ${{#PROPERTY#}} = '')"; + filter = default(FilterFunction); + } + else if (expression.Method == StringHasValueExtensionMethod) + { + propertyExpression = expression.Arguments[0] as MemberExpression; + + sql = "(${{#PROPERTY#}} IS NOT NULL AND ${{#PROPERTY#}} <> '')"; + filter = default(FilterFunction); + } + else if (expression.Method == StringLacksExtensionMethod) + { + propertyExpression = expression.Arguments[0] as MemberExpression; + valueExpression = expression.Arguments[1]; + filter = FilterFunction.NotContains; + } + else if (expression.Method == StringStartsWithMethod) + { + propertyExpression = expression.Object as MemberExpression; + valueExpression = expression.Arguments[0]; + filter = FilterFunction.BeginsWith; + } + else if (expression.Method == StringEndsWithMethod) + { + propertyExpression = expression.Object as MemberExpression; + valueExpression = expression.Arguments[0]; + filter = FilterFunction.EndsWith; + } + else if (expression.Method == IsAnyOfExtensionMethod) + { + propertyExpression = expression.Arguments.First() as MemberExpression; + valueExpression = expression.Arguments[1]; + filter = FilterFunction.In; + } + else + { + if (!throwOnError) return null; + + throw new ArgumentException("Invalid database criteria. The provided filter expression cannot be evaluated and converted into a SQL condition."); + } + + if (propertyExpression == null || !(propertyExpression.Member is PropertyInfo)) + { + if (!throwOnError) return null; + + throw new ArgumentException("Invalid database criteria. The provided filter expression cannot be evaluated and converted into a SQL condition." + expression.ToString() + + Environment.NewLine + Environment.NewLine + "Consider using application level filter using the \".Where(...)\" clause."); + } + + var property = propertyExpression.Member.Name; + + // Middle properties? + while (propertyExpression.Expression is MemberExpression) + { + propertyExpression = (propertyExpression.Expression as MemberExpression); + property = propertyExpression.Member.Name + "." + property; + } + + if (sql.HasValue()) + { + if (property.Contains(".")) return null; // Nesting is not supported. + + return new DirectDatabaseCriterion(sql.Replace("#PROPERTY#", property)) { PropertyName = property }; + } + else + { + var value = Expression.Lambda(valueExpression).Compile().DynamicInvoke(); + if (valueExpression.NodeType == ExpressionType.NewArrayInit) + value = $"({(value as string[]).Select(part => $"'{part}'").ToString(",")})"; + + return new Criterion(property, filter, value); + } + } + + static Criterion From(BinaryExpression expression) + { + var propertyExpression = expression.Left as MemberExpression; + + if (propertyExpression == null && expression.Left is UnaryExpression) + { + propertyExpression = (expression.Left as UnaryExpression).Operand as MemberExpression; + } + + if (propertyExpression == null || !(propertyExpression.Member is PropertyInfo)) + throw new ArgumentException("Invalid use of Property comparison in condition expression: " + expression.ToString()); + + var value = Expression.Lambda(expression.Right).Compile().DynamicInvoke(); + + var property = propertyExpression.Member.Name; + + // Middle properties? + while (propertyExpression.Expression is MemberExpression) + { + propertyExpression = (propertyExpression.Expression as MemberExpression); + property = propertyExpression.Member.Name + "." + property; + } + + return new Criterion(property, GetFilterFunction(expression.NodeType), value); + } + + /// + /// Gets the filter option for a specified Lambda expression node type. + /// + static FilterFunction GetFilterFunction(ExpressionType nodeType) + { + switch (nodeType) + { + case ExpressionType.Equal: return FilterFunction.Is; + case ExpressionType.NotEqual: return FilterFunction.IsNot; + case ExpressionType.GreaterThan: return FilterFunction.MoreThan; + case ExpressionType.GreaterThanOrEqual: return FilterFunction.MoreThanOrEqual; + case ExpressionType.LessThan: return FilterFunction.LessThan; + case ExpressionType.LessThanOrEqual: return FilterFunction.LessThanOrEqual; + default: + throw new NotSupportedException("GetFilterFunction() does not support expression of type " + nodeType); + } + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Database/DbTransactionScopeOption.cs b/Olive.Entities/Database/DbTransactionScopeOption.cs new file mode 100644 index 000000000..c97b22baa --- /dev/null +++ b/Olive.Entities/Database/DbTransactionScopeOption.cs @@ -0,0 +1,9 @@ +namespace Olive.Entities +{ + public enum DbTransactionScopeOption + { + Required, + RequiresNew, + Suppress + } +} \ No newline at end of file diff --git a/Olive.Entities/Database/DirectDatabaseCriterion.cs b/Olive.Entities/Database/DirectDatabaseCriterion.cs new file mode 100644 index 000000000..8276a0cd0 --- /dev/null +++ b/Olive.Entities/Database/DirectDatabaseCriterion.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace Olive.Entities +{ + /// + /// Enables adding a direct SQL WHERE criteria to the database query. + /// + public class DirectDatabaseCriterion : Criterion + { + /// + /// Gets the parameters used in the specified custom SQL criteria. + /// + public Dictionary Parameters = new Dictionary(); + + /// + /// Gets or sets the SQL criteria. + /// + public string SqlCriteria { get; set; } + + /// + /// N/A. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new object Value + { + get { return null; } + set { throw new NotSupportedException(); } + } + + /// + /// Specifies whether this criteria is compatible with normal caching. + /// + public bool IsCacheSafe { get; set; } + + /// + /// N/A. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public new FilterFunction FilterFunction + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + /// + /// Initializes a new instance of the class. + /// + public DirectDatabaseCriterion(string sqlCriteria) + : base("N/A", "N/A") => SqlCriteria = sqlCriteria; + + /// + /// Initializes a new instance of the class. + /// + /// Item1 = Parameter name (without the @ character). Item2 = parameter value. + public DirectDatabaseCriterion(string sqlCriteria, params Tuple[] parameters) + : this(sqlCriteria) + { + foreach (var p in parameters) + Parameters.Add(p.Item1, p.Item2); + } + + /// + /// Initializes a new instance of the class. + /// + public DirectDatabaseCriterion(string sqlCriteria, Dictionary parameters) + : this(sqlCriteria) => Parameters = parameters ?? throw new ArgumentNullException(nameof(parameters)); + + /// + /// Initializes a new instance of the class. + /// + /// Example: new {Parameter1 = SomeValue(), Parameter2 = AnotherValue()} + public DirectDatabaseCriterion(string sqlCriteria, object parameters) + : this(sqlCriteria) + { + if (parameters == null) throw new ArgumentNullException(nameof(parameters)); + + Parameters = parameters.GetType().GetProperties().ToDictionary(x => x.Name, x => x.GetValue(parameters)); + } + + /// + /// Returns a string that represents this instance. + /// + public override string ToString() => + $"{SqlCriteria}|{Parameters.Select(x => "{0}={1}".FormatWith(x.Key, x.Value)).ToString("|")}"; + + public string MapSqlCriteria(Dictionary propertyMappings) + { + if (PropertyName.IsEmpty() || PropertyName == "N/A") return SqlCriteria; + + return SqlCriteria.Replace($"${{{{{PropertyName}}}}}", propertyMappings[PropertyName]); + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Database/FilterFunction.cs b/Olive.Entities/Database/FilterFunction.cs new file mode 100644 index 000000000..a7d23eeca --- /dev/null +++ b/Olive.Entities/Database/FilterFunction.cs @@ -0,0 +1,70 @@ +using System; + +namespace Olive.Entities +{ + /// + /// Provides options for filter functions. + /// + public enum FilterFunction + { + Is, + IsNot, + Null, + NotNull, + + Contains, + NotContains, + ContainsAll, + ContainsAny, + + In, + NotIn, + BeginsWith, + EndsWith, + InRange, + + LessThan, + LessThanOrEqual, + MoreThan, + MoreThanOrEqual + } + + public static class FilterFunctionServices + { + /// + /// Gets the database operator equivalent for this filter option. + /// + public static string GetDatabaseOperator(this FilterFunction option) + { + switch (option) + { + case FilterFunction.Contains: + case FilterFunction.BeginsWith: + case FilterFunction.EndsWith: + return "LIKE"; + case FilterFunction.Is: + return "="; + case FilterFunction.IsNot: + return "<>"; + case FilterFunction.LessThan: + return "<"; + case FilterFunction.LessThanOrEqual: + return "<="; + case FilterFunction.MoreThan: + return ">"; + case FilterFunction.MoreThanOrEqual: + return ">="; + case FilterFunction.Null: + return "Is NULL"; + case FilterFunction.In: + return "IN"; + case FilterFunction.NotNull: + return "Is NOT NULL"; + case FilterFunction.NotContains: + return "NOT LIKE"; + default: + throw new NotSupportedException(option + " is not supported in GetDatabaseOperator()."); + } + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Database/ICriterion.cs b/Olive.Entities/Database/ICriterion.cs new file mode 100644 index 000000000..9b51a20b0 --- /dev/null +++ b/Olive.Entities/Database/ICriterion.cs @@ -0,0 +1,15 @@ +namespace Olive.Entities +{ + /// + /// Provides an abstraction for database query criteria. + /// + public interface ICriterion + { + string PropertyName { get; } + + FilterFunction FilterFunction { get; set; } + object Value { get; } + + string SqlCondition { get; } + } +} \ No newline at end of file diff --git a/Olive.Entities/Database/IDatabase.cs b/Olive.Entities/Database/IDatabase.cs new file mode 100644 index 000000000..9c4afe3a2 --- /dev/null +++ b/Olive.Entities/Database/IDatabase.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace Olive.Entities +{ + public interface IDatabase + { + AsyncEvent CacheRefreshed { get; } + + /// + /// It's raised when any record is saved or deleted in the system. + /// + AsyncEvent Updated { get; } + + Task Refresh(); + + bool AnyOpenTransaction(); + + Task EnlistOrCreateTransaction(Func action); + + Task Parse(string toString, bool caseSensitive = false) where T : IEntity; + + int CountAllObjectsInCache(); + + Task> ReadManyToManyRelation(IEntity instance, string property); + + Task Reload(T instance) where T : IEntity; + + Task Any() where T : IEntity; + + Task Any(Expression> criteria) where T : IEntity; + + Task None() where T : IEntity; + + Task None(Expression> criteria) where T : IEntity; + + Task Count(Expression> criteria) where T : IEntity; + + #region Delete + + Task Delete(IEntity instance); + + Task Delete(IEntity instance, DeleteBehaviour behaviour); + + Task Delete(IEnumerable instances) where T : IEntity; + + Task DeleteAll() where T : IEntity; + + Task DeleteAll(Expression> criteria) where T : IEntity; + + Task UpdateAll(Action change) where T : IEntity; + + #endregion + + Task FirstOrDefault(Expression> criteria) where T : IEntity; + + #region Get + + Task Get(string entityId) where T : IEntity; + + Task Get(Guid id) where T : IEntity; + + Task Get(Guid? id) where T : IEntity; + + Task Get(int? id) where T : IEntity; + + Task Get(int id) where T : IEntity; + + Task> Get(Guid entityID, Type objectType); + + Task Get(object entityID, Type objectType); + + Task GetOrDefault(object id) where T : IEntity; + + Task GetOrDefault(object id, Type type); + + #endregion + + Task> GetList(Expression> criteria = null) where T : IEntity; + + #region ProviderManagement + + Dictionary AssemblyProviderFactories { get; } + + IEnumerable GetRegisteredAssemblies(); + + IDataProvider GetProvider() where T : IEntity; + + IDataProvider GetProvider(IEntity item); + + IDataProvider GetProvider(Type type); + + ITransactionScope CreateTransactionScope(DbTransactionScopeOption option = DbTransactionScopeOption.Required); + + #endregion + + #region Save + + Task Save(T entity) where T : IEntity; + + Task Save(IEntity entity, SaveBehaviour behaviour); + + Task> Save(List records) where T : IEntity; + + Task> Update(IEnumerable items, Action action) where T : IEntity; + + Task> Update(IEnumerable items, Action action, SaveBehaviour behaviour) where T : IEntity; + + Task Update(T item, Action action) where T : IEntity; + + Task Update(T item, Action action, SaveBehaviour behaviour) where T : IEntity; + + Task BulkInsert(Entity[] objects, int batchSize = 10, bool bypassValidation = false); + + Task BulkUpdate(Entity[] objects, int batchSize = 10, bool bypassValidation = false); + + #endregion + + #region Save List + + Task> Save(T[] records) where T : IEntity; + + Task> Save(IEnumerable records) where T : IEntity; + + Task> Save(IEnumerable records, SaveBehaviour behaviour) where T : IEntity; + + #endregion + + IDatabaseQuery Of() where TEntity : IEntity; + + IDatabaseQuery Of(Type type); + } +} diff --git a/Olive.Entities/Database/IDatabaseQuery.cs b/Olive.Entities/Database/IDatabaseQuery.cs new file mode 100644 index 000000000..64eae1fbc --- /dev/null +++ b/Olive.Entities/Database/IDatabaseQuery.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace Olive.Entities +{ + public interface IDatabaseQuery + { + Type EntityType { get; } + Dictionary Parameters { get; } + int PageStartIndex { get; set; } + int? PageSize { get; set; } + int? TakeTop { get; set; } + + IDatabaseQuery Include(string associationProperty); + IDatabaseQuery Include(IEnumerable associationProperties); + IDatabaseQuery Where(params ICriterion[] criteria); + IDatabaseQuery OrderBy(string property, bool descending); + IDatabaseQuery ThenBy(string property, bool descending); + IDatabaseQuery Top(int rows); + IDatabaseQuery OrderBy(string property); + + Task Count(); + Task> GetList(); + Task FirstOrDefault(); + } + + public interface IDatabaseQuery : IDatabaseQuery + where TEntity : IEntity + { + IDatabaseQuery Where(Expression> criteria); + new Task> GetList(); + new Task FirstOrDefault(); + new IDatabaseQuery OrderBy(string property); + new IDatabaseQuery Top(int rows); + new IDatabaseQuery Where(params ICriterion[] criteria); + + IDatabaseQuery ThenBy(Expression> property, bool descending = false); + IDatabaseQuery OrderByDescending(Expression> property); + IDatabaseQuery OrderBy(Expression> property, bool descending = false); + IDatabaseQuery ThenByDescending(Expression> property); + IDatabaseQuery Include(Expression> property); + + /// + /// Gets a list of entities of the given type from the database with the specified type matching the specified criteria. + /// If no criteria is specified, the count of all instances will be returned. + /// + Task Aggregate(AggregateFunction function, Expression> property) + where TOutput : struct; + + Task Max(Expression> property) where TProperty : struct; + Task Min(Expression> property) where TProperty : struct; + Task Sum(Expression> property) where TProperty : struct; + Task Average(Expression> property) where TProperty : struct; + Task Average(Expression> property) where TProperty : struct; + Task Average(Expression> property); + } +} \ No newline at end of file diff --git a/Olive.Entities/Database/ITransactionScope.cs b/Olive.Entities/Database/ITransactionScope.cs new file mode 100644 index 000000000..c3779299b --- /dev/null +++ b/Olive.Entities/Database/ITransactionScope.cs @@ -0,0 +1,11 @@ +using System; + +namespace Olive.Entities +{ + public interface ITransactionScope : IDisposable + { + void Complete(); + + Guid ID { get; } + } +} diff --git a/Olive.Entities/Database/QueryOptions/FullTextSearchQueryOption.cs b/Olive.Entities/Database/QueryOptions/FullTextSearchQueryOption.cs new file mode 100644 index 000000000..e4cf8e68b --- /dev/null +++ b/Olive.Entities/Database/QueryOptions/FullTextSearchQueryOption.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +namespace Olive.Entities +{ + public class FullTextSearchQueryOption : QueryOption + { + /// + /// Creates a new FullTextIndexQueryOption instance. + /// + internal FullTextSearchQueryOption() { } + + #region Keyword + /// + /// Gets or sets the Keywords of this FullTextIndexQueryOption. + /// + public string Keyword { get; set; } + #endregion + + #region Properties + /// + /// Gets or sets the Properties of this FullTextIndexQueryOption. + /// + public IEnumerable Properties { get; set; } + #endregion + } +} diff --git a/Olive.Entities/Database/QueryOptions/PagingQueryOption.cs b/Olive.Entities/Database/QueryOptions/PagingQueryOption.cs new file mode 100644 index 000000000..f7e2844e4 --- /dev/null +++ b/Olive.Entities/Database/QueryOptions/PagingQueryOption.cs @@ -0,0 +1,37 @@ +using System; + +namespace Olive.Entities +{ + public class PagingQueryOption : QueryOption + { + /// + /// Creates a new ResultSetSizeQueryOption instance. + /// + internal PagingQueryOption() { } + + /// + /// Creates a new ResultSetSizeQueryOption instance. + /// + public PagingQueryOption(string orderBy, int startIndex, int pageSize) + { + if (orderBy.IsEmpty()) + throw new ArgumentException("Invalid PagingQueryOption specified. OrderBy is mandatory."); + + if (pageSize < 1) + throw new ArgumentException("Invalid PagingQueryOption specified. PageSize should be a positive number."); + + OrderBy = orderBy; + PageSize = pageSize; + PageStartIndex = startIndex; + } + + public int PageStartIndex { get; internal set; } + + public int PageSize { get; internal set; } + + /// + /// The direct SQL sort expression. E.g: MyColumn DESC, Something + /// + public string OrderBy { get; set; } + } +} \ No newline at end of file diff --git a/Olive.Entities/Database/QueryOptions/QueryOption.cs b/Olive.Entities/Database/QueryOptions/QueryOption.cs new file mode 100644 index 000000000..852857365 --- /dev/null +++ b/Olive.Entities/Database/QueryOptions/QueryOption.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace Olive.Entities +{ + public abstract class QueryOption + { + /// + /// Creates a FullTextSearch option for the search query. + /// + public static FullTextSearchQueryOption FullTextSearch(string keyword, params string[] properties) + { + if (keyword.IsEmpty()) + throw new ArgumentNullException(nameof(keyword)); + if (properties == null || properties.None()) + throw new ArgumentNullException(nameof(properties)); + return new FullTextSearchQueryOption { Keyword = keyword, Properties = properties }; + } + + public static FullTextSearchQueryOption FullTextSearch(string keyword, params Expression>[] properties) + { + if (properties == null || properties.None()) + throw new ArgumentNullException(nameof(properties)); + var propertyNames = new List(); + foreach (var property in properties) + { + var propertyExpression = (property.Body as UnaryExpression)?.Operand as MemberExpression; + if (propertyExpression == null) + throw new Exception($"Unsupported FullTextSearch expression. The only supported format is \"() => x.Property\". You provided: {property}"); + propertyNames.Add(propertyExpression.Member.Name); + } + + return FullTextSearch(keyword, propertyNames.ToArray()); + } + + public static FullTextSearchQueryOption FullTextSearch(string keyword, params Expression>[] properties) + { + if (properties == null || properties.None()) + throw new ArgumentNullException(nameof(properties)); + var propertyNames = new List(); + foreach (var property in properties) + { + var propertyExpression = (property.Body as UnaryExpression)?.Operand as MemberExpression; + if (propertyExpression == null || !(propertyExpression.Expression is ParameterExpression)) + throw new Exception($"Unsupported OrderBy expression. The only supported format is \"x => x.Property\". You provided: {property}"); + propertyNames.Add(propertyExpression.Member.Name); + } + + return FullTextSearch(keyword, propertyNames.ToArray()); + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Database/QueryOptions/RangeQueryOption.cs b/Olive.Entities/Database/QueryOptions/RangeQueryOption.cs new file mode 100644 index 000000000..03c8ebb3a --- /dev/null +++ b/Olive.Entities/Database/QueryOptions/RangeQueryOption.cs @@ -0,0 +1,14 @@ +namespace Olive.Entities +{ + public class RangeQueryOption : QueryOption + { + /// + /// Creates a new ResultSetSizeQueryOption instance. + /// + internal RangeQueryOption() { } + + public int From { get; internal set; } + + public int Number { get; internal set; } + } +} diff --git a/Olive.Entities/Database/QueryOptions/SortQueryOption.cs b/Olive.Entities/Database/QueryOptions/SortQueryOption.cs new file mode 100644 index 000000000..2f8233773 --- /dev/null +++ b/Olive.Entities/Database/QueryOptions/SortQueryOption.cs @@ -0,0 +1,20 @@ +namespace Olive.Entities +{ + public class SortQueryOption : QueryOption + { + /// + /// Creates a new SortQueryOption instance. + /// + public SortQueryOption() { } + + /// + /// Gets or sets the Property of this SortQueryOption. + /// + public string Property { get; set; } + + /// + /// Gets or sets the Descending of this SortQueryOption. + /// + public bool Descending { get; set; } + } +} diff --git a/Olive.Entities/Database/QueryOptions/TakeTopQueryOption.cs b/Olive.Entities/Database/QueryOptions/TakeTopQueryOption.cs new file mode 100644 index 000000000..7ae913a98 --- /dev/null +++ b/Olive.Entities/Database/QueryOptions/TakeTopQueryOption.cs @@ -0,0 +1,15 @@ +namespace Olive.Entities +{ + public class TakeTopQueryOption : QueryOption + { + /// + /// Creates a new ResultSetSizeQueryOption instance. + /// + public TakeTopQueryOption(int number) => Number = number; + + /// + /// Gets or sets the Number of this ResultSetSizeQueryOption. + /// + public int Number { get; } + } +} \ No newline at end of file diff --git a/Olive.Entities/Database/QueryOptions/WhereQueryOption.cs b/Olive.Entities/Database/QueryOptions/WhereQueryOption.cs new file mode 100644 index 000000000..2b8d98952 --- /dev/null +++ b/Olive.Entities/Database/QueryOptions/WhereQueryOption.cs @@ -0,0 +1,11 @@ +namespace Olive.Entities +{ + public class WhereQueryOption : QueryOption + { + public string SqlCriteria { get; set; } + + public WhereQueryOption() { } + + public WhereQueryOption(string sqlCriteria) => SqlCriteria = sqlCriteria; + } +} diff --git a/Olive.Entities/Engine/AggregateFunction.cs b/Olive.Entities/Engine/AggregateFunction.cs new file mode 100644 index 000000000..d63174600 --- /dev/null +++ b/Olive.Entities/Engine/AggregateFunction.cs @@ -0,0 +1,10 @@ +namespace Olive.Entities +{ + public enum AggregateFunction + { + Max, + Min, + Sum, + Average + } +} \ No newline at end of file diff --git a/Olive.Entities/Engine/CriteriaExtractor.cs b/Olive.Entities/Engine/CriteriaExtractor.cs new file mode 100644 index 000000000..536023152 --- /dev/null +++ b/Olive.Entities/Engine/CriteriaExtractor.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace Olive.Entities +{ + public class CriteriaExtractor where T : IEntity + { + Expression> Criteria; + + public static IEnumerable Parse(Expression> criteria) + { + return new CriteriaExtractor { Criteria = criteria }.DoParse(); + } + + List DoParse() + { + var result = new List(); + + foreach (var ex in GetUnitExpressions((LambdaExpression)Criteria)) + { + var condition = ProcessCriteria(ex); + + if (condition == null) + throw new Exception("Failed to extract a criterion from expression: " + ex); + + result.Add(condition); + } + + return result; + } + + static IEnumerable GetUnitExpressions(LambdaExpression expression) => GetUnitExpressions(expression.Body); + + static IEnumerable GetUnitExpressions(Expression expression) + { + if (expression.NodeType == ExpressionType.AndAlso) + { + var binary = expression as BinaryExpression; + + return GetUnitExpressions(binary.Left).Concat(binary.Right); + } + + else return new[] { expression }; + } + + static bool IsSimpleParameter(Expression expression) + { + if (expression is ParameterExpression) + return true; + + if (expression is UnaryExpression && (expression.NodeType == ExpressionType.Convert)) + return true; + + return false; + } + + static string GetPropertyExpression(MemberExpression memberInfo) + { + // Handle the member: + var property = memberInfo.Member as PropertyInfo; + if (property == null) return null; + + // Fix for overriden properties: + try { property = memberInfo.Expression.Type.GetProperty(property.Name) ?? property; } + catch { } + + if (CalculatedAttribute.IsCalculated(property)) return null; + if (memberInfo.Expression.Type.IsNullable()) return property.Name; + if (!property.DeclaringType.Implements()) return null; + + // Handle the "member owner" expression: + if (IsSimpleParameter(memberInfo.Expression)) + { + if (IsForeignKey(property)) + return property.Name.TrimEnd(2); + + return property.Name; + } + else if (memberInfo.Expression is MemberExpression) + { + // The expression is itself a member of something. + + var parentProperty = GetPropertyExpression(memberInfo.Expression as MemberExpression); + if (parentProperty == null) + return null; + else + return $"{parentProperty}.{property.Name}"; + } + else return null; + } + + static bool IsForeignKey(PropertyInfo property) => + property.Name.EndsWith("Id") && (property.PropertyType == typeof(Guid) || property.PropertyType == typeof(Guid?)); + + Criterion ProcessCriteria(Expression expression) + { + if (expression is BinaryExpression binary) + { + if (binary.NodeType == ExpressionType.OrElse) + return BinaryCriterion.From(binary); + } + + return CreateCriterion(expression); + } + + internal static Criterion CreateCriterion(Expression expression) + { + if (expression is BinaryExpression) + return CreateCriterion(expression as BinaryExpression); + + if (expression is UnaryExpression) + return CreateCriterion(expression as UnaryExpression); + + if (expression is MemberExpression) + return CreateCriterion(expression as MemberExpression); + + if (expression is MethodCallExpression) + return CreateCriterion(expression as MethodCallExpression); + + return null; + } + + static Criterion CreateCriterion(BinaryExpression expression) + { + if (expression.Left is MemberExpression) + return CreateCriterion(expression, expression.Left as MemberExpression); + + else if (expression.Left is ParameterExpression) + return new Criterion("ID", ToOperator(expression.NodeType), GetExpressionValue(expression.Right)); + + else if (expression.Left is UnaryExpression) + { + var unary = expression.Left as UnaryExpression; + var member = unary.Operand as MemberExpression; + + if (member == null /*|| !unary.IsLiftedToNull*/) return null; + + return CreateCriterion(expression, member); + } + else + return null; + } + + static Criterion CreateCriterion(UnaryExpression expression) + { + if (expression.NodeType != ExpressionType.Not) return null; + + var member = expression.Operand as MemberExpression; + if (member == null) return null; + + var property = GetPropertyExpression(member); + + if (property.IsEmpty()) return null; + + return new Criterion(property, FilterFunction.Is, false); + } + + static Criterion CreateCriterion(MemberExpression expression) + { + if (expression.NodeType != ExpressionType.MemberAccess) + return null; + + var property = GetPropertyExpression(expression); + + if (property.IsEmpty()) return null; + if (property == "HasValue") + { + // Changed at 27 /10/2016, following James J's refactoring of Expression Runner: + // property = expression.Member?.Name; + + // To this: + property = (expression.Expression as MemberExpression)?.Member?.Name; + + if (property.IsEmpty()) return null; + return new Criterion(property, FilterFunction.IsNot, value: null); + } + + if ((expression.Member as PropertyInfo).PropertyType != typeof(bool)) return null; + + // Only one property level is supported: + return new Criterion(property, FilterFunction.Is, true); + } + + static Criterion CreateCriterion(MethodCallExpression expression) => Criterion.From(expression, throwOnError: false); + + static Criterion CreateCriterion(BinaryExpression expression, MemberExpression member) + { + var property = GetPropertyExpression(member); + + if (property.IsEmpty()) return null; + + var value = GetExpressionValue(expression.Right); + + return new Criterion(property, ToOperator(expression.NodeType), value); + } + + static object GetExpressionValue(Expression expression) + { + object result; + + result = ExtractExpressionValue(expression); + + if (result is Entity) + { + if ((result as IntEntity)?.IsNew == true) return null; + return ((dynamic)result).ID; + } + else return result; + } + + static object ExtractExpressionValue(Expression expression) + { + if (expression == null) return null; + + if (expression is ConstantExpression) + return (expression as ConstantExpression).Value; + + if (expression is MemberExpression) + { + var memberExpression = expression as MemberExpression; + var member = memberExpression.Member; + + if (member is PropertyInfo) + return (member as PropertyInfo).GetValue(ExtractExpressionValue(memberExpression.Expression)); + + else if (member is FieldInfo) + return (member as FieldInfo).GetValue(ExtractExpressionValue(memberExpression.Expression)); + + else + throw new Exception("The specified expression cannot be converted to SQL without compilation. Use simple data variables or properties in your lambda queries."); + } + else if (expression is MethodCallExpression) + { + var methodExpression = expression as MethodCallExpression; + var method = (expression as MethodCallExpression).Method; + + var instance = ExtractExpressionValue(methodExpression.Object); + + return method.Invoke(instance, methodExpression.Arguments.Select(a => ExtractExpressionValue(a)).ToArray()); + } + else + { + throw new Exception("The specified expression cannot be converted to SQL without compilation. Use simple data variables or properties in your lambda queries."); + } + } + + static FilterFunction ToOperator(ExpressionType type) + { + switch (type) + { + case ExpressionType.Equal: + return FilterFunction.Is; + case ExpressionType.NotEqual: + return FilterFunction.IsNot; + case ExpressionType.GreaterThan: + return FilterFunction.MoreThan; + case ExpressionType.GreaterThanOrEqual: + return FilterFunction.MoreThanOrEqual; + case ExpressionType.LessThan: + return FilterFunction.LessThan; + case ExpressionType.LessThanOrEqual: + return FilterFunction.LessThanOrEqual; + default: throw new NotSupportedException(type + " is still not supported."); + } + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Engine/EntityFinder.cs b/Olive.Entities/Engine/EntityFinder.cs new file mode 100644 index 000000000..b6833b9e0 --- /dev/null +++ b/Olive.Entities/Engine/EntityFinder.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Olive.Entities +{ + /// + /// Finds an entity with unknown type from its given id. + /// + public static class EntityFinder + { + static readonly ConcurrentDictionary> PossibleTypesCache = new ConcurrentDictionary>(); + + // static readonly ConcurrentDictionary EntityTypesCache = new ConcurrentDictionary(); + + public static IEnumerable FindPossibleTypes(Type baseType, bool mustFind) => + PossibleTypesCache.GetOrAdd(baseType, t => SearchForPossibleTypes(t, mustFind)); + + /// + /// Gets the runtime type from the currently loaded assemblies. + /// + /// The type name (including namespace, but excluding assembly). + public static Type GetEntityType(string typeFullName) => + GetDomainEntityTypes().FirstOrDefault(x => x.FullName == typeFullName); + + public static IEnumerable GetDomainEntityTypes() + { + return AppDomain.CurrentDomain.GetAssemblies() + .Where(a => a.References(typeof(Entity).Assembly)) + .SelectMany(a => a.GetExportedTypes()) + .Where(t => t.Implements()) + .Where(t => !t.Name.StartsWith("App_Code")); + } + + public static IEnumerable SearchForPossibleTypes(Type baseType, bool mustFind) + { + IEnumerable result; + + if (baseType == null || baseType == typeof(Entity)) + result = GetDomainEntityTypes(); + + else if (baseType.IsInterface) + result = + Entity.Database.AssemblyProviderFactories.Except(f => f.Value.SupportsPolymorphism()) + .Select(a => a.Key).Where(a => a.References(baseType.Assembly)).Concat(baseType.Assembly) + .SelectMany(a => a.GetExportedTypes()) + .Where(t => t.Implements(baseType)).ToList(); + + else + result = baseType.Assembly.GetExportedTypes().Where(t => t.GetParentTypes().Contains(baseType)).Union(new[] { baseType }); + + result = result + // Not transient objects: + .Where(t => !TransientEntityAttribute.IsTransient(t)) + // No abstract or interface: + .Where(t => !t.IsAbstract && !t.IsInterface) + // Unless the type is marked non-persistent: + .Where(t => PersistentAttribute.IsTypePersistent(t)) + // Leaf nodes first (most concrete): + .OrderByDescending(t => t.GetParentTypes().Count()); + + result = result.Except(new[] { typeof(IApplicationEvent) }).ToArray(); + + if (result.None()) + { + if (baseType != null && mustFind) + throw new ArgumentException($"No type in the current application domain can be the implementation of the type {baseType.FullName}."); + + else if (mustFind) + throw new ArgumentException("No type in the current application domain implements Entity."); + } + + return result; + } + + // /// + // /// Finds the actual type of an object with the specified ID which inherits from a specified base type. + // /// + // public static IEntity Find(object entityId, Type baseType) + // { + // var key = baseType + + // return EntityTypesCache.GetOrAdd( + // } + + // static IEntity DiscoverType(object entityId, Type baseType) + // { + // if (baseType != null && !baseType.Implements()) + // throw new ArgumentException(baseType.FullName + " is not acceptable as a valid entity base type."); + + // if (baseType == null && !baseType.IsA()) + // { + // throw new ArgumentException("Only Guid-based entities can be retrieved from the database by their ID only."); + // } + + // var type = FromCache(entityId); + // if (type == null) + // { + // // Find possible types: + // var possibleTypes = FindPossibleTypes(baseType, true); + + // if (possibleTypes.Count() == 1) + // { + // type = possibleTypes.Single(); + // Cache(entityId, type); + // return Database.GetConcrete(entityId, type); + // } + // else + // { + // Exception firstError = null; + + // foreach (var candidateType in possibleTypes) + // { + // Entity result; + // try { result = Database.GetConcrete(entityId, candidateType); } + // catch (Exception ex) { if (firstError == null) firstError = ex; continue; } + + // if (result != null) + // { + // Cache(entityId, result.GetType()); + // return result; + // } + // } + + // if (firstError != null) throw firstError; + // } + // } + // else return Database.GetConcrete(entityId, type); + + // var error = "Could not find the type of the entity with the ID of " + entityId; + // if (baseType != null && baseType != typeof(Entity)) + // error += ", considered to be a an implementation of " + baseType.FullName; + + // throw new ArgumentException(error + "."); + // } + + public static void ResetCache() => PossibleTypesCache.Clear(); //EntityTypesCache.Clear(); + } +} diff --git a/Olive.Entities/Engine/IDataAccess.cs b/Olive.Entities/Engine/IDataAccess.cs new file mode 100644 index 000000000..b45b19f41 --- /dev/null +++ b/Olive.Entities/Engine/IDataAccess.cs @@ -0,0 +1,28 @@ +namespace Olive.Entities +{ + using System.Collections.Generic; + using System.Data; + using System.Threading.Tasks; + + /// + /// Provides direct data access to the underlying data source. + /// + public interface IDataAccess + { + Task GetOrCreateConnection(); + + Task CreateConnection(string connectionString = null); + + Task ExecuteReader(string command, CommandType commandType = CommandType.Text, params IDataParameter[] @params); + + Task ExecuteQuery(string databaseQuery, CommandType commandType = CommandType.Text, params IDataParameter[] @params); + + Task ExecuteNonQuery(string command, CommandType commandType = CommandType.Text, params IDataParameter[] @params); + + Task ExecuteScalar(string command, CommandType commandType = CommandType.Text, params IDataParameter[] @params); + + Task ExecuteScalar(string command, CommandType commandType = CommandType.Text, params IDataParameter[] @params); + + Task ExecuteBulkNonQueries(CommandType commandType, List> commands); + } +} diff --git a/Olive.Entities/Engine/IDataProvider.cs b/Olive.Entities/Engine/IDataProvider.cs new file mode 100644 index 000000000..a18b81d3e --- /dev/null +++ b/Olive.Entities/Engine/IDataProvider.cs @@ -0,0 +1,48 @@ +namespace Olive.Entities +{ + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Threading.Tasks; + + public interface IDataProvider + { + Task Get(object objectID); + Task Save(IEntity record); + Task Delete(IEntity record); + + Task> GetList(IDatabaseQuery query); + + /// + /// Returns a direct database criterion used to eager load associated objects. + /// + DirectDatabaseCriterion GetAssociationInclusionCriteria(IDatabaseQuery query, PropertyInfo association); + + IDataAccess Access { get; } + + Task Count(IDatabaseQuery query); + + Task Aggregate(IDatabaseQuery query, AggregateFunction function, string propertyName); + + string MapColumn(string propertyName); + string MapSubquery(string path); + + /// + /// Reads the many to many relation and returns the IDs of the associated objects. + /// + Task> ReadManyToManyRelation(IEntity instance, string property); + + IDictionary> GetUpdatedValues(IEntity original, IEntity updated); + + Task ExecuteNonQuery(string command); + Task ExecuteScalar(string command); + + bool SupportValidationBypassing(); + + Task BulkInsert(IEntity[] entities, int batchSize); + Task BulkUpdate(IEntity[] entities, int batchSize); + + string ConnectionString { get; set; } + string ConnectionStringKey { get; set; } + } +} \ No newline at end of file diff --git a/Olive.Entities/Engine/IDataProviderFactory.cs b/Olive.Entities/Engine/IDataProviderFactory.cs new file mode 100644 index 000000000..afbea6935 --- /dev/null +++ b/Olive.Entities/Engine/IDataProviderFactory.cs @@ -0,0 +1,18 @@ +using System; + +namespace Olive.Entities +{ + public interface IDataProviderFactory + { + IDataProvider GetProvider(Type type); + + /// + /// Determines whether this data provider factory handles interface data queries. + /// + bool SupportsPolymorphism(); + + string ConnectionString { get; } + + IDataAccess GetAccess(); + } +} diff --git a/Olive.Entities/Entity.T.cs b/Olive.Entities/Entity.T.cs new file mode 100644 index 000000000..8d981831d --- /dev/null +++ b/Olive.Entities/Entity.T.cs @@ -0,0 +1,50 @@ +namespace Olive.Entities +{ + using System.Xml.Serialization; + using Newtonsoft.Json; + + public class Entity : Entity, IEntity + { + /// + /// Gets or sets the ID of this object. + /// + [JsonExposed] + public virtual T ID { get; set; } + + /// + /// Gets the original id of this type as it was in the database. + /// + [XmlIgnore, JsonIgnore, System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public virtual T OriginalId { get; internal set; } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() => ID.GetHashCode(); + + /// + /// Determines whether this instance is equal to another specified instance. + /// + public override bool Equals(Entity other) + { + if (ReferenceEquals(this, other)) return true; + + var typed = other as Entity; + + if (ReferenceEquals(this, null) || ReferenceEquals(typed, null)) return false; + + if (GetType() != typed.GetType()) return false; + + return ID.Equals(typed.ID); + } + + /// + /// Gets the ID of this object. + /// + [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + public override object GetId() => ID; + } +} \ No newline at end of file diff --git a/Olive.Entities/Entity.cs b/Olive.Entities/Entity.cs new file mode 100644 index 000000000..428e0fa8d --- /dev/null +++ b/Olive.Entities/Entity.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; +using Newtonsoft.Json; + +namespace Olive.Entities +{ + /// + /// Entity, a persistent object in the application. + /// + public abstract class Entity : IEntity + { + static Dictionary PrimitiveProperties = new Dictionary(); + static object PrimitivePropertiesSyncLock = new object(); + object CachedCopiesLock = new object(); + internal List CachedCopies; + public Entity _ClonedFrom; + internal bool IsImmutable; + + /// + /// Base constructor (called implicitly in all typed entity classes) to initialize an object. + /// + protected Entity() + { + IsNew = true; + IsImmutable = true; + Initialize(); + } + + /// + /// Gets the id of this entity. + /// + public abstract object GetId(); + + public override int GetHashCode() => GetId().GetHashCode(); + + #region CachedCopies + + internal void RegisterCachedCopy(ICachedReference cachedCopy) + { + if (cachedCopy == null) return; + lock (CachedCopiesLock) + { + if (CachedCopies == null) CachedCopies = new List(); + CachedCopies.Add(cachedCopy); + } + } + + /// + /// Invalidates its cached references. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual void InvalidateCachedReferences() + { + lock (CachedCopiesLock) + { + if (CachedCopies == null) CachedCopies = new List(); + else foreach (var c in CachedCopies) c.Invalidate(); + } + + _ClonedFrom?.InvalidateCachedReferences(); + } + + #endregion + + /// + /// Determines whether this is a newly created instace. This value will be True for new objects, and False for anything loaded from the database. + /// + [XmlIgnore, JsonIgnore] + public virtual bool IsNew { get; internal set; } + + /// + /// Determines whether this instance is "soft-deleted". + /// + [XmlIgnore, JsonIgnore, EditorBrowsable(EditorBrowsableState.Never)] + public virtual bool IsMarkedSoftDeleted { get; internal set; } + + /// + /// Determines whether this object is already cloned and updated in the database without this instance being updated. + /// + public bool IsStale; + + /// + /// Initializes this instance. + /// This can be overridden in the business entity types to provide "construction" logic. + /// + protected internal virtual void Initialize() { } + + #region ToString(format) + + /// + /// Gets the primitive properties of this tye. + /// + PropertyInfo[] GetPrimitiveProperties() + { + var myType = GetType(); + if (PrimitiveProperties.ContainsKey(myType)) + { + // Already cached: + return PrimitiveProperties[myType]; + } + else + { + lock (PrimitivePropertiesSyncLock) + { + if (PrimitiveProperties.ContainsKey(myType)) + return PrimitiveProperties[myType]; + var result = ExtractPrimitiveProperties(myType); + PrimitiveProperties.Add(myType, result); + return result; + } + } + } + + /// + /// Extracts the primitive properties of a specified type. + /// + static PropertyInfo[] ExtractPrimitiveProperties(Type type) + { + var result = new List(); + var primitiveTypes = new[] { typeof(string), typeof(int), typeof(int?), typeof(double), typeof(double?), typeof(DateTime), typeof(DateTime?) }; + foreach (var p in type.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.CanRead).Where(p => primitiveTypes.Contains(p.PropertyType))) + { + if (p.Name == nameof(IsNew)) continue; + if (p.PropertyType.Implements()) continue; + if (CalculatedAttribute.IsCalculated(p)) continue; + result.Add(p); + } + + return result.ToArray(); + } + + /// + /// Returns a string that contains all primitive properties of this instance. + /// This should be used normally in "full text search". + /// + public virtual string ToString(string format) + { + if (format == "F") + { + var r = new StringBuilder(); + foreach (var p in GetPrimitiveProperties()) + { + try + { + r.Append(p.GetValue(this)?.ToString() + " "); + } + catch + { + // We don't want this method to throw an exception even if some properties cannot be read. + // No logging is needed + } + } + + return r.ToString(); + } + else + return ToString(); + } + + #endregion + + /// + /// Validates the data for the properties of the current instance. + /// It throws a ValidationException if an error is detected + /// + [EditorBrowsable(EditorBrowsableState.Never)] + protected virtual Task ValidateProperties() => Task.CompletedTask; + + /// + /// Validates this instance to ensure it can be saved in a data repository. + /// If this finds an issue, it throws a ValidationException for that. + /// This calls ValidateProperties(). Override this method to provide custom validation logic in a type. + /// + public virtual Task Validate() => ValidateProperties(); + + /// + /// This even is raised just after this instance is loaded from the database. + /// + public AsyncEvent Loaded { get; } = new AsyncEvent(); + protected internal virtual Task OnLoaded() => Loaded.Raise(); + + /// + /// This event is raised just before this instance is saved in the data repository. + /// + public AsyncEvent Saving { get; } = new AsyncEvent(); + protected internal virtual Task OnSaving(CancelEventArgs e) => Saving.Raise(e); + + /// + /// This is raised just before the object is being Validated. + /// It will automatically be called in Database.Save() method before calling the Validate() method. + /// Use this to do any last-minute object modifications, such as initializing complex values. + /// + public AsyncEvent Validating { get; } = new AsyncEvent(); + protected internal virtual Task OnValidating(EventArgs e) => Validating.Raise(e); + + /// + /// This event is raised after this instance is saved in the database. + /// + public AsyncEvent Saved { get; } = new AsyncEvent(); + + /// + /// Raises the event. + /// + /// The instance containing the event data. + protected internal virtual async Task OnSaved(SaveEventArgs e) + { + InvalidateCachedReferences(); + await Saved.Raise(e); + await EntityManager.RaiseStaticOnSaved(e); + InvalidateCachedReferences(); + } + + /// + /// This event is raised just before this instance is deleted from the database. + /// + public AsyncEvent Deleting { get; } = new AsyncEvent(); + protected internal virtual Task OnDeleting(CancelEventArgs e) => Deleting.Raise(e); + + /// + /// This event is raised just after this instance is deleted from the database. + /// + public AsyncEvent Deleted { get; } = new AsyncEvent(); + + protected internal virtual async Task OnDeleted(EventArgs e) + { + InvalidateCachedReferences(); + await Deleted.Raise(); + await EntityManager.RaiseStaticOnDeleted(e); + InvalidateCachedReferences(); + } + + /// + /// Creates a shallow copy of this object. + /// If you need to update an instance loaded from the database, you must create a Clone of it before applying any changes. + /// Otherwise you will be editing the "live" instance from the cache, that is used by all other threads! + /// + public virtual IEntity Clone() + { + var result = (Entity)MemberwiseClone(); + result.IsImmutable = false; + + result._ClonedFrom = this; + return result; + } + + /// + /// Determines whether the specified object is equal to this instance. + /// + public override bool Equals(object @object) => Equals(@object as Entity); + + /// Determines whether the specified object is equal to this instance. + public abstract bool Equals(Entity @object); + + /// + /// Implements the operator ==. + /// + public static bool operator ==(Entity left, object right) + { + if (right == null) return left == null; + var rightEntity = right as Entity; + if (rightEntity == null) return false; + + return left == rightEntity; + } + + public static bool operator !=(Entity left, object right) => !(left == right); + + public static bool operator ==(Entity left, Entity right) + { + if (ReferenceEquals(left, right)) return true; + if (ReferenceEquals(left, null)) return false; + return left.Equals(right); + } + + public static bool operator !=(Entity left, Entity right) => !(left == right); + + /// + /// Compares the current instance with another object of the same type. + /// + /// An object to compare with this instance. + public virtual int CompareTo(object other) + { + if (other == null) return 1; + else return string.Compare(ToString(), other.ToString(), ignoreCase: true); + } + + #region Database instance + static IDatabase DatabaseInstance; + + public static IDatabase Database => DatabaseInstance + ?? throw new InvalidOperationException("The database instance is not initialized."); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static void InitializeDatabase(IDatabase instance) + { + DatabaseInstance = instance ?? throw new ArgumentException("Database instance cannot be null."); + } + #endregion + } +} \ No newline at end of file diff --git a/Olive.Entities/EntityManager.cs b/Olive.Entities/EntityManager.cs new file mode 100644 index 000000000..eeb269fd4 --- /dev/null +++ b/Olive.Entities/EntityManager.cs @@ -0,0 +1,266 @@ +using System; +using System.ComponentModel; +using System.Reflection; +using System.Threading.Tasks; + +namespace Olive.Entities +{ + /// + /// Provides services for Entity objects. + /// + public static class EntityManager + { + /// + /// Determines whether the specified record is immutable, or closed for changes. + /// An object marked as immutable is shared in the application cache. Therefore it must not be changed. + /// + public static bool IsImmutable(IEntity entity) + { + var item = entity as Entity; + + if (item == null) + throw new ArgumentNullException("entity must be a non-null instance inheriting from Entity."); + + return item.IsImmutable && !entity.IsNew; + } + + /// + /// Marks the specified object as immutable. + /// + public static void MarkImmutable(IEntity entity) + { + if (entity == null) + throw new ArgumentNullException(nameof(entity)); + + (entity as Entity).IsImmutable = true; + } + + #region Entity static events + /// + /// This event is raised for the whole Entity type before "any" object is saved in the database. + /// You can handle this to provide global functionality/event handling scenarios. + /// + public readonly static AsyncEvent InstanceSaving = new AsyncEvent(); + + /// + /// This event is raised for the whole Entity type after "any" object is saved in the database. + /// You can handle this to provide global functionality/event handling scenarios. + /// + public readonly static AsyncEvent InstanceSaved = new AsyncEvent(); + + /// + /// This event is raised for the whole Entity type before "any" object is deleted from the database. + /// You can handle this to provide global functionality/event handling scenarios. + /// + public readonly static AsyncEvent InstanceDeleting = new AsyncEvent(); + + /// + /// This event is raised for the whole Entity type before "any" object is validated. + /// You can handle this to provide global functionality/event handling scenarios. + /// This will be called as the first line of the base Entity's OnValidating method. + /// + public readonly static AsyncEvent InstanceValidating = new AsyncEvent(); + + /// + /// This event is raised for the whole Entity type after "any" object is deleted from the database. + /// You can handle this to provide global functionality/event handling scenarios. + /// + public readonly static AsyncEvent InstanceDeleted = new AsyncEvent(); + #endregion + + #region Raise events + + internal static Task RaiseStaticOnSaved(SaveEventArgs args) => + InstanceSaved.Raise(args); + + internal static Task RaiseStaticOnDeleted(EventArgs args) => + InstanceDeleted.Raise(args); + + public static async Task RaiseOnDeleting(IEntity record, CancelEventArgs args) + { + if (record == null) throw new ArgumentNullException(nameof(record)); + + await InstanceDeleting.Raise(args); + + if (args.Cancel) return; + + await (record as Entity).OnDeleting(args); + } + + public static async Task RaiseOnValidating(IEntity record, EventArgs args) + { + if (record == null) throw new ArgumentNullException(nameof(record)); + + await InstanceValidating.Raise(args); + + await (record as Entity).OnValidating(args); + } + + public static async Task RaiseOnDeleted(IEntity record) + { + if (record == null) + throw new ArgumentNullException(nameof(record)); + + await (record as Entity).OnDeleted(EventArgs.Empty); + } + + public static async Task RaiseOnLoaded(IEntity record) + { + if (record == null) + throw new ArgumentNullException(nameof(record)); + + await (record as Entity).OnLoaded(); + } + + public static async Task RaiseOnSaving(IEntity record, CancelEventArgs e) + { + if (record == null) throw new ArgumentNullException(nameof(record)); + + await InstanceSaving.Raise(e); + if (e.Cancel) return; + + await (record as Entity).OnSaving(e); + } + + public static async Task RaiseOnSaved(IEntity record, SaveEventArgs e) + { + if (record == null) + throw new ArgumentNullException(nameof(record)); + + await (record as Entity).OnSaved(e); + } + + #endregion + + /// + /// Sets the state of an entity instance to saved. + /// + public static void SetSaved(IEntity entity, bool saved = true) + { + (entity as Entity).IsNew = !saved; + + entity.GetType().GetProperty("OriginalId").SetValue(entity, entity.GetId()); + } + + /// + /// Creates a new clone of an entity. This will work in a polymorphic way. + /// + public static T CloneAsNew(T entity) where T : Entity => CloneAsNew(entity, null); + + /// + /// Creates a new clone of an entity. This will work in a polymorphic way. + /// + public static T CloneAsNew(T entity, Action changes) where T : Entity + { + var result = (T)entity.Clone(); + result.IsNew = true; + + if (result is GuidEntity) (result as GuidEntity).ID = GuidEntity.NewGuidGenerator(result.GetType()); + // TODO: the following line need to be reviewed and fixed. + // if (result is IntEntity) (result as IntEntity).ID = IntEntity.NewIdGenerator(result.GetType()); + + // Setting the value of AutoNumber properties to zero + foreach (var propertyInfo in result.GetType().GetProperties()) + if (AutoNumberAttribute.IsAutoNumber(propertyInfo)) + propertyInfo.SetValue(result, 0); + + result.Initialize(); + + // Re attach Documents: + changes?.Invoke(result); + + return result; + } + + /// + /// Sets the ID of an object explicitly. + /// + public static void RestsetOriginalId(IEntity entity) + { + if (entity == null) + throw new ArgumentNullException(nameof(entity)); + + ((dynamic)entity).OriginalId = entity.ID; + } + + public static void SetSaved(IEntity entity, T id) + { + ((dynamic)entity).IsNew = false; + + entity.ID = id; + RestsetOriginalId(entity); + } + + /// + /// Read the value of a specified property from a specified object. + /// + public static object ReadProperty(object @object, string propertyName) + { + if (@object == null) + throw new ArgumentNullException(nameof(@object)); + + var property = FindProperty(@object.GetType(), propertyName); + + try + { + return property.GetValue(@object, null); + } + catch (Exception ex) + { + throw new Exception($"Could not read the value of the property {propertyName } from the given {@object.GetType().FullName} object.", ex); + } + } + + public static PropertyInfo FindProperty(Type type, string propertyName) + { + if (propertyName.IsEmpty()) throw new ArgumentNullException(nameof(propertyName)); + + var result = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); + + if (result == null) // Try inherited properties. + result = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public); + + if (result == null) throw new ArgumentException($"{type} does not have a property named {propertyName}"); + + return result; + } + + public static void WriteProperty(object @object, string propertyName, object value) + { + if (@object == null) + throw new ArgumentNullException(nameof(@object)); + + var property = FindProperty(@object.GetType(), propertyName); + + try + { + property.SetValue(@object, value, null); + } + catch (Exception ex) + { + throw new ArgumentException($"Could not set the value of the property {propertyName} from the given {@object.GetType().FullName} object.", ex); + } + } + + public static bool IsSoftDeleted(Entity entity) + { + if (entity == null) throw new ArgumentNullException(nameof(entity)); + + return entity.IsMarkedSoftDeleted; + } + + public static void MarkSoftDeleted(Entity entity) + { + if (entity == null) throw new ArgumentNullException(nameof(entity)); + + entity.IsMarkedSoftDeleted = true; + } + + public static void UnMarkSoftDeleted(Entity entity) + { + if (entity == null) throw new ArgumentNullException(nameof(entity)); + + entity.IsMarkedSoftDeleted = false; + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Extensions/@Misc.cs b/Olive.Entities/Extensions/@Misc.cs new file mode 100644 index 000000000..367e09295 --- /dev/null +++ b/Olive.Entities/Extensions/@Misc.cs @@ -0,0 +1,25 @@ +using System; + +namespace Olive.Entities +{ + public static partial class OliveExtensions + { + /// + /// Gets the root entity type of this type. + /// If this type inherits directly from Entity<T> then it will be returned, otherwise its parent... + /// + public static Type GetRootEntityType(this Type objectType) + { + var baseType = objectType.BaseType; + if (baseType == null) + throw new NotSupportedException(objectType.FullName + " not recognised. It must be a subclass of Olive.Entities.Entity."); + + if (baseType.Name == "GuidEntity") return objectType; + if (baseType == typeof(Entity)) return objectType; + if (baseType == typeof(Entity)) return objectType; + if (baseType == typeof(Entity)) return objectType; + + return GetRootEntityType(baseType); + } + } +} diff --git a/Olive.Entities/Extensions/Entity.cs b/Olive.Entities/Extensions/Entity.cs new file mode 100644 index 000000000..6549853ce --- /dev/null +++ b/Olive.Entities/Extensions/Entity.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Olive.Entities +{ + partial class OliveExtensions + { + /// + /// Determines if this item is in a specified list of specified items. + /// + public static bool IsAnyOf(this T item, params T[] options) where T : IEntity + { + if (item == null) return options.Contains(default(T)); + + return options.Contains(item); + } + + /// + /// Determines if this item is in a specified list of specified items. + /// + public static bool IsAnyOf(this T item, IEnumerable options) where T : IEntity + { + return options.Contains(item); + } + + /// + /// Determines if this item is none of a list of specified items. + /// + public static bool IsNoneOf(this T item, params T[] options) where T : IEntity + { + if (item == null) return !options.Contains(default(T)); + + return !options.Contains(item); + } + + /// + /// Determines if this item is none of a list of specified items. + /// + public static bool IsNoneOf(this T item, IEnumerable options) where T : IEntity + { + if (item == null) return !options.Contains(default(T)); + + return !options.Contains(item); + } + + /// + /// Clones all items of this collection. + /// + public static List CloneAll(this IEnumerable list) where T : IEntity + { + return list.Select(i => (T)i.Clone()).ToList(); + } + + /// + /// Determines whether this blob is an image. + /// + public static bool IsImage(this Blob doc) + { + throw new NotImplementedException(); + // if (doc.IsEmpty()) return false; + + // try + // { + // using (System.Drawing.Imaging.BitmapHelper.FromBuffer(doc.FileData)) + // { + // return true; + // } + // } + // catch + // { + // return false; + // } + } + + /// + /// Gets the id of this entity. + /// + public static string GetFullIdentifierString(this IEntity entity) + { + if (entity == null) return null; + + return entity.GetType().GetRootEntityType().FullName + "/" + entity.GetId(); + } + + /// + /// Validates all entities in this collection. + /// + /// Entity type + /// The entities. + public static Task ValidateAll(this IEnumerable entities) where T : Entity + { + return Task.WhenAll(entities.Select(x => x.Validate())); + } + + /// + /// Returns this Entity only if the given predicate evaluates to true and this is not null. + /// + public static T OnlyWhen(this T entity, Func criteria) where T : Entity + { + return entity != null && criteria(entity) ? entity : null; + } + + /// + /// Returns all entity Guid IDs for this collection. + /// + public static IEnumerable IDs(this IEnumerable> entities) + { + return entities.Select(entity => entity.ID); + } + + public static T On(this T @this, Func @event, Func handler, + [CallerFilePath] string callerFile = null, + [CallerLineNumber] int callerLine = 0) + where T : Entity + { + @event(@this).Handle(handler, callerFile, callerLine); + return @this; + } + + public static T On(this T @this, Func @event, Action handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int callerLine = 0) + where T : Entity + { + @event(@this).Handle(handler, callerFile, callerLine); + return @this; + } + + public static T On(this T @this, Func> @event, Func handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int callerLine = 0) + where T : Entity + { + @event(@this).Handle(handler, callerFile, callerLine); + return @this; + } + + public static T On(this T @this, Func> @event, Func handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int callerLine = 0) + where T : Entity + { + @event(@this).Handle(handler, callerFile, callerLine); + return @this; + } + + public static T On(this T @this, Func> @event, Action handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int callerLine = 0) + where T : Entity + { + @event(@this).Handle(handler, callerFile, callerLine); + return @this; + } + + public static T On(this T @this, Func> @event, Action handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int callerLine = 0) + where T : Entity + { + @event(@this).Handle(handler, callerFile, callerLine); + return @this; + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Extensions/Guid.cs b/Olive.Entities/Extensions/Guid.cs new file mode 100644 index 000000000..9f1a000ff --- /dev/null +++ b/Olive.Entities/Extensions/Guid.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; + +namespace Olive.Entities +{ + partial class OliveExtensions + { + /// + /// This will use Database.Get() to load the specified entity type with this ID. + /// + public static async Task To(this Guid? guid) where T : IEntity + { + if (guid == null) return default(T); + + return await guid.Value.To(); + } + + /// + /// This will use Database.Get() to load the specified entity type with this ID. + /// + public static async Task To(this Guid guid) where T : IEntity + { + if (guid == Guid.Empty) return default(T); + + return await Entity.Database.Get(guid); + } + } +} \ No newline at end of file diff --git a/Olive.Entities/Extensions/IHierarchy.cs b/Olive.Entities/Extensions/IHierarchy.cs new file mode 100644 index 000000000..84c1753d2 --- /dev/null +++ b/Olive.Entities/Extensions/IHierarchy.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Olive.Entities +{ + partial class OliveExtensions + { /// + /// Gets the full path of this hirarchical entity, seperated by " > ". + /// + public static string GetFullPath(this IHierarchy node) => node.GetFullPath(" > "); + + /// + /// Gets whether this node is a root hierarchy node. + /// + public static bool IsRootNode(this IHierarchy node) => node.GetParent() == null; + + /// + /// Gets the full path of this hirarchical entity, seperated by a specified seperation string. + /// + public static string GetFullPath(this IHierarchy hierarchy, string seperator) + { + if (hierarchy == null) return null; + if (hierarchy.GetParent() == null || hierarchy.GetParent() == hierarchy) + return hierarchy.Name; + else return hierarchy.GetParent().GetFullPath(seperator) + seperator + hierarchy.Name; + } + + /// + /// Gets this node as well as all its children hierarchy. + /// + public static IEnumerable WithAllChildren(this IHierarchy parent) => + parent.GetAllChildren().Concat(parent).OrderBy(i => i.GetFullPath()).ToArray(); + + /// + /// Gets all children hierarchy of this node. + /// + public static IEnumerable GetAllChildren(this IHierarchy parent) => + parent.GetChildren().Except(parent).SelectMany(c => c.WithAllChildren()).OrderBy(i => i.GetFullPath()).ToArray(); + + /// + /// Gets this node as well as all its parents hierarchy. + /// + public static IEnumerable WithAllParents(this IHierarchy child) => + child.GetAllParents().Concat(child).OrderBy(i => i.GetFullPath()).ToArray(); + + /// + /// Gets all parents hierarchy of this node. + /// + public static IEnumerable GetAllParents(this IHierarchy child) + { + var parent = child.GetParent(); + + if (parent == null || parent == child) return new IHierarchy[0]; + else return parent.WithAllParents().OrderBy(i => i.GetFullPath()).ToArray(); + } + + /// + /// Gets this node as well as all its parents hierarchy. + /// + public static IEnumerable WithAllParents(this T child) where T : IHierarchy => + (child as IHierarchy).WithAllParents().Cast().ToArray(); + + /// + /// Gets all parents hierarchy of this node. + /// + public static IEnumerable GetAllParents(this IHierarchy child) where T : IHierarchy => + (child as IHierarchy).GetAllParents().Cast().ToArray(); + } +} \ No newline at end of file diff --git a/Olive.Entities/GuidEntity.cs b/Olive.Entities/GuidEntity.cs new file mode 100644 index 000000000..7b6865c60 --- /dev/null +++ b/Olive.Entities/GuidEntity.cs @@ -0,0 +1,75 @@ +using System; +using System.ComponentModel; + +namespace Olive.Entities +{ + /// Represents a base Entity with ID of type Guid. + public abstract class GuidEntity : Entity, IEntity + { + bool IsIdLoaded; // For performance, this is used instead of Nullable + Guid id; + + [EditorBrowsable(EditorBrowsableState.Never)] + public static Func NewGuidGenerator = t => Guid.NewGuid(); + + /// + /// Gets a unique Identifier for this instance. In the database, this will be the primary key of this object. + /// + [JsonExposed] + public override Guid ID + { + get + { + if (IsIdLoaded) return id; + else + { + id = NewGuidGenerator(GetType()); + IsIdLoaded = true; + return id; + } + } + set + { + id = value; + IsIdLoaded = true; + } + } + + /// + /// Creates a shallow copy of this object. + /// If you need to update an instance loaded from the database, you must create a Clone of it before applying any changes. + /// Otherwise you will be editing the "live" instance from the cache, that is used by all other threads! + /// + public override IEntity Clone() + { + var result = base.Clone() as GuidEntity; + + // This is needed to avoid the problem caused by lazy loading of ID value. + result.ID = ID; + + return result; + } + + public static bool operator !=(GuidEntity entity, Guid? id) => entity?.ID != id; + + public static bool operator ==(GuidEntity entity, Guid? id) => entity?.ID == id; + + public static bool operator !=(GuidEntity entity, Guid id) => entity?.ID != id; + + public static bool operator ==(GuidEntity entity, Guid id) => entity?.ID == id; + + public static bool operator !=(Guid? id, GuidEntity entity) => entity?.ID != id; + + public static bool operator ==(Guid? id, GuidEntity entity) => entity?.ID == id; + + public static bool operator !=(Guid id, GuidEntity entity) => entity?.ID != id; + + public static bool operator ==(Guid id, GuidEntity entity) => entity?.ID == id; + + public override bool Equals(Entity other) => ID == (other as GuidEntity)?.ID; + + public override bool Equals(object other) => ID == (other as GuidEntity)?.ID; + + public override int GetHashCode() => ID.GetHashCode(); + } +} \ No newline at end of file diff --git a/Olive.Entities/IEntity.cs b/Olive.Entities/IEntity.cs new file mode 100644 index 000000000..299d44f77 --- /dev/null +++ b/Olive.Entities/IEntity.cs @@ -0,0 +1,50 @@ +using System; +using System.ComponentModel; +using System.Threading.Tasks; + +namespace Olive.Entities +{ + /// + /// Represents an M# Entity. + /// + public interface IEntity : IComparable + { + /// + /// Determines whether this object has just been instantiated as a new object, or represent an already persisted instance. + /// + bool IsNew { get; } + + /// + /// Validates this instance and throws ValidationException if necessary. + /// + Task Validate(); + + /// + /// Gets the id of this entity. + /// + object GetId(); + + /// + /// Creates a new object that is a copy of the current instance. + /// + /// A new object that is a copy of this instance. + IEntity Clone(); + + /// + /// Invalidates all its cached referencers. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + void InvalidateCachedReferences(); + } + + /// + /// A persistent object in the application. + /// + public interface IEntity : IEntity + { + /// + /// Gets the ID. + /// + T ID { get; set; } + } +} \ No newline at end of file diff --git a/Olive.Entities/IHierarchy.cs b/Olive.Entities/IHierarchy.cs new file mode 100644 index 000000000..fde472b83 --- /dev/null +++ b/Olive.Entities/IHierarchy.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Olive.Entities +{ + public interface IHierarchy : IEntity + { + IHierarchy GetParent(); + + IEnumerable GetChildren(); + + string Name { get; } + } +} diff --git a/Olive.Entities/IntEntity.cs b/Olive.Entities/IntEntity.cs new file mode 100644 index 000000000..228ebce39 --- /dev/null +++ b/Olive.Entities/IntEntity.cs @@ -0,0 +1,96 @@ +namespace Olive.Entities +{ + using System; + using System.Collections.Concurrent; + using System.ComponentModel; + using System.Linq; + using System.Threading.Tasks; + + public class IntEntity : Entity + { + // bool IsIdLoaded = false; + int id; + + [EditorBrowsable(EditorBrowsableState.Never)] + public static Func> NewIdGenerator = DefaultNewIdGenerator; + + // TODO: The ID property need to be reviewed and fixed. + /// + /// Gets a unique Identifier for this instance. In the database, this will be the primary key of this object. + /// + public override int ID + { + get + { + if (IsNew) + throw new InvalidOperationException($"ID is not avialable for instances of '{GetType().Name}' before being saved to the database."); + + return id; + // if (IsIdLoaded) return id; + // else + // { + // if (GetType().Defines(inherit: true)) + // throw new InvalidOperationException($"ID is not avialable for instances of '{GetType().Name}' before being saved to the database."); + + // id = NewIdGenerator(GetType()); + // IsIdLoaded = true; + // return id; + // } + } + set + { + if (IsNew) + throw new InvalidOperationException($"ID is not avialable for instances of '{GetType().Name}' before being saved to the database."); + + id = value; + // IsIdLoaded = true; + } + } + + static ConcurrentDictionary LastUsedIds = new ConcurrentDictionary(); + + static async Task DefaultNewIdGenerator(Type type) + { + // One generator per hierarchy + if (type.BaseType != typeof(IntEntity)) + return await DefaultNewIdGenerator(type.BaseType); + + var initialize = (Func>)(async (t) => + { + if (TransientEntityAttribute.IsTransient(t)) return 1; + + var biggestId = ((await Database.Of(t).OrderBy("ID", descending: true) + .Top(1).GetList()).FirstOrDefault()?.GetId()); + + if (biggestId != null) return 1 + (int)biggestId; + else return 1; + }); + + var value = await initialize(type); + + return LastUsedIds.AddOrUpdate(type, value, (t, old) => old + 1); + } + + public static bool operator !=(IntEntity entity, int? id) => entity?.ID != id; + + public static bool operator ==(IntEntity entity, int? id) => entity?.ID == id; + + public static bool operator !=(IntEntity entity, int id) => entity?.ID != id; + + public static bool operator ==(IntEntity entity, int id) => entity?.ID == id; + + public static bool operator !=(int? id, IntEntity entity) => entity?.ID != id; + + public static bool operator ==(int? id, IntEntity entity) => entity?.ID == id; + + public static bool operator !=(int id, IntEntity entity) => entity?.ID != id; + + public static bool operator ==(int id, IntEntity entity) => entity?.ID == id; + + public override bool Equals(Entity other) => GetType() == other?.GetType() && ID == (other as IntEntity)?.ID; + + public override bool Equals(object other) => Equals(other as Entity); + + public override int GetHashCode() => ID.GetHashCode(); + } +} \ No newline at end of file diff --git a/Olive.Entities/Olive.Entities.csproj b/Olive.Entities/Olive.Entities.csproj new file mode 100644 index 000000000..167230aa3 --- /dev/null +++ b/Olive.Entities/Olive.Entities.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.0 + Olive.Entities + Olive.Entities + + + + ..\@Assemblies\ + ..\@Assemblies\netcoreapp2.0\Olive.Entities.xml + 1701;1702;1705;1591;1573 + + + + + + + \ No newline at end of file diff --git a/Olive.Entities/Package.nuspec b/Olive.Entities/Package.nuspec new file mode 100644 index 000000000..9ac092974 --- /dev/null +++ b/Olive.Entities/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Entities + 1.0.4 + Olive Entities + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Olive.Entities/SaveBehaviour.cs b/Olive.Entities/SaveBehaviour.cs new file mode 100644 index 000000000..6643a6895 --- /dev/null +++ b/Olive.Entities/SaveBehaviour.cs @@ -0,0 +1,25 @@ +using System; + +namespace Olive.Entities +{ + [Flags] + public enum SaveBehaviour + { + Default = 1, + BypassValidation = 2, + BypassSaving = 4, + BypassSaved = 8, + BypassLogging = 16, + BypassAll = 30, + } + + [Flags] + public enum DeleteBehaviour + { + Default = 1, + BypassDeleting = 2, + BypassDeleted = 4, + BypassLogging = 8, + BypassAll = 14, + } +} diff --git a/Olive.Entities/SaveEventArgs.cs b/Olive.Entities/SaveEventArgs.cs new file mode 100644 index 000000000..e29de84c2 --- /dev/null +++ b/Olive.Entities/SaveEventArgs.cs @@ -0,0 +1,12 @@ +using System; + +namespace Olive.Entities +{ + public class SaveEventArgs : EventArgs + { + public SaveEventArgs(SaveMode mode) { Mode = mode; } + public SaveMode Mode { get; private set; } + } + + public enum SaveMode { Update, Insert } +} \ No newline at end of file diff --git a/Olive.Entities/Sorting/ISortable.cs b/Olive.Entities/Sorting/ISortable.cs new file mode 100644 index 000000000..9c0d2c7e7 --- /dev/null +++ b/Olive.Entities/Sorting/ISortable.cs @@ -0,0 +1,10 @@ +namespace Olive.Entities +{ + /// + /// Represents a sortable entity type. + /// + public interface ISortable : IEntity + { + int Order { get; set; } + } +} diff --git a/Olive.Entities/Sorting/Sorter.cs b/Olive.Entities/Sorting/Sorter.cs new file mode 100644 index 000000000..5e1a86cb3 --- /dev/null +++ b/Olive.Entities/Sorting/Sorter.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Olive.Entities +{ + /// + /// Provides Sorting services for all entities. + /// + public static class Sorter + { + public const int INCREMENT = 10; + + static AsyncLock AsyncLock = new AsyncLock(); + + public static async Task FindItemAbove(ISortable item) => + (await FindSiblings(item)).Except(item).Where(o => o.Order <= item.Order).WithMax(o => o.Order); + + public static async Task FindItemBelow(ISortable item) => + (await FindSiblings(item)).Except(item).Where(i => i.Order >= item.Order).WithMin(i => i.Order); + + public static bool CanMoveUp(ISortable item) => FindItemAbove(item) != null; + + public static bool CanMoveDown(ISortable item) => FindItemBelow(item) != null; + + /// + /// Moves this item before a specified other item. If null is specified, it will be moved to the end of its siblings. + /// + public static async Task MoveBefore(ISortable item, ISortable before, SaveBehaviour saveBehaviour = SaveBehaviour.Default) + { + var newOrder = (before == null ? int.MaxValue : before.Order) - 1; + + if (newOrder < 0) newOrder = 0; + + item = await Entity.Database.Update(item, o => o.Order = newOrder, saveBehaviour); + + await JustifyOrders(item, saveBehaviour); + } + + /// + /// Moves this item after a specified other item. If null is specified, it will be moved to the beginning of its siblings. + /// + public static async Task MoveAfter(ISortable item, ISortable after, SaveBehaviour saveBehaviour = SaveBehaviour.Default) + { + var newOrder = (after == null ? 0 : after.Order) + 1; + + item = await Entity.Database.Update(item, o => o.Order = newOrder, saveBehaviour); + + await JustifyOrders(item, saveBehaviour); + } + + /// + /// Moves an item up among its siblings. Returns False if the item is already first in the list, otherwise true. + /// + public static async Task MoveUp(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) + { + using (await AsyncLock.Lock()) + { + var above = await FindItemAbove(item); + + if (above == null) return false; + + if (above.Order == item.Order) above.Order--; + + await Swap(item, above, saveBehaviour); + + item = await Entity.Database.Reload(item); + above = await Entity.Database.Reload(above); + + await JustifyOrders(item, saveBehaviour); + + return true; + } + } + + /// + /// Moves an item up to first among its siblings. Returns False if the item is already first in the list, otherwise true. + /// + public static async Task MoveFirst(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) + { + using (await AsyncLock.Lock()) + { + var first = (await FindSiblings(item)).Min(o => o.Order); + + if (first <= 0) return false; + + await Entity.Database.Update(item, o => o.Order = first - 1, saveBehaviour); + await JustifyOrders(item, saveBehaviour); + return true; + } + } + + /// + /// Moves an item up to last among its siblings. Always returns true. + /// + public static async Task MoveLast(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) + { + using (await AsyncLock.Lock()) + { + var last = (await FindSiblings(item)).Max(o => o.Order); + + await Entity.Database.Update(item, o => o.Order = last + 1, saveBehaviour); + await JustifyOrders(item, saveBehaviour); + return true; + } + } + + /// + /// Moves an item down among its siblings. Returns False if the item is already last in the list, otherwise true. + /// + public static async Task MoveDown(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) + { + using (await AsyncLock.Lock()) + { + var below = await FindItemBelow(item); + + if (below == null) return false; + + if (below.Order == item.Order) item.Order++; + + await Swap(item, below, saveBehaviour); + + await JustifyOrders(item, saveBehaviour); + + return true; + } + } + + /// + /// Swaps the order of two specified items. + /// + static async Task Swap(ISortable one, ISortable two, SaveBehaviour saveBehaviour) + { + var somethingAboveAll = (await FindSiblings(one)).Max(i => i.Order) + 20; + + await Entity.Database.EnlistOrCreateTransaction(async () => + { + var order1 = two.Order; + var order2 = one.Order; + + await Entity.Database.Update(one, i => i.Order = order1, saveBehaviour); + await Entity.Database.Update(two, i => i.Order = order2, saveBehaviour); + }); + } + + /// + /// Justifies the order of a specified item and its siblings. + /// The value of the "Order" property in those objects will be 10, 20, 30, ... + /// + public static async Task JustifyOrders(ISortable item, SaveBehaviour saveBehaviour = SaveBehaviour.Default) + { + using (await AsyncLock.Lock()) + { + var changed = new List(); + + var order = 0; + + foreach (var sibling in (await FindSiblings(item)).OrderBy(i => i.Order).Distinct().ToArray()) + { + order += INCREMENT; + if (sibling.Order == order) continue; + + var clone = sibling.Clone() as Entity; + (clone as ISortable).Order = order; + changed.Add(clone); + } + + await Entity.Database.Save(changed, saveBehaviour); + } + } + + /// + /// Discovers the siblings of the specified sortable object. + /// + static async Task> FindSiblings(ISortable item) + { + var getSiblingsMethod = item.GetType().GetMethod("GetSiblings", BindingFlags.Public | BindingFlags.Instance); + + bool isAcceptable = true; + + if (getSiblingsMethod == null) isAcceptable = false; + else if (getSiblingsMethod.GetParameters().Any()) isAcceptable = false; + else if (!getSiblingsMethod.ReturnType.Implements(typeof(IEnumerable))) isAcceptable = false; + + IEnumerable result; + + if (!isAcceptable) + { + result = (await Entity.Database.Of(item.GetType()).GetList()).Cast(); + } + else + { + var list = new List(); + + try + { + foreach (ISortable element in getSiblingsMethod.Invoke(item, null) as IEnumerable) + list.Add(element); + } + catch (Exception ex) + { + throw new Exception($"Services.Sorter Could not process the GetSiblings method from the {item.GetType().Name} instance.", ex); + } + + result = list; + } + + return result.OrderBy(i => i.Order).ToList(); + } + + /// + /// Gets the Next order for an ISortable entity. + /// The result will be 10 plus the largest order of its siblings. + /// + public static async Task GetNewOrder(ISortable item) + { + using (await AsyncLock.Lock()) + { + if (!item.IsNew) + throw new ArgumentException("Sorter.GetNewOrder() method needs a new ISortable argument (with IsNew set to true).", nameof(item)); + + return INCREMENT + ((await FindSiblings(item)).LastOrDefault()?.Order ?? 0); + } + } + } +} \ No newline at end of file diff --git a/Olive.Entities/ValidationException.cs b/Olive.Entities/ValidationException.cs new file mode 100644 index 000000000..cf3b726aa --- /dev/null +++ b/Olive.Entities/ValidationException.cs @@ -0,0 +1,16 @@ +namespace Olive.Entities +{ + using System; + + public class ValidationException : Exception + { + public ValidationException() { } + public ValidationException(string messageFormat, params object[] arguments) : base(string.Format(messageFormat, arguments)) { } + public ValidationException(string message) : base(message) { } + public ValidationException(string message, Exception inner) : base(message, inner) { } + + public string InvalidPropertyName { get; set; } + + public bool IsMessageTranslated { get; set; } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Attributes/BaseCustomModelBindAttribute.cs b/Olive.Mvc/Attributes/BaseCustomModelBindAttribute.cs new file mode 100644 index 000000000..ccc13d048 --- /dev/null +++ b/Olive.Mvc/Attributes/BaseCustomModelBindAttribute.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Olive.Mvc +{ + /// + /// Any method attributed by this which takes one IViewModel parameter, will be automatically + /// called when its view model parameter object is being bound. + /// + [AttributeUsage(AttributeTargets.Method)] + public abstract class BaseModelBindAttribute : Attribute + { + protected static IEnumerable DiscoverBindMethods(ControllerContext cContext, object model, Type attributeType, + ConcurrentDictionary> cache) + { + var customBinders = cache.GetOrAdd(model.GetType(), + + t => t.Assembly.GetTypes().Where(x => x.IsA()).SelectMany(x => x.GetMethods()) + .Where(m => m.GetCustomAttributes(attributeType).Any()) + .Where(m => m.GetParameters().IsSingle() && m.GetParameters().First().ParameterType == t) + .ToList() + ); + + throw new NotImplementedException("The following code is commented to fix on the test time."); + // foreach (var customBinder in customBinders) + // { + // ControllerBase controller; + + // if (cContext.Controller.GetType().IsA(customBinder.DeclaringType)) + // controller = cContext.Controller; + // else + // { + // controller = (ControllerBase)customBinder.DeclaringType.CreateInstance(); + // (controller as Controller).Url = new UrlHelper(cContext.RequestContext); + // controller.ControllerContext = cContext; + // } + + // Action invokeMethod = () => + // { + // try { customBinder.Invoke(controller, new[] { model }); } + // catch (Exception ex) + // { + // throw new Exception($"Error in calling the binding method of {customBinder.DeclaringType.Name}.{customBinder.Name}({model.GetType().Name}).", ex); + // } + // }; + + // yield return invokeMethod; + // } + } + + protected static void Enqueue(ControllerContext cContext, object model, string actionsKey, Type attributeType, + ConcurrentDictionary> cache) + { + var queue = cContext.HttpContext.Items[actionsKey] as List; + if (queue == null) + { + queue = new List(); + cContext.HttpContext.Items[actionsKey] = queue; + } + + foreach (var action in DiscoverBindMethods(cContext, model, attributeType, cache)) + queue.Insert(0, action); + } + + protected static void SetRoot(ModelBindingContext model, string rootKey) + { + if (model.HttpContext.Items[rootKey] == null) model.HttpContext.Items[rootKey] = model; + } + + protected static void InvokeAllForRoot(ModelBindingContext model, string rootKey, string actionsKey) + { + var httpContext = model.HttpContext; + + // Don't invoke if it's not for the root item. + if (!ReferenceEquals(httpContext.Items[rootKey], model)) return; + + var actions = httpContext.Items[actionsKey] as List; + + httpContext.Items[rootKey] = null; + httpContext.Items[actionsKey] = null; + + if (actions == null) return; + + foreach (var action in actions.ToArray()) action(); + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Attributes/CopyDataAttribute.cs b/Olive.Mvc/Attributes/CopyDataAttribute.cs new file mode 100644 index 000000000..12bb4a189 --- /dev/null +++ b/Olive.Mvc/Attributes/CopyDataAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Olive.Mvc +{ + [AttributeUsage(AttributeTargets.Property)] + public class CopyDataAttribute : Attribute + { + public bool CanCopy; + + public CopyDataAttribute(bool canCopy) { CanCopy = canCopy; } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Attributes/CustomBoundAttribute.cs b/Olive.Mvc/Attributes/CustomBoundAttribute.cs new file mode 100644 index 000000000..264121557 --- /dev/null +++ b/Olive.Mvc/Attributes/CustomBoundAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Olive.Mvc +{ + [AttributeUsage(AttributeTargets.Property)] + public sealed class CustomBoundAttribute : Attribute + { + } +} diff --git a/Olive.Mvc/Attributes/HasDefaultAttribute.cs b/Olive.Mvc/Attributes/HasDefaultAttribute.cs new file mode 100644 index 000000000..084c46ae3 --- /dev/null +++ b/Olive.Mvc/Attributes/HasDefaultAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Olive.Mvc +{ + /// + /// Specifies that a ViewModel field has explicit default value that should be loaded for editing a new object. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class HasDefaultAttribute : Attribute { } +} diff --git a/Olive.Mvc/Attributes/KeepWhiteSpaceAttribute.cs b/Olive.Mvc/Attributes/KeepWhiteSpaceAttribute.cs new file mode 100644 index 000000000..b54aff6f1 --- /dev/null +++ b/Olive.Mvc/Attributes/KeepWhiteSpaceAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Olive.Mvc +{ + /// + /// Specifies that a string property should not be trimmed. + /// + [AttributeUsage(AttributeTargets.Property)] + public sealed class KeepWhiteSpaceAttribute : Attribute { } +} diff --git a/Olive.Mvc/Attributes/LocalizedDateAttribute.cs b/Olive.Mvc/Attributes/LocalizedDateAttribute.cs new file mode 100644 index 000000000..2ea48cc8e --- /dev/null +++ b/Olive.Mvc/Attributes/LocalizedDateAttribute.cs @@ -0,0 +1,7 @@ +using System; + +namespace Olive.Mvc +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class LocalizedDateAttribute : Attribute { } +} \ No newline at end of file diff --git a/Olive.Mvc/Attributes/MasterDetailsAttribute.cs b/Olive.Mvc/Attributes/MasterDetailsAttribute.cs new file mode 100644 index 000000000..94d507460 --- /dev/null +++ b/Olive.Mvc/Attributes/MasterDetailsAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Olive.Mvc +{ + [AttributeUsage(AttributeTargets.Property)] + public sealed class MasterDetailsAttribute : Attribute + { + public string Prefix { get; set; } + + public MasterDetailsAttribute(string prefix) => Prefix = prefix; + } +} \ No newline at end of file diff --git a/Olive.Mvc/Attributes/OnBoundAttribute.cs b/Olive.Mvc/Attributes/OnBoundAttribute.cs new file mode 100644 index 000000000..3677566f1 --- /dev/null +++ b/Olive.Mvc/Attributes/OnBoundAttribute.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Olive.Mvc +{ + /// + /// Any method attributed by this which takes one IViewModel parameter, will be automatically called when its view model parameter object is being bound. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class OnBoundAttribute : BaseModelBindAttribute + { + static ConcurrentDictionary> CustomBindMethods = new ConcurrentDictionary>(); + + const string ROOT = "_MSharp.CustomBindActions.RootBinder"; + const string ACTIONS = "_MSharp.CustomBindActions"; + + internal static void Enqueue(ControllerContext cContext, object model) => + Enqueue(cContext, model, ACTIONS, typeof(OnBoundAttribute), CustomBindMethods); + + internal static void SetRoot(ModelBindingContext model) => SetRoot(model, ROOT); + + internal static void InvokeAllForRoot(ModelBindingContext model) => + InvokeAllForRoot(model, ROOT, ACTIONS); + } +} \ No newline at end of file diff --git a/Olive.Mvc/Attributes/OnPreBindingAttribute.cs b/Olive.Mvc/Attributes/OnPreBindingAttribute.cs new file mode 100644 index 000000000..7d1a9cd2a --- /dev/null +++ b/Olive.Mvc/Attributes/OnPreBindingAttribute.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; + +namespace Olive.Mvc +{ + [AttributeUsage(AttributeTargets.Method)] + public sealed class OnPreBindingAttribute : BaseModelBindAttribute + { + static ConcurrentDictionary> CustomBindMethods = new ConcurrentDictionary>(); + + internal static void Execute(ControllerContext cContext, object model) + { + var methods = DiscoverBindMethods(cContext, model, typeof(OnPreBindingAttribute), CustomBindMethods); + + foreach (var action in methods) action(); + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Attributes/OnPreBoundAttribute.cs b/Olive.Mvc/Attributes/OnPreBoundAttribute.cs new file mode 100644 index 000000000..a85b9c5e0 --- /dev/null +++ b/Olive.Mvc/Attributes/OnPreBoundAttribute.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Olive.Mvc +{ + [AttributeUsage(AttributeTargets.Method)] + public sealed class OnPreBoundAttribute : BaseModelBindAttribute + { + static ConcurrentDictionary> CustomBindMethods = new ConcurrentDictionary>(); + + const string ROOT = "_MSharp.CustomPreBindActions.RootBinder"; + const string ACTIONS = "_MSharp.CustomPreBindActions"; + + internal static void Enqueue(ControllerContext cContext, object model) => + Enqueue(cContext, model, ACTIONS, typeof(OnPreBoundAttribute), CustomBindMethods); + + internal static void SetRoot(ModelBindingContext model) => SetRoot(model, ROOT); + + internal static void InvokeAllForRoot(ModelBindingContext model) => + InvokeAllForRoot(model, ROOT, ACTIONS); + } +} \ No newline at end of file diff --git a/Olive.Mvc/Attributes/RequiredUnlessDeletingAttribute.cs b/Olive.Mvc/Attributes/RequiredUnlessDeletingAttribute.cs new file mode 100644 index 000000000..def2cead1 --- /dev/null +++ b/Olive.Mvc/Attributes/RequiredUnlessDeletingAttribute.cs @@ -0,0 +1,53 @@ +namespace Olive.Mvc +{ + using System; + using System.ComponentModel.DataAnnotations; + + public class RequiredUnlessDeletingAttribute : RequiredAttribute + { + string DeletingProperty; + + /// + /// Check if the object is going to be deleted skip the validation. + /// + /// The boolean property`s name which shows the object will be deleted. + public RequiredUnlessDeletingAttribute(string deletingProperty = "MustBeDeleted") => + DeletingProperty = deletingProperty; + + protected override ValidationResult IsValid(object value, ValidationContext validationContext) => + UnlessDeletingAttributeChecker.IsValid(base.IsValid, value, validationContext, DeletingProperty); + } + + public class StringLengthUnlessDeletingAttribute : StringLengthAttribute + { + string DeletingProperty; + + /// + /// Check if the object is going to be deleted skip the validation. + /// + /// The boolean property`s name which shows the object will be deleted. + public StringLengthUnlessDeletingAttribute(int maximumLength, string deletingProperty = "MustBeDeleted") : base(maximumLength) => + DeletingProperty = deletingProperty; + + protected override ValidationResult IsValid(object value, ValidationContext validationContext) => + UnlessDeletingAttributeChecker.IsValid(base.IsValid, value, validationContext, DeletingProperty); + } + + internal static class UnlessDeletingAttributeChecker + { + public static ValidationResult IsValid( + Func func, + object value, + ValidationContext validationContext, + string deletingProperty + ) + { + var property = validationContext.ObjectType.GetProperty(deletingProperty); + + if ((bool)property.GetValue(validationContext.ObjectInstance)) + return ValidationResult.Success; + + return func(value, validationContext); + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Attributes/TimeAttribute.cs b/Olive.Mvc/Attributes/TimeAttribute.cs new file mode 100644 index 000000000..eadc0a57b --- /dev/null +++ b/Olive.Mvc/Attributes/TimeAttribute.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text.RegularExpressions; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Olive.Mvc +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public sealed class TimeAttribute : ValidationAttribute, IClientModelValidator + { + public override bool IsValid(object value) + { + if (value != null) + return Regex.IsMatch(value.ToStringOrEmpty(), @"^(?:0?[0-9]|1[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$"); + + return true; + } + + public void AddValidation(ClientModelValidationContext context) => + MergeAttribute(context.Attributes, "data-val-time", FormatErrorMessage(context.ModelMetadata.GetDisplayName())); + + static bool MergeAttribute(IDictionary attributes, string key, string value) + { + if (attributes.ContainsKey(key)) return false; + + attributes.Add(key, value); + return true; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Attributes/ViewDataAttribute.cs b/Olive.Mvc/Attributes/ViewDataAttribute.cs new file mode 100644 index 000000000..33f3f7db9 --- /dev/null +++ b/Olive.Mvc/Attributes/ViewDataAttribute.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Olive.Mvc +{ + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public sealed class ViewDataAttribute : ActionFilterAttribute + { + public string Key { get; set; } + public object Value { get; set; } + + public ViewDataAttribute(string key, object value) + { + Key = key; + Value = value; + } + + public override void OnActionExecuting(ActionExecutingContext context) + { + ((Controller)context.Controller).ViewData[Key] = Value; + base.OnActionExecuting(context); + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Authentication/ExternalLoginInfo.cs b/Olive.Mvc/Authentication/ExternalLoginInfo.cs new file mode 100644 index 000000000..447e236db --- /dev/null +++ b/Olive.Mvc/Authentication/ExternalLoginInfo.cs @@ -0,0 +1,13 @@ +namespace Olive.Mvc +{ + public class ExternalLoginInfo + { + public bool IsAuthenticated { get; set; } + + public string Issuer { get; set; } + + public string Email { get; set; } + public string NameIdentifier { get; set; } + public string UserName { get; set; } + } +} diff --git a/Olive.Mvc/Authentication/OwinAuthenticaionProvider.cs b/Olive.Mvc/Authentication/OwinAuthenticaionProvider.cs new file mode 100644 index 000000000..9c91b9995 --- /dev/null +++ b/Olive.Mvc/Authentication/OwinAuthenticaionProvider.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Olive.Web; + +namespace Olive.Mvc +{ + public class OwinAuthenticaionProvider : IAuthenticationProvider + { + public readonly AsyncEvent ExternalLoginAuthenticated = new AsyncEvent(); + // public string AuthenticationScheme { get; } + + // public OwinAuthenticaionProvider(string authenticationScheme) => AuthenticationScheme = authenticationScheme; + + public async Task LogOn(IUser user, string domain, TimeSpan timeout, bool remember) + { + var context = Context.Http; + + await context.SignOutAsync(IdentityConstants.ApplicationScheme); + + var claims = new List + { + new Claim(ClaimsIdentity.DefaultNameClaimType, user.GetId().ToString()) + }; + + claims.AddRange(user.GetRoles().Select(role => new Claim(ClaimsIdentity.DefaultRoleClaimType, role))); + + await context.SignInAsync(IdentityConstants.ApplicationScheme, + new ClaimsPrincipal(new ClaimsIdentity(claims, "AuthenticationType")), // AuthenticationType is just a text and I do not know what is its usage. + new AuthenticationProperties + { + IsPersistent = remember, + ExpiresUtc = DateTimeOffset.UtcNow.Add(timeout) + }); + } + + public Task LogOff(IUser user) => Context.Http.SignOutAsync(IdentityConstants.ApplicationScheme); + + public async Task LoginBy(string provider) + { + if (Context.HttpContextAccessor.HttpContext.Request.Query["ReturnUrl"].ToString().IsEmpty()) + { + // it's mandatory, otherwise Challenge() immediately returns to Login page + throw new InvalidOperationException("Request has no ReturnUrl."); + } + + await Context.Http.ChallengeAsync(provider, new AuthenticationProperties { RedirectUri = "/ExternalLoginCallback" }); + } + + IUser IAuthenticationProvider.LoadUser(IPrincipal principal) + { + throw new NotSupportedException("IAuthenticationProvider.LoadUser() is deprecated in M# MVC."); + } + + public void PreRequestHandler(string path) + { + if (path == "/ExternalLoginCallback") + { + // this needs to be done here (PreRequestHandler) because we need to get owin context from httpcontext + ExternalLoginCallback(); + } + } + + internal void ExternalLoginCallback() + { + throw new NotImplementedException("The following code is commented to fix on the test time."); + // var authenticationManager = AccessorsHelper.HttpContextAccessor.HttpContext.GetOwinContext().Authentication; + // var loginInfo = authenticationManager.GetExternalLoginInfo(); + + // var info = new ExternalLoginInfo(); + + // if (loginInfo != null) + // { + // var nameIdentifierClaim = loginInfo.ExternalIdentity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier); + + // info.IsAuthenticated = loginInfo.ExternalIdentity.IsAuthenticated; + // info.Issuer = nameIdentifierClaim.Get(c => c.Issuer); + // info.NameIdentifier = nameIdentifierClaim.Get(c => c.Value); + // info.Email = loginInfo.Email; + // info.UserName = loginInfo.DefaultUserName; + // } + + // NotifyExternalLoginAuthenticated(this, info); + } + + public async Task NotifyExternalLoginAuthenticated(ExternalLoginInfo info) + { + if (!ExternalLoginAuthenticated.IsHandled()) + throw new InvalidOperationException("ExternalLogin requested but no handler found for ExternalLoginAuthenticated event"); + + await ExternalLoginAuthenticated.Raise(info); + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Binding/IViewComponent.cs b/Olive.Mvc/Binding/IViewComponent.cs new file mode 100644 index 000000000..459a6a15c --- /dev/null +++ b/Olive.Mvc/Binding/IViewComponent.cs @@ -0,0 +1,9 @@ +namespace Olive.Mvc +{ + /// + /// Marks a controller class as ViewComponent. + /// + public interface IViewComponent + { + } +} diff --git a/Olive.Mvc/Binding/IViewModel.cs b/Olive.Mvc/Binding/IViewModel.cs new file mode 100644 index 000000000..aae951ae9 --- /dev/null +++ b/Olive.Mvc/Binding/IViewModel.cs @@ -0,0 +1,4 @@ +namespace Olive.Mvc +{ + public interface IViewModel { } +} diff --git a/Olive.Mvc/Binding/ListPaginationBinder.cs b/Olive.Mvc/Binding/ListPaginationBinder.cs new file mode 100644 index 000000000..2a4775c46 --- /dev/null +++ b/Olive.Mvc/Binding/ListPaginationBinder.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Olive.Mvc +{ + public class ListPaginationBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + + if (value == null) return null; + + var prefix = bindingContext.ModelName.OrEmpty().Unless("p"); + if (prefix.EndsWith(".p")) prefix = prefix.Split('.').ExceptLast().ToString("."); + + var old = bindingContext.Model as ListPagination; + + bindingContext.Result = ModelBindingResult.Success(new ListPagination(old.Container, value.FirstValue) + { + Prefix = prefix, + UseAjaxPost = old.UseAjaxPost, + UseAjaxGet = old.UseAjaxGet + }); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Binding/ListSortExpressionBinder.cs b/Olive.Mvc/Binding/ListSortExpressionBinder.cs new file mode 100644 index 000000000..e80d44af4 --- /dev/null +++ b/Olive.Mvc/Binding/ListSortExpressionBinder.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Olive.Mvc +{ + public class ListSortExpressionBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + + if (value == null) return Task.CompletedTask; + + var old = bindingContext.Model as ListSortExpression; + + bindingContext.Result = ModelBindingResult.Success(new ListSortExpression(old.Container, value.FirstValue) + { + UseAjaxPost = old.UseAjaxPost, + Prefix = old.Prefix + } + ); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Binding/OliveBinderProvider.cs b/Olive.Mvc/Binding/OliveBinderProvider.cs new file mode 100644 index 000000000..db7864734 --- /dev/null +++ b/Olive.Mvc/Binding/OliveBinderProvider.cs @@ -0,0 +1,419 @@ +using System; +using System.Collections; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Olive.Entities; + +namespace Olive.Mvc +{ + public class OliveBinderProvider : IModelBinderProvider + { + static Type[] PrimitiveTypes = new[] {typeof(DateTime), typeof(DateTime?), typeof(TimeSpan), + typeof(TimeSpan?), typeof(bool), typeof(bool?)}; + + static ConcurrentDictionary PropertyBinderCache = new ConcurrentDictionary(); + + IModelBinder IModelBinderProvider.GetBinder(ModelBinderProviderContext context) => SelectBinder(context); + + static internal IModelBinder SelectBinder(ModelBinderProviderContext context) + { + var modelType = context.Metadata.ModelType; + + if (modelType.IsA()) + { + var propertyBinders = new Dictionary(); + + foreach (var property in GetProperties(context.Metadata)) + propertyBinders.Add(property, context.CreateBinder(property)); + + return new OliveModelBinder(propertyBinders); + } + + if (modelType.IsA()) return new ListSortExpressionBinder(); + if (modelType.IsA()) return new ListPaginationBinder(); + if (modelType.IsA()) return new ColumnSelectionBinder(); + if (modelType.IsA()) return new EntityModelBinder(); + if (modelType.IsA() || modelType.IsA>()) return new DocumentModelBinder(); + + if (PrimitiveTypes.Contains(modelType)) return new PrimitiveValueModelBinder(); + + if (TryGetListModelBinder(modelType, out IModelBinder binder)) + return binder; + + if (modelType == typeof(OptionalBooleanFilter)) return new OptionalBooleanFilterModelBinder(); + if (modelType == typeof(List)) return new OptionalBooleanFilterListModelBinder(); + + if (IsListOfEnum(modelType)) + return new EnumListModelBinder(modelType.GetGenericArguments().First(), modelType); + + if (IsListOfEntity(modelType)) + return new EntityListModelBinder(modelType.GetGenericArguments().First(), modelType); + + return null; + } + + [EscapeGCop("The nature of the try methods is to have out parameter.")] + static bool TryGetListModelBinder(Type modelType, out IModelBinder modelBinder) + { + modelBinder = null; + + if (modelType.IsA>()) modelBinder = new ListModelBinder(); + if (modelType.IsA>()) modelBinder = new ListModelBinder(); + if (modelType.IsA>()) modelBinder = new ListModelBinder(); + if (modelType.IsA>()) modelBinder = new ListModelBinder(); + if (modelType.IsA>()) modelBinder = new ListModelBinder(); + if (modelType.IsA>()) modelBinder = new ListModelBinder(); + if (modelType.IsA>()) modelBinder = new ListModelBinder(); + if (modelType.IsA>()) modelBinder = new ListModelBinder(); + + return modelBinder != null; + } + + static ModelMetadata[] GetProperties(ModelMetadata metadata) + { + return PropertyBinderCache.GetOrAdd(metadata, x => + { + var result = new List(metadata.Properties.Count); + + foreach (var property in metadata.Properties) + { + if (((DefaultModelMetadata)property).Attributes.Attributes.OfType().Any()) + result.AddRange(GetProperties(property.ElementMetadata)); + + result.Add(property); + } + + return result.ToArray(); + }); + } + + static bool IsListOfEnum(Type modelType) + { + // if (modelType == null) return false; + if (!modelType.IsGenericType) return false; + if (!modelType.GetGenericArguments().First().IsEnum) return false; ; + + return modelType.GetGenericTypeDefinition().FullName.StartsWith("System.Collections.Generic.List"); + } + + static bool IsListOfEntity(Type modelType) + { + // if (modelType == null) return false; + if (!modelType.IsGenericType) return false; + if (!modelType.GetGenericArguments().First().IsA()) return false; ; + + return modelType.GetGenericTypeDefinition().FullName.StartsWith("System.Collections.Generic.List"); + } + } + + public class EntityModelBinder : IModelBinder + { + static Dictionary> CustomParsers = new Dictionary>(); + + #region GuidEntityReadableTextParsers + + static Dictionary> GuidEntityReadableTextParsers = new Dictionary>(); + + /// + /// If you want to use the string format of an guid entity in URL, then you can get MVC to bind the entity directly from a textual route value. This registers your binding in addition to the normal binding from GUID. + /// + public static void RegisterReadableTextParser(Func binder) where TEntity : GuidEntity + { + GuidEntityReadableTextParsers.Add(typeof(TEntity), binder); + } + + static GuidEntity ParseGuidEntityFromReadableText(Type entityType, string data) + { + var actualType = entityType; + + while (true) + { + var binder = GuidEntityReadableTextParsers.TryGet(actualType); + + if (binder != null) return binder(data); + + if (actualType.BaseType == typeof(GuidEntity)) + { + // Not found: + throw new Exception($"Cannot parse the data '{data}' to {entityType.FullName} as no parser is registered for this type.\r\n" + + "Hint: Use EntityModelBinder.RegisterParser() to define your 'text to entity convertor' logic."); + } + else actualType = actualType.BaseType; + } + } + + #endregion + + #region Custom parsers + + /// + /// Will register a custom binder for a type instead of the default which uses a Database.Get. + /// + public static void RegisterCustomParser(Func binder) where TEntity : IEntity + { + CustomParsers.Add(typeof(TEntity), binder); + } + + static Func FindCustomParser(Type entityType) + { + Func result = null; + + foreach (var actualType in entityType.WithAllParents()) + { + if (CustomParsers.TryGetValue(actualType, out result)) + return result; + } + + return null; + } + + #endregion + + public async Task BindModelAsync(ModelBindingContext bindingContext) + { + var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + + if (value == null) return; + + var data = value.FirstValue; + + // Special cases: + if (data.IsEmpty() || data.IsAnyOf("{NULL}", "-", Guid.Empty.ToString())) return; + + if (IsReadOnly(bindingContext)) return; + + if (bindingContext.ModelType.IsA() && data.TryParseAs() == null) + { + // We have some data which is not Guid. + bindingContext.Result = ModelBindingResult.Success(ParseGuidEntityFromReadableText(bindingContext.ModelType, data)); + } + + var customBinder = FindCustomParser(bindingContext.ModelType); + if (customBinder != null) + { + try + { + bindingContext.Result = ModelBindingResult.Success(customBinder(data)); + } + catch (Exception ex) + { + throw new Exception($"Failed to bind the value of type '{bindingContext.ModelType.FullName}' from '{data}'.", ex); + } + } + else + { + try + { + bindingContext.Result = ModelBindingResult.Success((await Entity.Database.GetOrDefault(data, bindingContext.ModelType)) + // Sometimes (e.g. in master detail binding) the view model data is written to the 'Item ', so it must be cloned. + ?.Clone()); + } + catch (Exception ex) + { + throw new Exception($"Failed to bind the value of type '{bindingContext.ModelType.FullName}' from '{data}'.", ex); + } + } + } + + bool IsReadOnly(ModelBindingContext context) + { + var metaData = context.ModelMetadata; + if (metaData == null) return false; + + var type = metaData.ContainerType; + if (type == null) return false; + + var propertyName = metaData.PropertyName; + if (propertyName.IsEmpty()) return false; + + var property = type.GetProperty(propertyName); + if (property == null) return false; + + return property.GetCustomAttributes().Any(x => x.IsReadOnly); + } + } + + class ListModelBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + + if (value == null) + bindingContext.Result = ModelBindingResult.Success(null); + + else if (value.FirstValue == "{NULL}" || value.FirstValue == Guid.Empty.ToString()) + bindingContext.Result = ModelBindingResult.Success(null); + + else + { + var result = new List(); + + // It is possible that data is sent as a single value but in pipeline seperated format. + foreach (var idOrIds in value.Values) + result.AddRange(idOrIds.Split('|').Trim().Select(x => x.To())); + + bindingContext.Result = ModelBindingResult.Success(result); + } + + return Task.CompletedTask; + } + } + + class OptionalBooleanFilterModelBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + + if (value == null) + bindingContext.Result = ModelBindingResult.Success(null); + else + bindingContext.Result = ModelBindingResult.Success(OptionalBooleanFilter.Parse(value.FirstValue)); + + return Task.CompletedTask; + } + } + + class OptionalBooleanFilterListModelBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + + if (value == null) + bindingContext.Result = ModelBindingResult.Success(null); + else + bindingContext.Result = ModelBindingResult.Success(value.FirstValue.OrEmpty().Split('|').Select(OptionalBooleanFilter.Parse).ExceptNull().ToList()); + + return Task.CompletedTask; + } + } + + class DocumentModelBinder : IModelBinder + { + public async Task BindModelAsync(ModelBindingContext bindingContext) + { + var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).Get(x => x.FirstValue).OrEmpty(); + + if (bindingContext.ModelType.IsA()) + bindingContext.Result = ModelBindingResult.Success(BindDocument(value)); + else + bindingContext.Result = ModelBindingResult.Success((await BindDocuments(value)).ToList()); + } + + internal async Task BindDocument(string value) + { + var docs = (await BindDocuments(value)).ToList(); + + return docs.FirstOrDefault() ?? new Blob(new byte[0], "«UNCHANGED»"); + } + + internal async Task> BindDocuments(string value) + { + if (value.IsEmpty() || value == "KEEP") + return new Blob[0]; + + else if (value == "REMOVE") + return new[] { Blob.Empty() }; + + else + return await value.Split('|').Trim() + .Where(x => x.StartsWith("file:")) + .Select(async id => await new FileUploadService().Bind(id)) + .AwaitAll(); + } + } + + class PrimitiveValueModelBinder : IModelBinder + { + public async Task BindModelAsync(ModelBindingContext bindingContext) + { + var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).Get(x => x.FirstValue).OrEmpty(); + + bindingContext.Result = ModelBindingResult.Success(await ViewModelServices.Convert(value, bindingContext.ModelType)); + } + } + + class EnumListModelBinder : IModelBinder + { + Type EnumType, ListType; + public EnumListModelBinder(Type enumType, Type listType) + { + EnumType = enumType; + ListType = listType; + } + + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + + var result = (IList)Activator.CreateInstance(ListType); + + if (value == null) + bindingContext.Result = ModelBindingResult.Success(null); + + else if (value.FirstValue != "{NULL}" || value.FirstValue.HasValue()) + { + foreach (var ids in value.Values) + { + foreach (var item in ids.Split('|').Trim()) + { + var asInt = item.TryParseAs(); + if (asInt.HasValue) + result.Add(Enum.ToObject(EnumType, asInt)); + else + result.Add(Enum.Parse(EnumType, item)); + } + } + } + + bindingContext.Result = ModelBindingResult.Success(result); + + return Task.CompletedTask; + } + } + + class EntityListModelBinder : IModelBinder + { + Type EntityType, ListType; + public EntityListModelBinder(Type entityType, Type listType) + { + EntityType = entityType; + ListType = listType; + } + + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + + var result = (IList)Activator.CreateInstance(ListType); + + if (value == null) + bindingContext.Result = ModelBindingResult.Success(null); + + else + { + foreach (var idOrIds in value.Values) + { + foreach (var id in idOrIds.OrEmpty().Split('|').Trim().Except("{NULL}", "-", Guid.Empty.ToString())) + { + var item = Entity.Database.GetOrDefault(id, EntityType); + + if (item != null) result.Add(item); + } + } + } + + bindingContext.Result = ModelBindingResult.Success(result); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Binding/OliveModelBinder.cs b/Olive.Mvc/Binding/OliveModelBinder.cs new file mode 100644 index 000000000..6c8867745 --- /dev/null +++ b/Olive.Mvc/Binding/OliveModelBinder.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Olive.Web; + +namespace Olive.Mvc +{ + public class OliveModelBinder : ComplexTypeModelBinder + { + readonly IDictionary PropertyBinders; + + public OliveModelBinder(IDictionary propertyBinders) : base(propertyBinders) + { + PropertyBinders = propertyBinders; + } + + // /// Sets the specified property by using the specified controller context, binding context, and property value. + // protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result) + // { + // if(result.IsModelSet && propertyMetadata.ModelType == typeof(string)) + // { + // var stringValue = (string)result.Model; + // if (stringValue.HasValue()) + // { + // if(propertyMetadata.ModelType.CustomAttributes.OfType().None()) + // result = ModelBindingResult.Success(stringValue); + // } + // } + + // base.SetProperty(bindingContext, modelName, propertyMetadata, result); + // } + + protected override Task BindProperty(ModelBindingContext bindingContext) + { + Task result; + + var attribute = ((DefaultModelMetadata)bindingContext.ModelMetadata).Attributes.Attributes.OfType().FirstOrDefault(); + if (attribute != null) + result = BindMasterDetailsProperty(bindingContext, attribute); + + else + result = base.BindProperty(bindingContext); + + return result; + } + + async Task BindMasterDetailsProperty(ModelBindingContext bindingContext, MasterDetailsAttribute attribute) + { + if (Context.Request.IsGet()) return; + + var prefix = attribute.Prefix + "-"; + var listObject = Activator.CreateInstance(bindingContext.ModelType) as IList; + // var formData = cContext.RequestContext.HttpContext.Request.Form; + + var childItemIds = bindingContext.ValueProvider.GetValue(prefix + ".Item").FirstValue?.Split("|")?.ToArray() ?? new string[0]; + + foreach (var id in childItemIds) + { + var formControlsPrefix = prefix + id + "."; + + var instanceType = bindingContext.ModelMetadata.ElementType; + var instance = Activator.CreateInstance(instanceType); + listObject.Add(instance); + + // Set the instance properties + foreach (var property in bindingContext.ModelMetadata.ElementMetadata.Properties) + { + var key = formControlsPrefix + property.PropertyName; + + await SetPropertyValue(bindingContext, instance, key, property); + } + + // All properties are written to ViewModel. Now also write them on the model (Item property): + var item = instance.GetType().GetProperty("Item").GetValue(instance); + await ViewModelServices.CopyData(instance, item); + } + + bindingContext.Result = ModelBindingResult.Success(listObject); + } + + async Task SetPropertyValue(ModelBindingContext bindingContext, object model, string modelName, ModelMetadata property) + { + var fieldName = property.BinderModelName ?? property.PropertyName; + + ModelBindingResult result; + using (bindingContext.EnterNestedScope( + modelMetadata: property, + fieldName: fieldName, + modelName: modelName, + model: model)) + { + await base.BindProperty(bindingContext); + + if (bindingContext.ModelState.Keys.Contains(modelName) && + bindingContext.ModelState[modelName].ValidationState == ModelValidationState.Unvalidated) + bindingContext.ModelState[modelName].ValidationState = ModelValidationState.Skipped; + + result = bindingContext.Result; + } + + if (result.IsModelSet) + { + if (property.PropertyName == "Item" && result.Model == null) + result = ModelBindingResult.Success(Activator.CreateInstance(property.ModelType)); + + property.PropertySetter(model, result.Model); + // SetProperty(bindingContext, modelName, property, result); + } + else if (property.IsBindingRequired) + { + var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName); + bindingContext.ModelState.TryAddModelError(modelName, message); + } + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Binding/ViewModelServices.cs b/Olive.Mvc/Binding/ViewModelServices.cs new file mode 100644 index 000000000..62d97fc0d --- /dev/null +++ b/Olive.Mvc/Binding/ViewModelServices.cs @@ -0,0 +1,225 @@ +namespace Olive +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.ComponentModel; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using Entities; + using Mvc; + + public static class ViewModelServices + { + static BindingFlags PropertyFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance + | BindingFlags.FlattenHierarchy | BindingFlags.SetProperty; + + public static async Task To(this IEntity model, string sourcePrefix = null, string targetPrefix = null) where T : IViewModel + { + var result = (T)Activator.CreateInstance(typeof(T)); + + await model.CopyDataTo(result, sourcePrefix, targetPrefix); + + return result; + } + + public static Task CopyDataTo(this IViewModel from, IEntity to, string sourcePrefix = null, string targetPrefix = null) => + CopyData(from, to, sourcePrefix, targetPrefix); + + public static Task CopyDataTo(this IEntity from, IViewModel to, string sourcePrefix = null, string targetPrefix = null) => + CopyData(from, to, sourcePrefix, targetPrefix); + + static bool IsReadonly(PropertyInfo property) => property.GetCustomAttribute()?.IsReadOnly == true; + + // [TODO]: Remove the following attribute + [EscapeGCop("It takes time to fix this warning now. I will check it later.")] + public static async Task CopyData(object from, object to, string sourcePrefix = null, string targetPrefix = null) + { + if (from == null) throw new ArgumentNullException(nameof(from)); + if (to == null) throw new ArgumentNullException(nameof(to)); + + foreach (var property in from.GetType().GetProperties(PropertyFlags)) + { + if (sourcePrefix.HasValue() && !property.Name.StartsWith(sourcePrefix)) continue; + + var inTarget = to.GetType().GetProperty(targetPrefix + property.Name.TrimStart(sourcePrefix.OrEmpty()), PropertyFlags); + + if (inTarget == null) continue; + + if (inTarget.GetSetMethod() == null) continue; + + if (inTarget.GetCustomAttributes().Any()) continue; + + if (new[] { property, inTarget }.Any(p => p.GetCustomAttributes().Any(x => x.CanCopy == false))) continue; + + if (from is IViewModel) + { + if ((from as IViewModel).IsInvisible(property.Name) && + // If it's readonly then it's perhaps set by a property setter or custom code + // as it can't be injected maliciously. + !IsReadonly(property)) + continue; // Not visible + } + + var sourceValue = property.GetValue(from); + + if (from is IEntity && to is IViewModel) + { + var hasClrDefaultValue = property.PropertyType.IsValueType && + (from as IEntity).IsNew && + inTarget.GetCustomAttributes().None(); + + var isValueDefault = sourceValue.ToStringOrEmpty() == property.PropertyType.GetDefaultValue().ToStringOrEmpty(); + + if (hasClrDefaultValue && isValueDefault) continue; + } + + if (property.GetCustomAttributes().Any(x => x.IsReadOnly)) + { + if (property.GetCustomAttributes().None()) continue; + if (sourceValue == null || string.Empty.Equals(sourceValue)) continue; + // Note: Property setters this way cannot set something to null. + } + + try + { + var value = await Convert(sourceValue, inTarget.PropertyType); + + if (property.Defines() && !inTarget.Defines()) + value = ((DateTime?)value).ToUniversal(); + + if (!property.Defines() && inTarget.Defines()) + value = ((DateTime?)value).ToLocal(); + + if (inTarget.PropertyType == typeof(Blob)) + { + if (from is IEntity && to is IViewModel) + { + (value as Blob).FolderName = (sourceValue as Blob).FolderName; + } + else if (from is IViewModel && ((value as Blob).Get(x => x.FileName == "«UNCHANGED»") ?? true)) + { + // Null in view model means not changed. + continue; + } + } + + if (value == null && inTarget.PropertyType.IsA()) continue; // Don't set to null + + inTarget.SetValue(to, value); + } + catch (Exception ex) + { + throw new Exception($"Could not copy the {property.PropertyType} value of {{{sourceValue}}} from {from.GetType().Name}." + + $" {property.Name.TrimStart(sourcePrefix.OrEmpty())} to {inTarget.PropertyType} in {to.GetType().Name}", ex); + } + } + } + + // [TODO]: Remove the following attribute + [EscapeGCop("It takes time to fix this warning now. I will check it later.")] + public static async Task Convert(object source, Type target) + { + if (source == null) return null; + + if (source is Blob) return (source as Blob).Clone(attach: true, @readonly: true); + + if (source.GetType().IsA(target)) return source; + + if (source is string && target == typeof(Guid?)) return (source as string).TryParseAs(); + + if (source is GuidEntity && target == typeof(Guid?)) return (source as GuidEntity).ID; + + if (target.IsA()) + { + if (new[] { typeof(string), typeof(Guid?), typeof(Guid) }.Lacks(source.GetType())) + { + throw new Exception($"Cannot convert {source.GetType().FullName} to {target.FullName}"); + } + + if (source.ToString().IsEmpty()) return null; + + else return (await Entity.Database.GetOrDefault(source.ToString(), target)).Get(x => x.Clone()); + } + + if (target.IsA() && source is IEntity) + { + IViewModel result; + if (target.GetConstructor(new[] { source.GetType() }) != null) + result = (IViewModel)Activator.CreateInstance(target, new object[] { source }); + else result = (IViewModel)Activator.CreateInstance(target); + await (source as IEntity).CopyDataTo(result); + return result; + } + + if (source is string && target.Implements()) + return await ConvertCollection((source as string).Split('|'), target); + + if (source.GetType().Implements() && target == typeof(string)) + return (source as IEnumerable).ToString("|"); + + if (source is IEnumerable) + { + if (target.IsA>()) + return await new DocumentModelBinder().BindDocuments(source.ToStringOrEmpty()); + + var result = await ConvertCollection(source as IEnumerable, target); + if (result != null) return result; + } + + if (source is IEntity) + { + if (target.IsA()) return source; + else return Convert((source as IEntity).GetId(), target); + } + + if (target.IsA()) + return await new DocumentModelBinder().BindDocuments(source.ToStringOrEmpty()); + + try + { + return source.ToStringOrEmpty().To(target); + } + catch + { + throw; + } + } + + static async Task ConvertCollection(IEnumerable source, Type target) + { + IList result; + Type objectType = null; + + if (target.IsA()) + { + result = Activator.CreateInstance(target) as IList; + } + else if (target.IsA>()) + { + objectType = target.GetGenericArguments().Single(); + result = typeof(List<>).MakeGenericType(objectType).CreateInstance() as IList; + } + else return null; + + var targetHoldsIds = target.IsA>() || target.IsA>() || target.IsA>(); + + foreach (var item in source) + { + if (item is IEntity) + { + if (targetHoldsIds) result.Add((item as IEntity).GetId()); + else result.Add(item); + } + else + { + if (targetHoldsIds) result.Add(item); + else result.Add(await Entity.Database.Get(item, objectType)); + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Extentions/Extensions.HtmlHelpers.cs b/Olive.Mvc/Extentions/Extensions.HtmlHelpers.cs new file mode 100644 index 000000000..ad8f14a14 --- /dev/null +++ b/Olive.Mvc/Extentions/Extensions.HtmlHelpers.cs @@ -0,0 +1,381 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Rendering; +using Newtonsoft.Json; +using Olive; +using Olive.Entities; +using Olive.Services.Testing; +using Olive.Web; + +namespace Olive.Mvc +{ + partial class OliveMvcExtensions + { + const int DEFAULT_VISIBLE_PAGES = 7; + + // public static UrlHelper GetUrlHelper(this IHtmlHelper html) => new UrlHelper(html.ViewContext.RequestContext); + + // /// + // /// Renders the specified partial view as an HTML-encoded string. + // /// + // /// The HTML helper instance that this method extends. + // /// The name of the partial view to render. + // /// The view model for the partial view. + // /// The partial view that is rendered as an HTML-encoded string. + // public static HtmlString Partial(this IHtmlHelper html, string partialViewName, T model, bool skipAjaxPost) where T : IViewModel + // { + // var request = HttpContext.Current.Request; + // if (skipAjaxPost && request.IsAjaxCall() && request.IsPost()) return HtmlString.Empty; + + // if (model == null) + // { + // model = (html.ViewContext.Controller as Controller).Bind(); + + // if (model == null) + // throw new Exception("The model object passed to Partial() cannot be null."); + // } + + // return html.Partial(partialViewName, model); + // } + + public static HtmlString ToJson(this IHtmlHelper html, object obj) + { + if (obj == null) return new HtmlString("[]"); + + return new HtmlString(JsonConvert.SerializeObject(obj)); + } + + public static HtmlString GetActionsJson(this IHtmlHelper html) + { + var data = html.ViewContext.HttpContext.Items["JavascriptActions"] as object; + + return html.ToJson(data); + } + + public static IHtmlContent RadioButtonsFor(this IHtmlHelper html, Expression> property, IEnumerable selectList, object htmlAttributes) + { + var propertyInfo = property.GetProperty(); + + var value = propertyInfo.GetValue(html.ViewData.Model); + + if (value is IEntity) value = (value as IEntity).GetId(); + + var settings = ToHtmlAttributes(htmlAttributes); + + var result = new HtmlContentBuilder(); + + result.AppendHtmlLine($"
"); + + foreach (var item in selectList) + { + result.AppendHtmlLine($""); + + var id = propertyInfo.Name + "_" + selectList.IndexOf(item); + + result.AppendHtml(html.RadioButton(propertyInfo.Name, item.Value, IsSelected(item, value), new { id = id })); + + result.AppendHtmlLine($""); + + result.AppendHtmlLine("
"); + } + + result.AppendHtmlLine(""); + + return result; + } + + static bool IsSelected(SelectListItem item, object boundValue) + { + if (boundValue.ToStringOrEmpty() == item.Value) return true; + + if (boundValue == null && item.Value == "-") return true; + + return false; + } + + public static IHtmlContent FileUploadFor(this IHtmlHelper html, Expression>> property, object htmlAttributes = null) => + new DefaultFileUploadMarkupGenerator().Generate(html, html.ViewData.Model, property, htmlAttributes); + + public static IHtmlContent FileUploadFor(this IHtmlHelper html, Expression> property, object htmlAttributes = null) => + new DefaultFileUploadMarkupGenerator().Generate(html, html.ViewData.Model, property, htmlAttributes); + + public static HtmlString CheckBoxesFor(this IHtmlHelper html, Expression> property, IEnumerable selectList, object htmlAttributes = null) => + GenerateCheckBoxesFor(html, property, selectList, htmlAttributes, setContainerId: true); + + static HtmlString GenerateCheckBoxesFor(this IHtmlHelper html, Expression> property, IEnumerable selectList, object htmlAttributes, bool setContainerId) + { + var propertyInfo = property.GetProperty(); + + var value = propertyInfo.GetValue(html.ViewData.Model); + + return html.GenerateCheckBoxes(propertyInfo.Name, value as IEnumerable, selectList, htmlAttributes, propertyInfo, setContainerId); + } + + public static HtmlString CheckBoxes(this IHtmlHelper html, string name, IEnumerable selectedItems, IEnumerable selectList, object htmlAttributes = null, PropertyInfo property = null) + { + return GenerateCheckBoxes(html, name, selectedItems, selectList, htmlAttributes, property, setContainerId: true); + } + + static HtmlString GenerateCheckBoxes(this IHtmlHelper html, string name, IEnumerable selectedItems, IEnumerable selectList, object htmlAttributes = null, PropertyInfo property = null, bool setContainerId = true) + { + var currentItems = new string[0]; + + if (selectedItems != null) + { + if (selectedItems is string) currentItems = new[] { (string)selectedItems }; + else currentItems = (selectedItems as IEnumerable).Cast().ExceptNull() + .Select(x => (x as IEntity).Get(b => b.GetId()).ToStringOrEmpty().Or(x.ToString())).ToArray(); + } + + var settings = ToHtmlAttributes(htmlAttributes); + + var r = new StringBuilder(); + + r.Append("
"); + + var isRequiredProperty = property?.IsDefined(typeof(RequiredAttribute)) == true; + + var requiredValidationMessage = isRequiredProperty ? GetRequiredValidationMessage(property) : string.Empty; + + foreach (var item in selectList) + { + r.AddFormattedLine("", settings); + + var id = name + "_" + selectList.IndexOf(item); + + r.AppendLine($""); + + r.AppendLine($""); + r.AppendLine("
"); + } + + r.AppendLine(""); + + return new HtmlString(r.ToString()); + } + + static string GetRequiredValidationMessage(PropertyInfo property) + { + var requiredAttribute = property?.GetCustomAttribute(typeof(RequiredAttribute)) as RequiredAttribute; + + if (requiredAttribute.ErrorMessage.HasValue()) return requiredAttribute.ErrorMessage; + + var propertyName = property.Name; + + if (property.IsDefined(typeof(DisplayNameAttribute))) + { + var displayAttribute = property?.GetCustomAttribute(typeof(DisplayNameAttribute)) as DisplayNameAttribute; + propertyName = displayAttribute?.DisplayName; + } + + return $"The {propertyName} field is required."; + } + + public static HtmlString CollapsibleCheckBoxesFor(this IHtmlHelper html, + Expression> property, + IEnumerable selectList, + object htmlAttributes = null) + { + var name = property.GetProperty().Name; + + var itemsHtml = html.GenerateCheckBoxesFor(property, selectList, htmlAttributes: null, setContainerId: false).ToString(); + var attributes = ToHtmlAttributes(htmlAttributes); + + var result = $@"
+
+ +
{itemsHtml}
+
+
+
+ + +
+
+ + +
+
"; + + return new HtmlString(result); + } + + internal static string ToHtmlAttributes(object htmlAttributes) + { + if (htmlAttributes == null) return string.Empty; + + var settings = htmlAttributes.GetType().GetProperties() + .Select(x => new { name = x.Name.Replace("_", "-"), value = x.GetValue(htmlAttributes) }).ToList(); + + var r = new StringBuilder(); + + return settings.Select(x => x.name + "=\"" + x.value + "\"").ToString(" ").WithPrefix(" "); + } + + public static HtmlString Pagination(this IHtmlHelper html, ListPagination paging, object htmlAttributes = null, string prefix = null) => + Pagination(html, paging, DEFAULT_VISIBLE_PAGES, htmlAttributes, prefix); + + public static HtmlString Pagination(this IHtmlHelper html, ListPagination paging, int visiblePages, object htmlAttributes = null, string prefix = null) => + new PaginationRenderer(html, paging, visiblePages, htmlAttributes, prefix).Render(); + + // public static void RenderAction(this IHtmlHelper html, string action = "Index") + // { + // html.RenderAction(action, typeof(TController).Name.TrimEnd("Controller")); + // } + + // /// + // /// Invokes the Index action method of the specified controller and returns the result as an HTML string. + // /// An anonymous object containing query string / route values to pass. + // /// + // public static HtmlString Action(this IHtmlHelper html, object queryParameters) + // { + // return Action(html, "Index", queryParameters); + // } + + // /// + // /// Invokes the specified child action method of the specified controller and returns the result as an HTML string. + // /// An anonymous object containing query string / route values to pass. + // /// + // public static HtmlString Action(this IHtmlHelper html, string action = "Index", object queryParameters = null) + // { + // return html.Action(action, typeof(TController).Name.TrimEnd("Controller"), queryParameters); + // } + + /// + /// Will join this with other Mvc Html String items; + /// + public static IHtmlContent Concat(this IHtmlContent me, IHtmlContent first, params IHtmlContent[] others) + { + var result = new HtmlContentBuilder(); + result.AppendHtml(me); + result.AppendHtml(first); + others.Do(x => result.AppendHtml(x)); + + return result; + } + + public static IHtmlContent RegisterStartupActions(this IHtmlHelper html) + { + var startupActions = html.GetActionsJson().ToString().Unless("[]"); + + var result = startupActions.HasValue() ? html.Hidden("Startup.Actions", startupActions) : HtmlString.Empty; + + var request = html.ViewContext.HttpContext.Request; + + if (request.IsAjaxGet()) + { + var title = Context.Http.Items["Page.Title"].ToStringOrEmpty().Or(html.ViewData["Title"].ToStringOrEmpty()); + result = result.Concat(html.Hidden("page.meta.title", title)); + } + + return result; + } + + public static HttpRequest Request(this IHtmlHelper html) => + html.ViewContext.HttpContext.Request; + + /// + /// Creates a hidden field to contain the json data for the start-up actions. + /// + public static IHtmlContent StartupActionsJson(this IHtmlHelper html) + { + if (!html.Request().IsAjaxPost()) return null; + + var startupActions = html.GetActionsJson().ToString().Unless("[]"); + + if (startupActions.HasValue()) + return html.Hidden("Startup.Actions", startupActions); + + return HtmlString.Empty; + } + + public static HtmlString ResetDatabaseLink(this IHtmlHelper html) + { + if (!WebTestManager.IsTddExecutionMode()) return null; + + if (html.Request().IsAjaxCall()) return null; + + if (WebTestManager.IsSanityExecutionMode()) + html.RunJavascript("page.skipNewWindows();"); + + return new HtmlString(WebTestManager.GetWebTestWidgetHtml(Context.Http.Request)); + } + + // /// + // /// Creates a new Html helper for a new ViewModel object. + // /// This enables to start from a new view context, so that normal Html helper methods (such as TextBoxFor, etc) yield the correct name attributes, use correct existing value, etc. + // /// + // public static HtmlHelper For(this IHtmlHelper html, TTarget model) + // { + // var container = new BasicViewDataContainer { ViewData = new ViewDataDictionary { Model = model } }; + + // var viewContext = new ViewContext(html.ViewContext, html.ViewContext.View, container.ViewData, html.ViewContext.Writer); + + // return new HtmlHelper(viewContext, container); + // } + + public static HtmlString RunJavascript(this IHtmlHelper html, string script, PageLifecycleStage stage = PageLifecycleStage.Init) => + RunJavascript(html, script, script, stage); + + public static HtmlString RunJavascript(this IHtmlHelper html, string key, string script, PageLifecycleStage stage = PageLifecycleStage.Init) + { + var actions = html.ViewContext.HttpContext.Items["JavascriptActions"] as List; + if (actions == null) html.ViewContext.HttpContext.Items["JavascriptActions"] = actions = new List(); + + // If already added, ignore: + var exists = actions + .Where(x => x.GetType().GetProperty("Script") != null) + .Where(x => x.GetType().GetProperty("Key") != null) + .Cast().Any(x => x.Key == key); + + if (!exists) + actions.Add(new { Script = script, Key = key, Stage = stage.ToString() }); + + return HtmlString.Empty; + } + + public static HtmlString ReferenceScriptFile(this IHtmlHelper html, string scriptUrl) + { + var items = html.ViewContext.HttpContext.Items["MVC.Registered.Script.Files"] as List; + if (items == null) html.ViewContext.HttpContext.Items["MVC.Registered.Script.Files"] = items = new List(); + + if (items.Lacks(scriptUrl)) items.Add(scriptUrl); + + return HtmlString.Empty; + } + + /// + /// Call this at the bottom of the layout file. It will register script tags to reference dynamically referenced script files. + /// + public static HtmlString RegisterDynamicScriptFiles(this IHtmlHelper html) + { + var result = new StringBuilder(); + + if (html.ViewContext.HttpContext.Items["MVC.Registered.Script.Files"] is List items) + return new HtmlString(items.Select(f => $"").ToLinesString()); + + return HtmlString.Empty; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Extentions/Extensions.SelectListItem.cs b/Olive.Mvc/Extentions/Extensions.SelectListItem.cs new file mode 100644 index 000000000..08a3d40eb --- /dev/null +++ b/Olive.Mvc/Extentions/Extensions.SelectListItem.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Rendering; +using Olive; +using Olive.Entities; +using Olive.Services.Globalization; + +namespace Olive.Mvc +{ + partial class OliveMvcExtensions + { + public static void AddRange(this IList listItems, IEnumerable items, Func displayExpression) where T : IEntity + { + foreach (var item in items) + listItems.Add(new SelectListItem + { + Text = displayExpression(item).ToStringOrEmpty(), + Value = item.GetId().ToString() + }); + } + + public static void AddRange(this IList listItems, IEnumerable items, T selectedItem, Func displayExpression) where T : IEntity + { + foreach (var item in items) + { + if (item == null) continue; + + listItems.Add(new SelectListItem + { + Text = displayExpression(item).ToStringOrEmpty(), + Value = item.GetId().ToString(), + Selected = item.Equals(selectedItem) + }); + } + } + + public static void AddRange(this IList listItems, IEnumerable items, + Func displayExpression, + Func valueExpression) + { + foreach (var item in items) + { + var value = valueExpression(item); + + if (value is IEntity) value = (value as IEntity).GetId(); + + listItems.Add(new SelectListItem + { + Text = displayExpression(item).ToStringOrEmpty(), + Value = value.ToStringOrEmpty() + }); + } + } + + public static void AddRange(this IList listItems, IEnumerable items) => + AddRange(listItems, items, (IEnumerable)null); + + public static void AddRange(this IList listItems, IEnumerable items, IEntity selectedItem) => + AddRange(listItems, items, new[] { selectedItem }); + + public static void AddRange(this IList listItems, IEnumerable items, IEnumerable selectedItems, Func displayExpression) where T : IEntity + { + foreach (var item in items) + { + if (item == null) continue; + + listItems.Add(new SelectListItem + { + Text = displayExpression(item).ToString(), + Value = item.GetId().ToString(), + Selected = selectedItems != null && selectedItems.Contains(item) + }); + } + } + + //public static async Task Add(this IList items, IEntity entity, ILanguage language) + //{ + // items.Add(new SelectListItem + // { + // Text = await entity.ToString(language), + // Value = entity.GetId().ToString() + // }); + //} + + public static void Add(this IList items, IEntity entity) => + items.Add(new SelectListItem { Text = entity.ToString(), Value = entity.GetId().ToString() }); + + public static void Add(this IList items, object text, object value) => + items.Add(new SelectListItem { Text = text.ToStringOrEmpty(), Value = value.ToStringOrEmpty() }); + + public static void AddRange(this IList listItems, IEnumerable items, IEnumerable selectedItems) + { + var selected = selectedItems?.OfType(); + + foreach (var item in items) + { + if (item == null) continue; + else if (item is IEntity) listItems.Add((IEntity)item); + else if (item is SelectListItem) listItems.Add((SelectListItem)item); + else listItems.Add(new SelectListItem { Text = item.ToStringOrEmpty(), Value = item.ToStringOrEmpty() }); + + if (selected != null && selected.Contains(item)) + listItems[listItems.Count - 1].Selected = true; + } + } + + public static string GetSelected(this SelectListItem item) + { + if (item.Selected) return "selected=\"selected\""; + else return null; + } + + public static List Clone(this IEnumerable items) + { + return items.Select(x => new SelectListItem + { + Text = x.Text, + Selected = x.Selected, + Value = x.Value, + Group = x.Group, + Disabled = x.Disabled + }).ToList(); + } + + public static void SetSelected(this IEnumerable items, params string[] selectedValues) + { + if (selectedValues.None()) return; + + items.Where(x => selectedValues.Contains(x.Value.Or(x.Text))).Do(x => x.Selected = true); + } + + public static List ToSelectListItems(this IEnumerable items) + { + var result = new List(); + result.AddRange(items); + + return result; + } + + public static void AddRange(this List items) where TEnum : struct, IConvertible + { + var type = typeof(TEnum); + + if (!type.IsEnum) throw new ArgumentException("TEnum must be an enumerated type"); + + foreach (var item in Enum.GetValues(type)) + items.Add(item.ToString(), (int)item); + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Extentions/Extensions.UrlHelpers.cs b/Olive.Mvc/Extentions/Extensions.UrlHelpers.cs new file mode 100644 index 000000000..cfa662099 --- /dev/null +++ b/Olive.Mvc/Extentions/Extensions.UrlHelpers.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Olive.Entities; +using Olive.Web; + +namespace Olive.Mvc +{ + partial class OliveMvcExtensions + { + static ConcurrentDictionary> IndexActionRoutes = new ConcurrentDictionary>(); + + public static string Current(this IUrlHelper url) + { + var result = url.ActionContext?.HttpContext.Request.GetValue("current.request.url"); + + if (result.IsEmpty()) result = Context.Http?.Request.GetValue("current.request.url"); + + if (result.HasValue()) + { + if (!url.IsLocalUrl(result)) throw new Exception("Invalid injected current url."); + } + else + { + result = url.ActionContext?.HttpContext.Request.ToRawUrl(); + + if (result.IsEmpty()) result = Context.Http?.Request.ToRawUrl(); + } + + return result; + } + + public static string Current(this IUrlHelper url, object queryParameters) + { + if (queryParameters == null) return Current(url); + + var settings = queryParameters.GetType().GetProperties() + .ToDictionary(x => x.Name, x => x.GetValue(queryParameters).ToStringOrEmpty()); + + return Current(url, settings); + } + + public static string Current(this IUrlHelper url, IDictionary queryParameters) + { + var result = url.CurrentUri().RemoveEmptyQueryParameters(); + + if (queryParameters == null) queryParameters = new Dictionary(); + + foreach (var item in queryParameters) + result = result.RemoveQueryString(item.Key).AddQueryString(item.Key, item.Value); + + return result.PathAndQuery; + } + + public static Uri CurrentUri(this IUrlHelper urlHelper) + { + var url = urlHelper.Current(); + if (url.StartsWith("http")) return url.AsUri().RemoveEmptyQueryParameters(); + return ($"http://domain.com{url}").AsUri().RemoveEmptyQueryParameters(); + } + + public static string QueryString(this IUrlHelper url) => url.ActionContext.HttpContext.Request.QueryString.ToString(); + + public static string ReturnUrl(this IUrlHelper urlHelper) + { + var url = urlHelper.ActionContext.HttpContext.Request.GetValue("ReturnUrl"); + if (url.IsEmpty()) return string.Empty; + + if (urlHelper.IsLocalUrl(url)) return url; + + throw new Exception(url + " is not a valid ReturnUrl as it's external and so unsafe."); + } + + /// + /// Returns the specified actionUrl. But it first adds the current route and query string query parameters, all as query string. + /// + public static string ActionWithQuery(this IUrlHelper url, string actionUrl, IEntity listItem = null) => + url.ActionWithQuery(actionUrl, new { list_item = listItem }); + + public static string ActionWithQuery(this IUrlHelper url, string actionUrl, object query) + { + var data = url.ActionContext.GetRequestParameters(); + + if (query != null) + { + var queryData = query.GetType().GetProperties() + .ToDictionary(p => p.Name.ToLower().Replace("_", "."), p => + { + var value = p.GetValue(query); + if (value is IEntity) + return (value as IEntity).GetId().ToStringOrEmpty(); + return value.ToStringOrEmpty(); + }); + + foreach (var item in queryData.Where(x => x.Value.HasValue())) data[item.Key] = item.Value; + } + + var queryString = data.Where(x => x.Value.HasValue()).Select(x => x.Key + "=" + WebUtility.UrlEncode(x.Value)).ToString("&"); + + return url.Content("~/" + actionUrl + queryString.WithPrefix("?").Replace("?&", "?")); + } + + /// + /// Determines if a request parameter (route or query string) value exists for the specified key. + /// + public static bool Has(this ActionContext actionContext, string key) => actionContext.Param(key).HasValue(); + + /// + /// Determines if a request parameter (route or query string) value does not exists for the specified key. + /// + public static bool Lacks(this ActionContext actionContext, string key) => !actionContext.Has(key); + + /// + /// Will get the value for the specified key in the current request whether it comes from Route or Query String. + /// + public static string Param(this ActionContext actionContext, string key) + { + if (key.IsEmpty()) throw new ArgumentNullException(nameof(key)); + + return actionContext.GetRequestParameters() + .FirstOrDefault(x => x.Key.OrEmpty().ToLower() == key.ToLower()) + .Get(x => x.Value); + } + + public static Task> GetList(this HttpRequest request, string key, char separator = ',') where T : IEntity + { + return request.GetValue(key).OrEmpty().Split(separator).Trim().Select(x => Entity.Database.Get(x)).AwaitAll(); + } + + public static Dictionary GetRequestParameters(this ActionContext actionContext) + { + var result = actionContext.RouteData.Values.ToDictionary(x => x.Key, x => x.Value.ToStringOrEmpty()); + + result.Remove("controller"); + result.Remove("action"); + + var byQuerystring = actionContext.HttpContext.Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString()); + + byQuerystring = byQuerystring.Except(x => result.ContainsKey(x.Key)).ToDictionary(x => x.Key, x => x.Value); + + result.Add(byQuerystring); + + return result; + } + + public static object RouteValue(this IUrlHelper url, string key) => url.ActionContext.RouteData.Values[key]; + + static List FindIndexRouteTemplates(string controllerName) + { + var relevantAssemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.References(Assembly.GetExecutingAssembly())).ToList(); + + var types = relevantAssemblies.SelectMany(a => a.GetTypes().Where(t => t.Name == controllerName)) + .ExceptNull() + .Where(x => x.InhritsFrom(typeof(ControllerBase))).ToList(); + + if (types.None()) + throw new Exception("Controller class not found: " + controllerName); + + if (types.HasMany()) + throw new Exception("Multiple Controller classes found: " + types.Select(x => x.AssemblyQualifiedName).ToLinesString()); + + var type = types.Single(); + + var indexAction = type.GetMethods().Where(x => x.Name == "Index").ToList(); + + if (indexAction.None()) throw new Exception(type.FullName + " has no Index method."); + + if (indexAction.HasMany()) throw new Exception(type.FullName + " has multiple Index methods."); + + var attributes = indexAction.First().GetCustomAttributes(); + + if (attributes.None()) throw new Exception(type.FullName + ".Index() has no Route attribute."); + + return attributes.Select(x => new RouteTemplate(x.Template)).ToList(); + } + + public static string Index(this IUrlHelper url, object routeValues = null) where TController : Microsoft.AspNetCore.Mvc.Controller => + url.Index(typeof(TController).Name.TrimEnd("Controller"), routeValues); + + public static string Index(this IUrlHelper url, string controllerName, object routeValues = null) + { + var routeParameters = new Dictionary(); + + if (routeValues != null) + routeParameters = routeValues.GetType().GetProperties() + .ToDictionary(p => p.Name.ToCamelCaseId(), p => p.GetValue(routeValues).ToStringOrEmpty()); + + return url.Index(controllerName, routeParameters); + } + + public static string Index(this IUrlHelper url, string controllerName, Dictionary routeParameters) + { + if (!controllerName.EndsWith("Controller")) controllerName += "Controller"; + + var routeTemplates = IndexActionRoutes.GetOrAdd(controllerName, FindIndexRouteTemplates); + + var bestRouteMatch = FindBestRouteMatch(routeTemplates, routeParameters); + + if (bestRouteMatch == null) + { + var message = $"Failed to evaluate: @Url.Index(\"{controllerName.TrimEnd("Controller")}\"" + + routeParameters.Select(x => x.Key + "= «" + x.Value + "»").ToString(", ").WithWrappers(", new { ", "}") + + ")\r\n\r\n"; + + message += "Destination route pattern(s) don't match the provided parameters.\r\n\r\n"; + message += "Destination route pattern(s): " + routeTemplates.ToLinesString(); + + throw new Exception(message); + } + + try + { + return bestRouteMatch.Merge(routeParameters); + } + catch (Exception ex) + { + throw new Exception("Failed to create the URL for pattern: '" + bestRouteMatch + "' with the provided data: {" + + routeParameters.Select(x => x.Key + ":" + x.Value).ToString(" , ") + "}", ex); + } + } + + static RouteTemplate FindBestRouteMatch(IEnumerable templates, Dictionary providedParameters) + { + templates = templates.Where(t => t.IsMatch(providedParameters)).ToList(); + + return templates + // Exact number of parameters takes priority. + .OrderByDescending(t => t.FindMatchingParameters(providedParameters).Count()) + + // Then the one with highest number of parameters. + .ThenBy(t => t.Parameters.Count - t.FindMatchingParameters(providedParameters).Count()) + .FirstOrDefault(); + } + + public static string MergeRoute(this IUrlHelper url, string routeTemplate, Dictionary routeData) + { + if (routeTemplate.IsEmpty()) + throw new ArgumentNullException(nameof(routeTemplate)); + + return new RouteTemplate(routeTemplate).Merge(routeData); + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Extentions/Extensions.cs b/Olive.Mvc/Extentions/Extensions.cs new file mode 100644 index 000000000..b9b57dd8e --- /dev/null +++ b/Olive.Mvc/Extentions/Extensions.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Olive.Entities.Data; +using Olive.Entities; +using Olive; + +namespace Olive.Mvc +{ + public static partial class OliveMvcExtensions + { + public static IEnumerable TakePage(this IEnumerable list, ListPagination paging) => + list.TakePage(paging.PageSize, paging.CurrentPage); + + public static HtmlString Replace(this IHtmlContent content, string oldText, string newText) => + new HtmlString(GetString(content).KeepReplacing(oldText, newText)); + + public static HtmlString PrefixName(this IHtmlContent content, string prefix) + { + var code = GetString(content) + .Replace(" name=\"", $" name=\"{prefix}.") + .Replace(" id=\"", $" id=\"{prefix}.") + .Replace(" for=\"", $" for=\"{prefix}."); + + return new HtmlString(code); + } + + internal static string GetString(this IHtmlContent content) + { + var writer = new System.IO.StringWriter(); + content.WriteTo(writer, HtmlEncoder.Default); + + return writer.ToString(); + } + + internal static void ReplaceIdentificationAttributes(this Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput output, string newValue) + { + output.Attributes.Remove(output.Attributes.First(att => att.Name == "name")); + output.Attributes.Add("name", newValue); + + output.Attributes.Remove(output.Attributes.First(att => att.Name == "id")); + output.Attributes.Add("id", newValue); + + if (output.Attributes.Any(att => att.Name == "for")) + { + output.Attributes.Remove(output.Attributes.First(att => att.Name == "for")); + output.Attributes.Add("for", newValue); + } + } + + /// + /// Looks for a property named propertyName_Visible on this object. If it finds one and find it to be false it returns true. + /// Otherwise false. + /// + public static bool IsInvisible(this IViewModel viewModel, string propertyName) + { + var visibleProperty = viewModel.GetType().GetProperty(propertyName + "_Visible"); + + if (visibleProperty == null) return false; + + return !(bool)visibleProperty.GetValue(viewModel); + } + + public static bool IsValid(this ModelStateDictionary modelState, IViewModel viewModel) + { + foreach (var item in modelState) + if (viewModel.IsInvisible(item.Key)) + item.Value.Errors.Clear(); + + return modelState.IsValid; + } + + /// + /// Gets all errors for all values in this model state instance. + /// + public static IEnumerable GetErrors(this ModelStateDictionary modelState, bool errorStack = false) + { + foreach (var item in modelState.Values) + foreach (ModelError error in item.Errors) + yield return error.ErrorMessage.Or(error.Exception.Get(x => errorStack ? x.ToLogString() : x.Message)); + } + + /// + /// Will convert this html string into a + /// + public static HtmlString Raw(this string text) => new HtmlString(text.OrEmpty()); + + /// + /// Gets access to the current ViewBag. + /// + public static dynamic ViewBag(this HttpContext context) => (dynamic)context.Items["ViewBag"]; + + /// + /// Gets the name of the currently requested controller. + /// + public static string GetCurrentControllerName(this ActionContext context) => + context.RouteData.Values["controller"].ToString(); + + public static T OrderBy(this T query, ListSortExpression sort) + where T : IDatabaseQuery + { + query.OrderBy(sort.Expression, sort.Descending); + return query; + } + + public static T Page(this T query, ListPagination paging) + where T : IDatabaseQuery + { + query.Page(paging.CurrentPage, paging.PageSize ?? 100000); + return query; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Extentions/Http.cs b/Olive.Mvc/Extentions/Http.cs new file mode 100644 index 000000000..badd7818b --- /dev/null +++ b/Olive.Mvc/Extentions/Http.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Routing; +using Olive; +using Olive.Entities; +using Olive.Web; + +namespace Olive.Mvc +{ + partial class OliveMvcExtensions + { + public static HttpContext GetHttpContextBase(this HttpContext context) + { + var owinInfo = context.Items["owin.Environment"] as + IDictionary; + + return owinInfo?["System.Web.HttpContextBase"] as HttpContext; + } + + /// + /// Determines if this is an Ajax GET http request. + /// + public static bool IsAjaxGet(this HttpRequest request) => request.IsAjaxCall() && request.IsGet(); + + /// + /// Determines if this is an Ajax Post http request. + /// + public static bool IsAjaxPost(this HttpRequest request) => request.IsAjaxCall() && request.IsPost(); + + /// + /// Dispatches a binary data block back to the client. + /// + public static async Task Dispatch(this HttpResponse response, byte[] responseData, string fileName, string contentType = "Application/octet-stream") + { + if (responseData == null) throw new ArgumentNullException(nameof(responseData)); + + if (fileName.IsEmpty()) throw new ArgumentNullException(nameof(fileName)); + + response.Clear(); + response.ContentType = contentType; + + response.Headers.Add("Cache-Control", "no-store"); + response.Headers.Add("Pragma", "no-cache"); + + response.Headers.Add("Content-Disposition", "attachment; filename=\"{0}\"".FormatWith(fileName.Remove("\"", ","))); + + await response.Body.WriteAsync(responseData, 0, responseData.Length); + } + + /// + /// Dispatches a file back to the client. + /// + public static async Task Dispatch(this HttpResponse response, Blob blob, string contentType = "Application/octet-stream") => + await Dispatch(response, blob.LocalPath.AsFile(), blob.FileName, contentType); + + /// + /// Dispatches a file back to the client. + /// + /// If set to null, the same file name of the file will be used. + public static async Task Dispatch(this HttpResponse response, FileInfo responseFile, string fileName = null, string contentType = "Application/octet-stream") + { + if (responseFile == null) throw new ArgumentNullException(nameof(responseFile)); + + if (fileName.IsEmpty()) fileName = responseFile.Name; + + var data = await responseFile.ReadAllBytes(); + + await response.Dispatch(data, fileName, contentType); + } + + /// + /// Dispatches a string back to the client as a file. + /// + public static async Task Dispatch(this HttpResponse response, string responseText, string fileName, string contentType = "Application/octet-stream", System.Text.Encoding encoding = null) + { + response.Clear(); + + if (encoding == null) encoding = Encoding.UTF8; + + var bytes = encoding == Encoding.UTF8 ? responseText.GetUtf8WithSignatureBytes() : encoding.GetBytes(responseText); + + await response.Dispatch(bytes, fileName, contentType); + } + + /// + /// Gets a URL helper for the current http context. + /// + public static UrlHelper GetUrlHelper(this HttpContext context) => + new UrlHelper(Context.ActionContextAccessor.ActionContext); + } +} \ No newline at end of file diff --git a/Olive.Mvc/Olive.Mvc.csproj b/Olive.Mvc/Olive.Mvc.csproj new file mode 100644 index 000000000..5c489b61f --- /dev/null +++ b/Olive.Mvc/Olive.Mvc.csproj @@ -0,0 +1,26 @@ + + + + netcoreapp2.0 + + + + ..\@Assemblies\ + ..\@Assemblies\netcoreapp2.0\Olive.Mvc.xml + 1701;1702;1705;1591;1573 + + + + + + + + + + + + + + + + diff --git a/Olive.Mvc/Package.nuspec b/Olive.Mvc/Package.nuspec new file mode 100644 index 000000000..8c49b85c2 --- /dev/null +++ b/Olive.Mvc/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Mvc + 1.0.6 + Olive MVC + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Olive.Mvc/Services/DefaultFileUploadMarkupGenerator.cs b/Olive.Mvc/Services/DefaultFileUploadMarkupGenerator.cs new file mode 100644 index 000000000..6feff8dc4 --- /dev/null +++ b/Olive.Mvc/Services/DefaultFileUploadMarkupGenerator.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq.Expressions; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; +using Olive.Entities; +using Olive.Web; + +namespace Olive.Mvc +{ + class DefaultFileUploadMarkupGenerator + { + public IHtmlContent Generate(IHtmlHelper html, object model, Expression> property, object htmlAttributes) + { + var propertyInfo = property.GetProperty(); + var blob = propertyInfo.GetValue(model) as Blob ?? Blob.Empty(); + var value = html.ViewContext.HttpContext.Request.HasFormContentType ? + html.ViewContext.HttpContext.Request.Form[propertyInfo.Name] : + Microsoft.Extensions.Primitives.StringValues.Empty; + if (value == "KEEP") + { + var itemProperty = model.GetType().GetProperty("Item"); + var item = itemProperty.GetValue(model); + var originalPropertyInfo = item.GetType().GetProperty(propertyInfo.Name); + blob = originalPropertyInfo.GetValue(item) as Blob ?? Blob.Empty(); + } + + // Note: If this method is called with an IEnumerable property, + // then the existing data will never be loaded. + var result = new HtmlContentBuilder(); + + result.AppendHtmlLine("
"); + result.AppendHtmlLine($"" + + $"{blob.FileName.OrEmpty().HtmlEncode()}"); + result.AppendHtmlLine($""); + // For validation to work, this works instead of Hidden. + if (value.ToString().IsEmpty() && blob.HasValue()) value = "KEEP"; + result.AppendHtml(html.TextBox(propertyInfo.Name, value.OrEmpty(), string.Empty, + new { tabindex = "-1", style = "width:1px; height:0; border:0; padding:0; margin:0;", @class = "file-id", autocomplete = "off" })); + result.AppendHtmlLine("
"); + result.AppendHtmlLine(""); + result.AppendHtmlLine("
"); + + return result; + } + } +} diff --git a/Olive.Mvc/Services/FileUploadService.cs b/Olive.Mvc/Services/FileUploadService.cs new file mode 100644 index 000000000..9b1903b2f --- /dev/null +++ b/Olive.Mvc/Services/FileUploadService.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Olive.Entities; + +namespace Olive.Mvc +{ + // TODO: Make it flexible, to be overriden in projects. Use DI + + public class FileUploadService + { + public async Task TempSaveUploadedFile(IFormFile file) + { + var id = Guid.NewGuid().ToString(); + + var path = GetFolder(id).EnsureExists().GetFile(file.FileName.ToSafeFileName()); + + using (var stream = new MemoryStream()) + { + file.CopyTo(stream); + await File.WriteAllBytesAsync(path.FullName, stream.ToArray()); + } + + return new { ID = id, Name = file.FileName.ToSafeFileName() }; + } + + public static DirectoryInfo GetFolder(string key = null) + { + var folder = Blob.GetPhysicalFilesRoot(Blob.AccessMode.Secure) + .GetOrCreateSubDirectory("@Temp.File.Uploads").FullName; + + if (key.HasValue()) folder = Path.Combine(folder, key); + + return folder.AsDirectory(); + } + + internal async Task Bind(string fileKey) + { + if (!fileKey.StartsWith("file:")) throw new Exception("Expected file input is in the format of 'file:{GUID}'."); + + fileKey = fileKey.TrimStart("file:"); + + var folder = GetFolder(fileKey); + if (!folder.Exists()) + throw new Exception("The folder for this uploaded file does not exist: " + fileKey); + + if (folder.GetFiles().None()) + throw new Exception("There is no file in the temp folder " + fileKey); + + if (folder.GetFiles().HasMany()) + throw new Exception("There are multiple files in the temp folder " + fileKey); + + var file = folder.GetFiles().Single(); + + return new Blob(await file.ReadAllBytes(), file.Name); + } + + public static async Task DeleteTempFiles(TimeSpan olderThan) + { + foreach (var folder in GetFolder().EnsureExists().GetDirectories()) + { + // Is it Guid? + if (folder.Name.TryParseAs() == null) continue; + + // Age: + var age = LocalTime.Now.Subtract(folder.LastWriteTime); + if (age < olderThan) continue; + + await folder.Delete(recursive: true, harshly: true); + } + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Services/PaginationNavigation.cs b/Olive.Mvc/Services/PaginationNavigation.cs new file mode 100644 index 000000000..137631731 --- /dev/null +++ b/Olive.Mvc/Services/PaginationNavigation.cs @@ -0,0 +1,123 @@ +namespace Olive.Mvc +{ + using System.Collections.Generic; + using System.Text; + using Microsoft.AspNetCore.Html; + using Microsoft.AspNetCore.Mvc.Rendering; + using Microsoft.AspNetCore.Mvc.Routing; + using Olive.Web; + + class PaginationRenderer + { + IHtmlHelper Html; + ListPagination Paging; + int VisiblePages, Start, End; + object HtmlAttributes; + string Prefix; + + public PaginationRenderer(IHtmlHelper html, ListPagination paging, int visiblePages, object htmlAttributes, string prefix) + { + Html = html; + Paging = paging; + VisiblePages = visiblePages; + HtmlAttributes = htmlAttributes; + Prefix = prefix; + } + + void FindBoundaries() + { + if (Paging.CurrentPage > Paging.LastPage) Paging.CurrentPage = 1; + + Start = Paging.CurrentPage; + End = Paging.CurrentPage; + + while ((Start > 1 || End < Paging.LastPage) && End - Start < VisiblePages - 1) + { + if (Start > 1) Start--; + if (End - Start < VisiblePages - 1 && End < Paging.LastPage) End++; + } + } + + string GetPagingKey() => Paging.Prefix.WithSuffix(".") + "p"; + + string GetLinkAttributes(int number) + { + var urlHelper = new UrlHelper(Context.ActionContextAccessor.ActionContext); + + if (Paging.UseAjaxPost) + { + return "href=\"#\" formaction=\"{0}\" data-pagination=\"{1}{2}\"" + .FormatWith(urlHelper.ActionWithQuery(Paging.Container.GetType().Name + "/Reload"), Paging.Prefix.WithSuffix(".p="), Paging.GetQuery(number)); + } + else + { + var url = urlHelper.Current(new Dictionary { { GetPagingKey(), Paging.GetQuery(number) } }); + + return "href=\"{0}\"".FormatWith(url) + " data-redirect=\"ajax\"".OnlyWhen(Paging.UseAjaxGet); + } + } + + public HtmlString Render() + { + if (Paging.PageSize == null || Paging.TotalItemsCount == 0) + return null; + + if (ListPagination.DisplayForSinglePage == false && Paging.LastPage == 1) + return null; + + FindBoundaries(); + + var r = new StringBuilder(); + + if (ListPagination.WrapperCssClass.HasValue()) + r.AppendLine($"
"); + + r.AddFormattedLine("", OliveMvcExtensions.ToHtmlAttributes(HtmlAttributes)); + + var isFirst = Paging.CurrentPage == 1; + var isLast = Paging.CurrentPage == Paging.LastPage; + + // add first page control + if (Paging.ShowFirstLastLinks) + AddPaginationControl(r, Paging.FirstText, "First page", Paging.CurrentPage == 1, 1); + + // add previous page control + if (Paging.ShowPreviousNextLinks) + { + var previousPage = isFirst ? 1 : Paging.CurrentPage - 1; + AddPaginationControl(r, Paging.PreviousText, "Previous page", isFirst, previousPage); + } + + for (var i = Start; i <= End; i++) + { + r.AppendFormat("
  • ", " class=\"active\"".OnlyWhen(i == Paging.CurrentPage)); + r.AddFormattedLine("{1}
  • ", GetLinkAttributes(i), i); + } + + // add next page control + if (Paging.ShowPreviousNextLinks) + { + var nextPage = isLast ? Paging.LastPage : Paging.CurrentPage + 1; + AddPaginationControl(r, Paging.NextText, "Next page", isLast, nextPage); + } + + // add last page control + if (Paging.ShowFirstLastLinks) + AddPaginationControl(r, Paging.LastText, "Last page", isLast, Paging.LastPage); + + r.AppendLine(""); + r.AppendLineIf("
    ", ListPagination.WrapperCssClass.HasValue()); + + return new HtmlString(r.ToString()); + } + + void AddPaginationControl(StringBuilder builder, string text, string ariaText, bool isDisabled, int pageNumber) + { + builder.AppendFormat("", " class=\"disabled\"".OnlyWhen(isDisabled)); + builder.AddFormattedLine("", GetLinkAttributes(pageNumber), ariaText); + builder.AddFormattedLine("{0}", text); + builder.AppendLine(""); + builder.AppendLine(""); + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Services/TempFileService.cs b/Olive.Mvc/Services/TempFileService.cs new file mode 100644 index 000000000..5c7a62be0 --- /dev/null +++ b/Olive.Mvc/Services/TempFileService.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Olive.Web; + +namespace Olive.Mvc +{ + public class TempFileService + { + public static async Task Download(string key) + { + var folder = FileUploadService.GetFolder(key); + if (!folder.Exists()) return CreateError("The folder does not exist for key: " + key); + + var files = folder.GetFiles(); + + if (files.None()) return CreateError("There is no file for key: " + key); + if (files.HasMany()) return CreateError("There are multiple files for the key: " + key); + + var file = files.Single(); + + return new FileContentResult(await file.ReadAllBytes(), "application/octet-stream") { FileDownloadName = file.Name }; + } + + static FileContentResult CreateError(string errorText) + { + var bytes = Encoding.ASCII.GetBytes(errorText); + + return new FileContentResult(bytes, "application/octet-stream") { FileDownloadName = "Error.txt" }; + } + + public static async Task CreateDownloadAction(byte[] data, string filename) + { + var key = Guid.NewGuid().ToString(); + var folder = FileUploadService.GetFolder(key).EnsureExists(); + await folder.GetFile(filename).WriteAllBytes(data); + + var url = "/temp-file/" + key; + + Context.Http.Perform(c => url = c.GetUrlHelper().Content("~" + url)); + + return new { Download = url }; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/TagHelpers/PrefixIdentificationsTagHelper.cs b/Olive.Mvc/TagHelpers/PrefixIdentificationsTagHelper.cs new file mode 100644 index 000000000..8538bce37 --- /dev/null +++ b/Olive.Mvc/TagHelpers/PrefixIdentificationsTagHelper.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Olive.Mvc +{ + [HtmlTargetElement(Attributes = PREFIX_ATTRIBUTE_NAME)] + public class PrefixIdentificationsTagHelper : TagHelper + { + const string PREFIX_ATTRIBUTE_NAME = "asp-prefix"; + + [HtmlAttributeName(PREFIX_ATTRIBUTE_NAME)] + public string Prefix { get; set; } + + public override int Order => 0; + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await base.ProcessAsync(context, output); + + var newName = $"{Prefix}.{output.Attributes.FirstOrDefault(att => att.Name == "name").Value}"; + + output.ReplaceIdentificationAttributes(newName); + } + } +} diff --git a/Olive.Mvc/TagHelpers/ReplaceIdentificationsTagHelper.cs b/Olive.Mvc/TagHelpers/ReplaceIdentificationsTagHelper.cs new file mode 100644 index 000000000..18316c57f --- /dev/null +++ b/Olive.Mvc/TagHelpers/ReplaceIdentificationsTagHelper.cs @@ -0,0 +1,30 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Olive.Mvc +{ + [HtmlTargetElement(Attributes = REPLACE_THIS_ATTRIBUTE_NAME + "," + WITH_THIS_ATTRIBUTE_NAME)] + public class ReplaceIdentificationsTagHelper : TagHelper + { + const string REPLACE_THIS_ATTRIBUTE_NAME = "asp-replace-this"; + const string WITH_THIS_ATTRIBUTE_NAME = "asp-with-this"; + + [HtmlAttributeName(REPLACE_THIS_ATTRIBUTE_NAME)] + public string ReplaceThis { get; set; } + + [HtmlAttributeName(WITH_THIS_ATTRIBUTE_NAME)] + public string WithThis { get; set; } + + public override int Order => 0; + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + await base.ProcessAsync(context, output); + + var newName = output.Attributes.First(att => att.Name == "name").Value.ToString().Replace(ReplaceThis, WithThis); + + output.ReplaceIdentificationAttributes(newName); + } + } +} diff --git a/Olive.Mvc/TagHelpers/SelectTagHelper.cs b/Olive.Mvc/TagHelpers/SelectTagHelper.cs new file mode 100644 index 000000000..2aeba90c3 --- /dev/null +++ b/Olive.Mvc/TagHelpers/SelectTagHelper.cs @@ -0,0 +1,44 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Olive.Entities; + +namespace Olive.Mvc +{ + [HtmlTargetElement("select", Attributes = "asp-for")] + [HtmlTargetElement("select", Attributes = "asp-items")] + //[HtmlTargetElement("select")] + public class SelectTagHelper : Microsoft.AspNetCore.Mvc.TagHelpers.SelectTagHelper + { + public SelectTagHelper(IHtmlGenerator generator) : base(generator) + { + } + + public override int Order => 0; + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var isEntity = For?.Model?.GetType()?.IsA() ?? false; + + // It should clear the original tag helper effects because it will call the base method again. + output.PreContent.Clear(); + output.Content.Clear(); + output.PostContent.Clear(); + + await base.ProcessAsync(context, output); + + if (isEntity) + { + var postContent = output.PostContent.GetContent(); + + var stringId = ((IEntity)For.Model).GetId().ToString(); + + postContent = postContent.Replace($"\"{stringId}\"", $"\"{stringId}\" selected=\"selected\""); + + output.PostContent.Clear(); + + output.PostContent.SetHtmlContent(postContent); + } + } + } +} diff --git a/Olive.Mvc/TagHelpers/ValidationTranslatorTagHelper.cs b/Olive.Mvc/TagHelpers/ValidationTranslatorTagHelper.cs new file mode 100644 index 000000000..c89cffbd3 --- /dev/null +++ b/Olive.Mvc/TagHelpers/ValidationTranslatorTagHelper.cs @@ -0,0 +1,32 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Olive.Services.Globalization; + +namespace Olive.Mvc +{ + [HtmlTargetElement("select")] + [HtmlTargetElement("input")] + public class ValidationTranslatorTagHelper : TagHelper + { + static bool TranslateValidators = Config.Get("Translate.Validators", defaultValue: false); + static string[] ValidationTextAttributes = new[] { "data-val-length", "data-val-required", "data-val-email" }; + + public override int Order => int.MaxValue; + + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (TranslateValidators) + { + var temp = output.Attributes + .Where(att => att.Name.IsAnyOf(ValidationTextAttributes)) + .Select(att => new { Name = att.Name, Value = att.Value.ToString() }).ToArray(); + + foreach (var att in temp) + output.Attributes.SetAttribute(att.Name, Translator.Translate(att.Value)); + } + + await base.ProcessAsync(context, output); + } + } +} diff --git a/Olive.Mvc/Utilities/ColumnSelection.cs b/Olive.Mvc/Utilities/ColumnSelection.cs new file mode 100644 index 000000000..346a22c78 --- /dev/null +++ b/Olive.Mvc/Utilities/ColumnSelection.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Rendering; +using Olive.Web; + +namespace Olive.Mvc +{ + public class ColumnSelection + { + List Current; + string cookieKey, Prefix; + + public ColumnSelection(string prefix = null) => Prefix = prefix; + + public List Options { get; set; } + + public IEnumerable GetListOptions() + { + var result = new List(); + result.AddRange(Options); + return result; + } + + public async Task> GetCurrent() + { + if (Current != null) return Current; + + SetSelection((await CookieProperty.Get(CookieKey)).OrEmpty().Split('|')); + + return Current; + } + + public List Default { get; set; } + + [ReadOnly(true)] + public string CookieKey + { + get + { + if (cookieKey == null) + cookieKey = Context.ActionContextAccessor.ActionContext.RouteData.Values["action"] + + Prefix.WithPrefix(".") + ".Columns"; + + return cookieKey; + } + set + { + cookieKey = value; + } + } + + public bool Contains(string column) => Current.Contains(column); + + internal void SetSelection(IEnumerable selectedColumns) + { + selectedColumns = selectedColumns.Or(new string[0]).Intersect(Options); + + Current = selectedColumns.Or(Default).ToList(); + + if (Current.IsEquivalentTo(Default)) + CookieProperty.Remove(CookieKey); + else + CookieProperty.Set(CookieKey, Current.ToString("|")); + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/Controller.File.cs b/Olive.Mvc/Utilities/Controller.File.cs new file mode 100644 index 000000000..1e49d3386 --- /dev/null +++ b/Olive.Mvc/Utilities/Controller.File.cs @@ -0,0 +1,26 @@ +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Olive.Entities; + +namespace Olive.Mvc +{ + partial class Controller : Microsoft.AspNetCore.Mvc.Controller + { + /// + /// Gets a FilePathResult based on the file's path. It sets the mime type based on the file's extension. + /// + /// If specified, the browser will not try to process the file directly (such as PDF files) and instead always opens the file download dialogue. + protected async Task File(Blob file, string downloadFileName = null) => + File(await file.GetFileData(), file.GetMimeType(), downloadFileName.Or(file.FileName)); + + protected JsonResult NonobstructiveFile(byte[] data, string filename) => + AddAction(TempFileService.CreateDownloadAction(data, filename)); + + protected async Task NonobstructiveFile(Blob file, string downloadFileName = null) => + NonobstructiveFile(await file.GetFileData(), downloadFileName.Or(file.FileName)); + + protected async Task NonobstructiveFile(FileInfo file) => + NonobstructiveFile(await file.ReadAllBytes(), file.Name); + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/Controller.cs b/Olive.Mvc/Utilities/Controller.cs new file mode 100644 index 000000000..0029c3c00 --- /dev/null +++ b/Olive.Mvc/Utilities/Controller.cs @@ -0,0 +1,264 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.CodeAnalysis; +using Olive.Entities; +using Olive.Services.Globalization; +using Olive.Web; + +namespace Olive.Mvc +{ + /// + /// Provides methods that respond to HTTP requests that are made to an ASP.NET MVC Web site. + /// + [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)] + public abstract partial class Controller : Microsoft.AspNetCore.Mvc.Controller + { + /// + /// Initializes a new instance of the System.Web.Mvc.Controller class. + /// + protected Controller() + { + ModelState.Clear(); + HttpContext.Items["ViewBag"] = ViewBag; + } + + protected static IDatabase Database => Entities.Data.Database.Instance; + + /// + /// Provides a dummy Html helper. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public HtmlHelper Html + { + get + { + throw new NotImplementedException("The following code is commented to fix on the test time."); + // return new HtmlHelper(new ViewContext(ControllerContext, + // new RazorView(ControllerContext, "unknownPath", "unknownLayout", false, null), + // new ViewDataDictionary(), + // new TempDataDictionary(), TextWriter.Null), new ViewPage()); + } + } + + /// + /// Do not use this overload. Always provide a viewmodel as a parameter. + /// + protected new internal ViewResult View() => + throw new InvalidOperationException("View() method should not be called without specifying a view model."); + + /// + /// Creates a ViewResult object by using the model that renders a view to the response. + /// + protected new internal async Task View(object model) + { + AddAction(await NotificationAction.GetScheduledNotification()); + return base.View(model); + } + + /// + /// Creates a ViewResult object by using the model that renders a view to the response. + /// + protected new internal async Task View(string viewName) + { + AddAction(await NotificationAction.GetScheduledNotification()); + return base.View(viewName); + } + + /// + /// Creates a ViewResult object by using the model that renders a view to the response. + /// + protected new internal async Task View(string viewName, object model) + { + AddAction(await NotificationAction.GetScheduledNotification()); + return base.View(viewName, model); + } + + /// + /// Gets HTTP-specific information about an individual HTTP request. + /// + public new HttpContext HttpContext => base.HttpContext ?? Context.Http; + + /// + /// Creates a new instance of the specified view model type and binds it using the standard request data. + /// + + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + await BindAttributeRunner.Run(context, next); + await base.OnActionExecutionAsync(context, next); + } + + public override async Task TryUpdateModelAsync(TModel model) + { + if (await base.TryUpdateModelAsync(model)) + { + await BindAttributeRunner.BindOn(this, (IViewModel)model); + return true; + } + + return false; + } + + protected new HttpRequest Request => HttpContext.Request; + + protected List Actions => + (List)(HttpContext.Items["JavascriptActions"] ?? (HttpContext.Items["JavascriptActions"] = new List())); + + [NonAction] + protected JsonResult AddAction(object action) + { + if (action != null) Actions.Add(action); + + return JsonActions(); + } + + [NonAction] + protected JsonResult JsonActions() => Json(Actions); + + [NonAction] + protected async Task JsonActions(IViewModel info) + { + if (Request.IsAjaxCall()) return Json(Actions); + else return await View(info); + } + + [NonAction] + protected JsonResult Notify(object message, bool obstruct = true) => Notify(message, style: null, obstruct: obstruct); + + [NonAction] + protected JsonResult Notify(object message, string style, bool obstruct = true) => + AddAction(new NotificationAction { Notify = message.ToStringOrEmpty(), Style = style, Obstruct = obstruct }); + + [NonAction] + public JsonResult JavaScript(string script) => JavaScript(script, PageLifecycleStage.Init); + + [NonAction] + public JsonResult JavaScript(string script, PageLifecycleStage stage) => + AddAction(new { Script = script, Stage = stage.ToString() }); + + [NonAction] + public ActionResult AjaxRedirect(string url) + { + url = url.Or("#"); + if (!url.OrEmpty().ToLower().StartsWithAny("/", "http:", "https:")) url = "/" + url; + + if (Actions.OfType().Any()) + NotificationAction.ScheduleForNextRequest(Actions); + + Actions.Add(new { Redirect = url, WithAjax = true }); + + return JsonActions(); + } + + [NonAction] + public ActionResult Redirect(string url, string target = null) + { + url = url.Or("#"); + if (!url.OrEmpty().ToLower().StartsWithAny("/", "http:", "https:")) url = "/" + url; + + if (Actions.OfType().Any()) + NotificationAction.ScheduleForNextRequest(Actions); + + if (Request.IsAjaxCall() || target.HasValue()) + { + Actions.Add(new { Redirect = url, Target = target }); + + return JsonActions(); + } + else + { + return base.Redirect(url); + } + } + + [NonAction] + protected JsonResult Do(WindowAction action) + { + if (Actions.OfType().Any()) + if (new[] { WindowAction.Refresh, WindowAction.CloseModalRefreshParent }.Contains(action)) + NotificationAction.ScheduleForNextRequest(Actions); + + return AddAction(new { BrowserAction = action.ToString() }); + } + + [NonAction] + protected JsonResult ReplaceView(string text, bool htmlEncode = true) + { + if (htmlEncode) text = text.HtmlEncode(); + + return AddAction(new { ReplaceView = text }); + } + + [NonAction] + protected JsonResult ReplaceSource(string controlId, IEnumerable newItems) => + AddAction(new { ReplaceSource = controlId, Items = newItems.ToList() }); + + [NonAction] + protected ActionResult SubFormView(string subFormName, string subFormView) where TViewModel : new() => + SubFormView(subFormName, subFormView, new TViewModel()); + + [NonAction] + protected JsonResult SubFormView(string subFormName, string subFormView, object viewModel) + { + ModelState.Clear(); + + var view = RenderPartialView(subFormView, viewModel); + + return AddAction(new { SubForm = subFormName, NewItem = view }); + } + + protected async Task RenderPartialView(string viewName, object model) + { + var renderer = HttpContext.RequestServices.GetService(typeof(IViewRenderService)) as IViewRenderService; + + return await renderer.RenderToStringAsync(viewName, model); + } + + protected virtual ActionResult RedirectToLogin() => + Redirect("/login?ReturnUrl=" + HttpContext.GetUrlHelper().Current().UrlEncode()); + + /// + /// Will return the translation of the specified phrase in the language specified in user's cookie (or default language). + /// + public static async Task Translate(string phrase) => await Translator.Translate(phrase); + + /// + /// Will return the translation of the specified validation exception's message in the language specified in user's cookie (or default language). + /// If the IsMessageTranslated property is set, it will return message without extra translation. + /// + public static async Task Translate(ValidationException exception) + { + if (exception.IsMessageTranslated) return exception.Message; + else return await Translate(exception.Message); + } + + /// + /// Will return the translation of the specified markup in the language specified in user's cookie (or default language). + /// + public static async Task TranslateHtml(string markup) => await Translator.TranslateHtml(markup); + + /// + /// Creates a System.Web.Mvc.PartialViewResult object that renders a partial view, + /// by using the specified view name and model. + public new virtual PartialViewResult PartialView(string viewName, object model) => base.PartialView(viewName, model); + } + + public enum WindowAction + { + CloseModal, + CloseModalRefreshParent, + Refresh, + Close, + Back, + Print, + ShowPleaseWait + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/DatabaseFilters.cs b/Olive.Mvc/Utilities/DatabaseFilters.cs new file mode 100644 index 000000000..240aade0d --- /dev/null +++ b/Olive.Mvc/Utilities/DatabaseFilters.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using Olive.Entities; + +namespace Olive +{ + public class DatabaseFilters : List where T : IEntity + { + public void Add(Expression> criterion) => Add(Criterion.From(criterion)); + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/EmptyListItem.cs b/Olive.Mvc/Utilities/EmptyListItem.cs new file mode 100644 index 000000000..52a97be77 --- /dev/null +++ b/Olive.Mvc/Utilities/EmptyListItem.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Olive.Mvc +{ + public class EmptyListItem : SelectListItem + { + public EmptyListItem() : this("---Select---") { } + + public EmptyListItem(string text) + { + Text = text; + Value = string.Empty; + } + + public EmptyListItem(string text, string value) + { + Text = text; + Value = value.OrEmpty(); + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/ExecuteBindMethodsFilter.cs b/Olive.Mvc/Utilities/ExecuteBindMethodsFilter.cs new file mode 100644 index 000000000..0e0cd2e15 --- /dev/null +++ b/Olive.Mvc/Utilities/ExecuteBindMethodsFilter.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Olive.Mvc +{ + public static class BindAttributeRunner + { + static ConcurrentDictionary PreBindingCache = new ConcurrentDictionary(); + static ConcurrentDictionary PreBoundCache = new ConcurrentDictionary(); + static ConcurrentDictionary BoundCache = new ConcurrentDictionary(); + + public static Task Run(ActionExecutingContext context, ActionExecutionDelegate next) + { + var args = context.ActionArguments.Select(x => x.Value).OfType().ToArray(); + return BindOn(context.Controller, args); + } + + public static async Task BindOn(object controller, params IViewModel[] items) + { + foreach (var item in items) + await Run(item, controller); + + foreach (var item in items) + await Run(item, controller); + + foreach (var item in items) + await Run(item, controller); + } + + internal static async Task Run(IViewModel viewModel, object controller) + where TAttribute : Attribute + { + var methods = FindMethods(viewModel, controller); + await InvokeMethods(methods, controller, viewModel); + } + + static MethodInfo[] FindMethods(IViewModel viewModel, object controller) + where TAtt : Attribute + { + var key = GetKey(controller, viewModel); + return GetCache().GetOrAdd(key, + t => + { + var methods = controller.GetType().GetMethods().Where(m => m.Defines()).ToArray(); + + methods = methods.Where(m => m.GetParameters().IsSingle() + && m.GetParameters().First().ParameterType == viewModel.GetType()).ToArray(); + + return methods; + }); + } + + static Task InvokeMethods(MethodInfo[] methods, object controller, object viewModel) + { + foreach (var info in viewModel.GetType().GetProperties()) + { + var nestedValue = info.GetValue(viewModel); + if (info.PropertyType.IsA() && nestedValue != null) + InvokeMethods(methods, controller, nestedValue); + } + + var tasks = new List(); + + foreach (var method in methods) + { + var result = method.Invoke(controller, new[] { viewModel }); + if (result is Task task) tasks.Add(task); + } + + return Task.WhenAll(tasks); + } + + static ConcurrentDictionary GetCache() + { + if (typeof(TAttribute) == typeof(OnPreBindingAttribute)) return PreBindingCache; + else if (typeof(TAttribute) == typeof(OnPreBoundAttribute)) return PreBoundCache; + else if (typeof(TAttribute) == typeof(OnBoundAttribute)) return BoundCache; + else throw new NotSupportedException(typeof(TAttribute) + " is not supported!!"); + } + + static string GetKey(object controller, IViewModel viewModel) + { + return controller.GetType().FullName + "|" + viewModel.GetType().FullName; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/ITaskManager.cs b/Olive.Mvc/Utilities/ITaskManager.cs new file mode 100644 index 000000000..74b278331 --- /dev/null +++ b/Olive.Mvc/Utilities/ITaskManager.cs @@ -0,0 +1,7 @@ +namespace Olive.Mvc +{ + public interface ITaskManager + { + void Run(); + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/JsonHandlerAttribute.cs b/Olive.Mvc/Utilities/JsonHandlerAttribute.cs new file mode 100644 index 000000000..28e4b76d7 --- /dev/null +++ b/Olive.Mvc/Utilities/JsonHandlerAttribute.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Olive.Mvc +{ + public class JsonHandlerAttribute : ActionFilterAttribute + { + public override void OnActionExecuted(ActionExecutedContext filterContext) + { + if (filterContext.Result is JsonResult jsonResult) + filterContext.Result = new JsonNetResult(jsonResult.Value, jsonResult.SerializerSettings); + + base.OnActionExecuted(filterContext); + } + } +} diff --git a/Olive.Mvc/Utilities/JsonNetResult.cs b/Olive.Mvc/Utilities/JsonNetResult.cs new file mode 100644 index 000000000..a0aef31ee --- /dev/null +++ b/Olive.Mvc/Utilities/JsonNetResult.cs @@ -0,0 +1,54 @@ +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; + +namespace Olive.Mvc +{ + public class JsonNetResult : JsonResult + { + public JsonNetResult(object value) : base(value) + { + Settings = new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Error, + Formatting = Formatting.Indented + }; + } + + public JsonNetResult(object value, JsonSerializerSettings serializerSettings) : base(value, serializerSettings) + { + Settings = new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Error, + Formatting = Formatting.Indented + }; + } + + public JsonSerializerSettings Settings { get; } + + public override async Task ExecuteResultAsync(ActionContext context) + { + if (context == null) throw new ArgumentNullException(nameof(context)); + + var response = context.HttpContext.Response; + response.ContentType = string.IsNullOrEmpty(ContentType) ? "application/json" : ContentType; + + if (Value == null) return; + + using (var writer = new StringWriter()) + { + var scriptSerializer = JsonSerializer.Create(Settings); + scriptSerializer.Serialize(writer, Value); + + var data = Encoding.UTF8.GetBytes(writer.ToString()); + + await response.Body.WriteAsync(data, 0, data.Length); + } + + return; + } + } +} diff --git a/Olive.Mvc/Utilities/ListPagination.cs b/Olive.Mvc/Utilities/ListPagination.cs new file mode 100644 index 000000000..6e8c1c493 --- /dev/null +++ b/Olive.Mvc/Utilities/ListPagination.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Rendering; +using Olive.Entities; + +namespace Olive.Mvc +{ + public class ListPagination + { + public static string DefaultFirstText = "«"; + public static string DefaultPreviousText = "‹"; + public static string DefaultNextText = "›"; + public static string DefaultLastText = "»"; + + public static bool DisplayForSinglePage = false; + public static string WrapperCssClass; + public static bool DefaultShowFirstLastLinks = true; + public static bool DefaultShowPreviousNextLinks = false; + + public bool ShowFirstLastLinks = DefaultShowFirstLastLinks; + public bool ShowPreviousNextLinks = DefaultShowPreviousNextLinks; + + public int CurrentPage { get; set; } + public int? PageSize { get; set; } + public int TotalItemsCount { get; set; } + public string Prefix { get; set; } + public bool UseAjaxPost { get; set; } + public bool UseAjaxGet { get; set; } + + public string FirstText { get; set; } = DefaultFirstText; + public string PreviousText { get; set; } = DefaultPreviousText; + public string NextText { get; set; } = DefaultNextText; + public string LastText { get; set; } = DefaultLastText; + + public List SizeOptions = new List(); + public IViewModel Container; + + public int LastPage + { + get + { + if (PageSize == null) return 0; + return (int)Math.Ceiling(1.0 * TotalItemsCount / PageSize.Value); + } + } + + public ListPagination(IViewModel container, int? pageSize = null) + { + CurrentPage = 1; + PageSize = pageSize; + Container = container; + } + + public ListPagination(IViewModel container, string queryInfo) + : this(container) + { + if (queryInfo.IsEmpty()) return; + + var parts = queryInfo.Split('-'); + CurrentPage = parts.First().TryParseAs() ?? 1; + if (CurrentPage < 1) CurrentPage = 1; + + if (parts.HasMany()) + { + PageSize = parts.ElementAt(1).TryParseAs(); + if (PageSize < 1) PageSize = null; + } + } + + public override string ToString() => CurrentPage + PageSize.ToStringOrEmpty().WithPrefix("-"); + + public void Clear() => SizeOptions.Clear(); + + public void AddPageSizeOptions(params object[] options) + { + foreach (var option in options) + { + var text = option.ToStringOrEmpty(); + + var size = text.TryParseAs(); + + SizeOptions.Add(new SelectListItem + { + Text = text, + Value = 1 + size.ToString().WithPrefix("-"), + Selected = size == PageSize + }); + } + } + + public string GetQuery(int pageNumber) => pageNumber + PageSize.ToStringOrEmpty().WithPrefix("-"); + + public PagingQueryOption ToQueryOption(string orderBy = null) + { + orderBy = orderBy.Or("ID"); + + var start = CurrentPage * PageSize ?? 0; + var size = PageSize ?? TotalItemsCount; + + return new PagingQueryOption(orderBy, start, size); + } + + public PagingQueryOption ToQueryOption(ListSortExpression sort) + { + var orderBy = sort.Expression.Or("ID") + " DESC".OnlyWhen(sort.Descending); + + return ToQueryOption(orderBy); + } + } + + public class ColumnSelectionBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + + if (value == null) return Task.CompletedTask; ; + + var result = bindingContext.Model as ColumnSelection; + + result?.SetSelection(value.FirstValue.OrEmpty().Split('|')); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/ListSortExpression.cs b/Olive.Mvc/Utilities/ListSortExpression.cs new file mode 100644 index 000000000..d7770282a --- /dev/null +++ b/Olive.Mvc/Utilities/ListSortExpression.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Olive.Web; + +namespace Olive.Mvc +{ + public class ListSortExpression + { + public string Expression { get; set; } + public bool Descending { get; set; } + public string Prefix { get; set; } + public bool UseAjaxPost { get; set; } + public IViewModel Container { get; set; } + + public ListSortExpression(IViewModel container) { Container = container; } + + public ListSortExpression(IViewModel container, string expression) : this(container) + { + Descending = expression?.EndsWith(".DESC") ?? false; + Expression = expression?.TrimEnd(".DESC"); + } + + public IEnumerable Apply(IEnumerable items, Func expression) + { + if (Descending) return items.OrderByDescending(expression); + else return items.OrderBy(expression); + } + + public IEnumerable Apply(IEnumerable items) + { + if (items == null) return Enumerable.Empty(); + + if (Expression.IsEmpty()) return items; + + try + { + if (Descending) return items.OrderByDescending(Expression); + else return items.OrderBy(Expression); + } + catch (Exception ex) + { + throw new Exception("Could not sort the " + typeof(T).Name.ToPlural() + " list with expression: " + Expression, ex); + } + } + + public override string ToString() => Expression + ".DESC".OnlyWhen(Descending); + + public string Url(string sortExpression) + { + sortExpression = sortExpression.OrEmpty(); + + if (UseAjaxPost) + return Context.HttpContextAccessor.HttpContext.GetUrlHelper().ActionWithQuery(Container.GetType().Name + "/Reload"); + else + return UrlForGet(sortExpression); + } + + string UrlForGet(string sortExpression) + { + var result = Context.HttpContextAccessor.HttpContext.GetUrlHelper().CurrentUri(); + + var queryKey = Prefix.WithSuffix(".").ToLower() + "s"; + + var currentSort = result.GetQueryString().TryGet(queryKey).Or(Expression); + + if (currentSort.HasValue()) + { + if (currentSort == sortExpression) sortExpression += ".DESC"; + else if (currentSort == sortExpression + ".DESC") sortExpression = sortExpression.TrimEnd(".DESC"); + } + + result = result.RemoveQueryString(queryKey).AddQueryString(queryKey, sortExpression); + + return result.PathAndQuery; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/MenuItem.cs b/Olive.Mvc/Utilities/MenuItem.cs new file mode 100644 index 000000000..2d1e42bd2 --- /dev/null +++ b/Olive.Mvc/Utilities/MenuItem.cs @@ -0,0 +1,24 @@ +using Olive.Web; + +namespace Olive.Mvc +{ + public class MenuItem + { + public string Key { get; set; } + + public string Url { get; set; } + + public MenuItem(string key, string url) + { + Key = key; + Url = url; + } + + public bool MatchesCurrentUrl() + { + var currentUrl = Context.HttpContextAccessor.HttpContext.Request.ToPathAndQuery(); + + return currentUrl.StartsWith(Url.OrEmpty().TrimAfter("?", trimPhrase: true), caseSensitive: false); + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/NotificationAction.cs b/Olive.Mvc/Utilities/NotificationAction.cs new file mode 100644 index 000000000..d15e2e30b --- /dev/null +++ b/Olive.Mvc/Utilities/NotificationAction.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Olive.Web; + +namespace Olive.Mvc +{ + class NotificationAction + { + public string Notify { get; set; } + public bool Obstruct { get; set; } + public string Style { get; set; } + + const string COOKIE_KEY = "M#.Scheduled.Notifications"; + + /// + /// Removes the notification actions from the current set of specified actions, and schedules them in the next request through the cookie. + /// + internal static void ScheduleForNextRequest(List actions) + { + var notificationActions = actions.OfType().ToList(); + + if (notificationActions.None()) return; + + notificationActions.Do(a => actions.Remove(a)); + + var json = JsonConvert.SerializeObject(new NotificationAction + { + Notify = notificationActions.Select(x => x.Notify).ToLinesString(), + Obstruct = notificationActions.First().Obstruct, + Style = notificationActions.First().Style + }); + + CookieProperty.Set(COOKIE_KEY, json); + } + + internal static async Task GetScheduledNotification() + { + var value = await CookieProperty.Get(COOKIE_KEY); + + if (value.IsEmpty()) return null; + + CookieProperty.Remove(COOKIE_KEY); + + return JsonConvert.DeserializeObject(value); + } + } +} diff --git a/Olive.Mvc/Utilities/OptionalBooleanFilter.cs b/Olive.Mvc/Utilities/OptionalBooleanFilter.cs new file mode 100644 index 000000000..071520b7e --- /dev/null +++ b/Olive.Mvc/Utilities/OptionalBooleanFilter.cs @@ -0,0 +1,52 @@ +namespace Olive.Mvc +{ + public class OptionalBooleanFilter + { + public readonly bool? Value; + public readonly static OptionalBooleanFilter Null = new OptionalBooleanFilter(null); + public readonly static OptionalBooleanFilter True = new OptionalBooleanFilter(true); + public readonly static OptionalBooleanFilter False = new OptionalBooleanFilter(false); + + private OptionalBooleanFilter(bool? value) => Value = value; + + public override string ToString() + { + if (Value == null) return "Null"; + else return Value.Value.ToString(); + } + + public static OptionalBooleanFilter Parse(string text) + { + text = text.OrEmpty().ToLower(); + + if (text == "null") return Null; + + if (text.IsAnyOf("true", "yes")) return True; + + if (text.IsAnyOf("false", "no")) return False; + + return null; + } + + public static implicit operator OptionalBooleanFilter(bool? value) => new OptionalBooleanFilter(value); + + public static implicit operator bool? (OptionalBooleanFilter value) => value.Value; + + public override int GetHashCode() => Value.GetHashCode(); + + public override bool Equals(object obj) + { + if (obj == null) return false; + + try + { + return Value == ((OptionalBooleanFilter)obj).Value; + } + catch + { + // No logging is needed + return false; + } + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/PageLifecycleStage.cs b/Olive.Mvc/Utilities/PageLifecycleStage.cs new file mode 100644 index 000000000..49277a8fb --- /dev/null +++ b/Olive.Mvc/Utilities/PageLifecycleStage.cs @@ -0,0 +1,8 @@ +namespace Olive.Mvc +{ + public enum PageLifecycleStage + { + PreInit, + Init + } +} diff --git a/Olive.Mvc/Utilities/RangeFileContentResult.cs b/Olive.Mvc/Utilities/RangeFileContentResult.cs new file mode 100644 index 000000000..cce5e1ca3 --- /dev/null +++ b/Olive.Mvc/Utilities/RangeFileContentResult.cs @@ -0,0 +1,31 @@ +namespace Olive.Mvc +{ + using System; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Olive.Entities; + + public class RangeFileContentResult : RangeFileResult + { + public byte[] FileContents { get; private set; } + + public RangeFileContentResult(byte[] fileContents, string contentType, string fileName, DateTime modificationDate) + : base(contentType, fileName, modificationDate, fileContents.Length) => + FileContents = fileContents ?? throw new ArgumentNullException(nameof(fileContents)); + + public static async Task From(Blob blob) + { + var data = await blob.GetFileData(); + var mime = blob.GetMimeType(); + return new RangeFileContentResult(data, mime, blob.FileName, LocalTime.Now); + } + + protected override void WriteEntireEntity(HttpResponse response) => + response.Body.Write(FileContents, 0, FileContents.Length); + + protected override void WriteEntityRange(HttpResponse response, long rangeStartIndex, long rangeEndIndex) + { + response.Body.Write(FileContents, Convert.ToInt32(rangeStartIndex), Convert.ToInt32(rangeEndIndex - rangeStartIndex) + 1); + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/RangeFileResult.cs b/Olive.Mvc/Utilities/RangeFileResult.cs new file mode 100644 index 000000000..08992912f --- /dev/null +++ b/Olive.Mvc/Utilities/RangeFileResult.cs @@ -0,0 +1,281 @@ +using System; +using System.Globalization; +using System.Security.Cryptography; +using System.Text; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Olive.Mvc +{ + public abstract class RangeFileResult : ActionResult + { + static string[] DateFormats = new string[] { "r", "dddd, dd-MMM-yy HH':'mm':'ss 'GMT'", "ddd MMM d HH':'mm':'ss yyyy" }; + + const int HTTP_STATUS_CODE_OK = 200; + const int HTTP_STATUS_CODE_PARTIAL_CONTENT = 206; + const int HTTP_STATUS_CODE_PAYLOAD_TO_LONG = 413; + const int HTTP_STATUS_CODE_BAD_REQUEST = 400; + const int HTTP_STATUS_CODE_PRECONDITION_FAILED = 412; + const int HTTP_STATUS_CODE_NOT_MODIFIED = 304; + + const int ADDITIONAL_CONTENT_LENGTH = 49; + + DateTime HttpModificationDate; + string EntityTag; + long[] RangesStartIndexes, RangesEndIndexes; + bool RangeRequest, MultipartRequest; + + protected RangeFileResult(string contentType, string fileName, DateTime modificationDate, long fileLength) + { + if (contentType.IsEmpty()) throw new ArgumentNullException(nameof(contentType)); + + ContentType = contentType; + FileName = fileName; + FileModificationDate = modificationDate; + HttpModificationDate = modificationDate.ToUniversal(); + HttpModificationDate = new DateTime(HttpModificationDate.Year, HttpModificationDate.Month, HttpModificationDate.Day, HttpModificationDate.Hour, HttpModificationDate.Minute, HttpModificationDate.Second, DateTimeKind.Utc); + FileLength = fileLength; + } + + protected virtual string GenerateEntityTag(ActionContext context) + { + var entityTagBytes = Encoding.ASCII.GetBytes($"{FileName}|{FileModificationDate}"); + return Convert.ToBase64String(new MD5CryptoServiceProvider().ComputeHash(entityTagBytes)); + } + + public string ContentType { get; private set; } + + public string FileName { get; private set; } + + public DateTime FileModificationDate { get; private set; } + + public long FileLength { get; private set; } + + protected abstract void WriteEntireEntity(HttpResponse response); + + protected abstract void WriteEntityRange(HttpResponse response, long rangeStartIndex, long rangeEndIndex); + + public override void ExecuteResult(ActionContext context) + { + EntityTag = GenerateEntityTag(context); + FillRanges(context.HttpContext.Request); + + if (!(IsRangesValid(context.HttpContext.Response) && + IsModificationDateValid(context.HttpContext.Request, context.HttpContext.Response) && + IsEntityTagValid(context.HttpContext.Request, context.HttpContext.Response))) + return; + + context.HttpContext.Response.Headers.Add("Last-Modified", FileModificationDate.ToString("r")); + context.HttpContext.Response.Headers.Add("ETag", string.Format("\"{0}\"", EntityTag)); + context.HttpContext.Response.Headers.Add("Accept-Ranges", "bytes"); + + if (!RangeRequest) + { + context.HttpContext.Response.Headers.Add("Content-Length", FileLength.ToString()); + context.HttpContext.Response.ContentType = ContentType; + context.HttpContext.Response.StatusCode = HTTP_STATUS_CODE_OK; + if (context.HttpContext.Request.Method != "HEAD") + WriteEntireEntity(context.HttpContext.Response); + } + else + { + string boundary = "---------------------------" + LocalTime.Now.Ticks.ToString("x"); + + context.HttpContext.Response.Headers.Add("Content-Length", GetContentLength(boundary).ToString()); + if (!MultipartRequest) + { + context.HttpContext.Response.Headers.Add("Content-Range", string.Format("bytes {0}-{1}/{2}", RangesStartIndexes[0], RangesEndIndexes[0], FileLength)); + context.HttpContext.Response.ContentType = ContentType; + } + else + context.HttpContext.Response.ContentType = string.Format("multipart/byteranges; boundary={0}", boundary); + context.HttpContext.Response.StatusCode = HTTP_STATUS_CODE_PARTIAL_CONTENT; + if (context.HttpContext.Request.Method != "HEAD") + { + for (int i = 0; i < RangesStartIndexes.Length; i++) + { + if (MultipartRequest) + { + context.HttpContext.Response.WriteAsync(string.Format("--{0}\r\n", boundary)).RunSynchronously(); + context.HttpContext.Response.WriteAsync(string.Format("Content-Type: {0}\r\n", ContentType)).RunSynchronously(); + context.HttpContext.Response.WriteAsync(string.Format("Content-Range: bytes {0}-{1}/{2}\r\n\r\n", RangesStartIndexes[i], RangesEndIndexes[i], FileLength)).RunSynchronously(); + } + + if (!context.HttpContext.RequestAborted.IsCancellationRequested) + { + WriteEntityRange(context.HttpContext.Response, RangesStartIndexes[i], RangesEndIndexes[i]); + if (MultipartRequest) + context.HttpContext.Response.WriteAsync("\r\n").RunSynchronously(); + } + else + return; + } + + if (MultipartRequest) context.HttpContext.Response.WriteAsync(string.Format("--{0}--", boundary)).RunSynchronously(); + } + } + } + + string GetHeader(HttpRequest request, string header, string defaultValue = "") + { + return request.Headers[header].ToString().IsEmpty() ? defaultValue : + request.Headers[header].ToString().Replace("\"", string.Empty); + } + + void FillRanges(HttpRequest request) + { + string rangesHeader = GetHeader(request, "Range"); + string ifRangeHeader = GetHeader(request, "If-Range", EntityTag); + bool isIfRangeHeaderDate = DateTime.TryParseExact(ifRangeHeader, DateFormats, null, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime ifRangeHeaderDate); + + if (rangesHeader.IsEmpty() || (!isIfRangeHeaderDate && ifRangeHeader != EntityTag) || (isIfRangeHeaderDate && HttpModificationDate > ifRangeHeaderDate)) + { + RangesStartIndexes = new long[] { 0 }; + RangesEndIndexes = new long[] { FileLength - 1 }; + RangeRequest = false; + MultipartRequest = false; + } + else + { + var ranges = rangesHeader.Remove("bytes=").Split(','); + + RangesStartIndexes = new long[ranges.Length]; + RangesEndIndexes = new long[ranges.Length]; + RangeRequest = true; + MultipartRequest = ranges.HasMany(); + + for (int i = 0; i < ranges.Length; i++) + { + var currentRange = ranges[i].Split('-'); + + if (currentRange[1].IsEmpty()) + RangesEndIndexes[i] = FileLength - 1; + else + RangesEndIndexes[i] = currentRange[1].To(); + + if (currentRange[0].IsEmpty()) + { + RangesStartIndexes[i] = FileLength - 1 - RangesEndIndexes[i]; + RangesEndIndexes[i] = FileLength - 1; + } + else + RangesStartIndexes[i] = currentRange[0].To(); + } + } + } + + int GetContentLength(string boundary) + { + int contentLength = 0; + + for (int i = 0; i < RangesStartIndexes.Length; i++) + { + contentLength += Convert.ToInt32(RangesEndIndexes[i] - RangesStartIndexes[i]) + 1; + + if (MultipartRequest) + contentLength += boundary.Length + ContentType.Length + RangesStartIndexes[i].ToString().Length + + RangesEndIndexes[i].ToString().Length + FileLength.ToString().Length + ADDITIONAL_CONTENT_LENGTH; + } + + if (MultipartRequest) + contentLength += boundary.Length + 4; + + return contentLength; + } + + bool IsRangesValid(HttpResponse response) + { + if (FileLength > int.MaxValue) + { + response.StatusCode = HTTP_STATUS_CODE_PAYLOAD_TO_LONG; + return false; + } + + for (int i = 0; i < RangesStartIndexes.Length; i++) + { + if (RangesStartIndexes[i] > FileLength - 1 || RangesEndIndexes[i] > FileLength - 1 || RangesStartIndexes[i] < 0 || RangesEndIndexes[i] < 0 || RangesEndIndexes[i] < RangesStartIndexes[i]) + { + response.StatusCode = HTTP_STATUS_CODE_BAD_REQUEST; + return false; + } + } + + return true; + } + + bool IsModificationDateValid(HttpRequest request, HttpResponse response) + { + string modifiedSinceHeader = GetHeader(request, "If-Modified-Since"); + if (modifiedSinceHeader.HasValue()) + { + DateTime.TryParseExact(modifiedSinceHeader, DateFormats, null, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime modifiedSinceDate); + + if (HttpModificationDate <= modifiedSinceDate) + { + response.StatusCode = HTTP_STATUS_CODE_NOT_MODIFIED; + return false; + } + } + + string unmodifiedSinceHeader = GetHeader(request, "If-Unmodified-Since", GetHeader(request, "Unless-Modified-Since")); + if (unmodifiedSinceHeader.HasValue()) + { + bool unmodifiedSinceDateParsed = DateTime.TryParseExact(unmodifiedSinceHeader, DateFormats, null, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out DateTime unmodifiedSinceDate); + + if (HttpModificationDate > unmodifiedSinceDate) + { + response.StatusCode = HTTP_STATUS_CODE_PRECONDITION_FAILED; + return false; + } + } + + return true; + } + + bool IsEntityTagValid(HttpRequest request, HttpResponse response) + { + string matchHeader = GetHeader(request, "If-Match"); + if (matchHeader.HasValue() && matchHeader != "*") + { + var entitiesTags = matchHeader.Split(','); + int entitieTagIndex; + for (entitieTagIndex = 0; entitieTagIndex < entitiesTags.Length; entitieTagIndex++) + { + if (EntityTag == entitiesTags[entitieTagIndex]) + break; + } + + if (entitieTagIndex >= entitiesTags.Length) + { + response.StatusCode = HTTP_STATUS_CODE_PRECONDITION_FAILED; + return false; + } + } + + string noneMatchHeader = GetHeader(request, "If-None-Match"); + if (noneMatchHeader.HasValue()) + { + if (noneMatchHeader == "*") + { + response.StatusCode = HTTP_STATUS_CODE_PRECONDITION_FAILED; + return false; + } + + var entitiesTags = noneMatchHeader.Split(','); + foreach (string entityTag in entitiesTags) + { + if (EntityTag != entityTag) + continue; + + response.Headers.Add("ETag", $"\"{entityTag}\""); + response.StatusCode = HTTP_STATUS_CODE_NOT_MODIFIED; + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/RazorPage.cs b/Olive.Mvc/Utilities/RazorPage.cs new file mode 100644 index 000000000..8cb57a068 --- /dev/null +++ b/Olive.Mvc/Utilities/RazorPage.cs @@ -0,0 +1,47 @@ +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Http; +using Olive.Services.Globalization; + +namespace Olive.Mvc +{ + public abstract class RazorPage : Microsoft.AspNetCore.Mvc.Razor.RazorPage + { + /// + /// Gets the View Model instance to provide a consistent API to gain access to the ViewModel object from controller and View. + /// + protected virtual TModel info => Model; + + public HttpRequest Request => Context.Request; + + /// + /// Will return the translation of the specified phrase in the language specified in user's cookie (or default language). + /// + public static Task Translate(string phrase) => Translator.Translate(phrase); + + /// + /// Will return the translation of the specified markup in the language specified in user's cookie (or default language). + /// + public static async Task TranslateHtml(string markup) + { + if (markup.IsEmpty()) return HtmlString.Empty; + + return new HtmlString(await Translator.TranslateHtml(markup)); + } + + /// + /// Gets a file from its relative path. + /// + public FileInfo MapPath(string relativePath) + { + var fileProvider = Environment.ContentRootFileProvider; + var path = fileProvider.GetFileInfo(relativePath)?.PhysicalPath; + if (path.IsEmpty()) return null; + return path.AsFile(); + } + + public virtual IHostingEnvironment Environment => Web.Context.HostingEnvironment; + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/ReferencesMetadataReferenceFeatureProvider.cs b/Olive.Mvc/Utilities/ReferencesMetadataReferenceFeatureProvider.cs new file mode 100644 index 000000000..b5def9f18 --- /dev/null +++ b/Olive.Mvc/Utilities/ReferencesMetadataReferenceFeatureProvider.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection.PortableExecutable; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.DependencyModel; + +namespace Olive.Mvc +{ + public class ReferencesMetadataReferenceFeatureProvider : IApplicationFeatureProvider + { + public void PopulateFeature(IEnumerable parts, MetadataReferenceFeature feature) + { + var libraryPaths = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var assemblyPart in parts.OfType()) + { + var dependencyContext = DependencyContext.Load(assemblyPart.Assembly); + if (dependencyContext != null) + { + foreach (var library in dependencyContext.CompileLibraries) + { + if (string.Equals("reference", library.Type, StringComparison.OrdinalIgnoreCase)) + { + foreach (var libraryAssembly in library.Assemblies) + { + libraryPaths.Add(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, libraryAssembly)); + } + } + else + { + foreach (var path in library.ResolveReferencePaths()) + libraryPaths.Add(path); + } + } + } + else + { + libraryPaths.Add(assemblyPart.Assembly.Location); + } + } + + foreach (var path in libraryPaths) + feature.MetadataReferences.Add(CreateMetadataReference(path)); + } + + static MetadataReference CreateMetadataReference(string path) + { + using (var stream = File.OpenRead(path)) + { + var moduleMetadata = ModuleMetadata.CreateFromStream(stream, PEStreamOptions.PrefetchMetadata); + var assemblyMetadata = AssemblyMetadata.Create(moduleMetadata); + + return assemblyMetadata.GetReference(filePath: path); + } + } + } +} diff --git a/Olive.Mvc/Utilities/RouteTemplate.cs b/Olive.Mvc/Utilities/RouteTemplate.cs new file mode 100644 index 000000000..a87ca111f --- /dev/null +++ b/Olive.Mvc/Utilities/RouteTemplate.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Olive.Web; + +namespace Olive.Mvc +{ + public class RouteTemplate + { + public string Template; + public List Parameters = new List(); + public RouteTemplate(string pattern) + { + Template = pattern; + var remaining = pattern; + while (remaining.Contains("{")) + { + var parameter = remaining.Substring("{", "}", inclusive: true); + var key = parameter.TrimStart("{").TrimEnd("}"); + var mandatory = !key.EndsWith("?"); + key = key.TrimEnd("?"); + var type = typeof(string); + if (key.Contains(":")) + { + // TODO: Type doesn't matter in this use case. + // type = Type.GetType(key.Split(':').Last()); + key = key.Split(':').First(); + } + + Parameters.Add(new RouteTemplateParameter { Parameter = parameter, Key = key.ToCamelCaseId(), Type = type, IsMandatory = mandatory }); + remaining = remaining.Substring(remaining.IndexOf("{") + 1); + } + } + + public bool IsMatch(Dictionary routeData) + { + foreach (var p in Parameters) + { + var routeKey = routeData.Keys.FirstOrDefault(x => x.ToLower() == p.Key.ToLower()); + if (routeKey != null) + { + if (!p.MatchesType(routeData[routeKey])) + return false; + } + else if (p.IsMandatory) + { + // The value of this parameter in the route is not provided. + return false; + } + } + + return true; + } + + public IEnumerable FindMatchingParameters(Dictionary routeData) + { + foreach (var p in Parameters) + { + var routeKey = routeData.Keys.FirstOrDefault(x => x.ToLower() == p.Key.ToLower()); + if (routeKey == null) + continue; + if (!p.MatchesType(routeData[routeKey])) + continue; + yield return p; + } + } + + public override string ToString() => Template; + + /// + /// It will merge the provided route data parameters into the pattern of the template. + /// If any parameter in the template is non-optional, and yet a value has not been provided, it will throw an error. + /// If any of the provided route data parameters aren't expected in the pattern, then they will be added to the query string. + /// + internal string Merge(Dictionary routeData) + { + if (routeData == null) + throw new ArgumentNullException(nameof(routeData)); + var result = Template; + foreach (var p in Parameters) + { + var routeKey = routeData.Keys.FirstOrDefault(x => x.ToLower() == p.Key.ToLower()); + if (routeKey != null) + { + // TODO: Type check + result = result.Replace(p.Parameter, routeData[routeKey]); + routeData.Remove(routeKey); + } + else + { + if (p.IsMandatory) + { + throw new Exception("The value of " + p.Parameter + " in the route " + Template + " is not provided."); + } + + result = result.Replace(p.Parameter, null); + } + } + + result = "/" + result.KeepReplacing("//", "/").TrimEnd("/").TrimStart("/"); + result += routeData.Select(x => x.Key + "=" + x.Value.UrlEncode()).ToString("&").WithPrefix("?"); + return result; + } + + public class RouteTemplateParameter + { + public string Parameter; // Example: {key:type?} + public string Key; + public Type Type; + public bool IsMandatory; + + internal bool MatchesType(string value) + { + try + { + value.To(Type); + // No error? + return true; + } + catch + { + // No logging is needed + return false; + } + } + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/SecureFileAccessor.cs b/Olive.Mvc/Utilities/SecureFileAccessor.cs new file mode 100644 index 000000000..3da8cf58b --- /dev/null +++ b/Olive.Mvc/Utilities/SecureFileAccessor.cs @@ -0,0 +1,115 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Olive.Entities; +using Olive.Web; + +namespace Olive.Mvc +{ + public class FileAccessor + { + string[] PathParts; + + Type Type; + string Property; + PropertyInfo PropertyInfo; + IUser CurrentUser; + + public IEntity Instance { get; private set; } + + public Blob Blob { get; private set; } + + public string SecurityErrors { get; private set; } + + /// + /// Use create method to instantiate the class. + /// + private FileAccessor() { } + + /// + /// Creates a new SecureFileAccessor instance. + /// + public static async Task Create(string path, IUser currentUser) + { + var result = new FileAccessor + { + CurrentUser = currentUser, + PathParts = path.Split('/') + }; + + if (result.PathParts.Length < 2) + throw new Exception($"Invalid path specified: '{path}'"); + + result.FindRequestedProperty(); + + await result.FindRequestedObject(); + + result.SecurityErrors = result.GetSecurityErrors(); + + return result; + } + + public bool IsAllowed() => SecurityErrors.IsEmpty(); + + public FileInfo GetFile() + { + var file = Blob.LocalPath; + + // Fall-back logic + if (!File.Exists(file)) + file = Blob.FallbackPaths.FirstOrDefault(File.Exists); + + return file.AsFile(); + } + + void FindRequestedProperty() + { + var typeName = PathParts[0].Split('.')[0]; + + Type = Entity.Database.GetRegisteredAssemblies().Select(a => a.GetExportedTypes().SingleOrDefault(t => t.Name == typeName)).ExceptNull().FirstOrDefault(); + if (Type == null) throw new Exception($"Invalid type name specified: '{typeName}'"); + + Property = PathParts[0].Split('.')[1]; + + PropertyInfo = Type.GetProperty(Property); + if (PropertyInfo == null) + throw new Exception($"Could not find the property '{Property}' on the type '{Type.FullName}'."); + } + + async Task FindRequestedObject() + { + var idData = PathParts[1]; + + foreach (var key in new[] { ".", "/" }) + if (idData.Contains(key)) idData = idData.Substring(0, idData.IndexOf(key)); + + Instance = await Entity.Database.GetOrDefault(idData, Type); + + if (Instance == null) throw new Exception($"Invalid {Type.FullName} ID specified: '{idData}'"); + + Blob = EntityManager.ReadProperty(Instance, Property) as Blob; + } + + bool NeedsSecureAccess() => PropertyInfo.GetCustomAttribute() != null; + + string GetSecurityErrors() + { + if (!NeedsSecureAccess()) return null; + + var method = Type.GetMethod($"Is{Property}VisibleTo", BindingFlags.Public | BindingFlags.Instance); + + if (method == null) + return $"{Type.FullName}.Is{Property}VisibleTo() method is not defined."; + + if (method.GetParameters().Count() != 1 || !method.GetParameters().Single().ParameterType.Implements()) + return $"{Type.FullName}.{method.Name}() doesn't accept a single argument that implements IUser"; + + if (!(bool)method.Invoke(Instance, new object[] { CurrentUser })) + return "You are not authorised to view the requested file."; + + return null; + } + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/Startup.cs b/Olive.Mvc/Utilities/Startup.cs new file mode 100644 index 000000000..752a50769 --- /dev/null +++ b/Olive.Mvc/Utilities/Startup.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json.Serialization; +using Olive.Entities; +using Olive.Services.Testing; +using Olive.Web; + +namespace Olive.Mvc +{ + public abstract class Startup + { + protected virtual IViewLocationExpander GetViewLocationExpander() => new ViewLocationExpander(); + + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public virtual void ConfigureServices(IServiceCollection services) + { + services.InjectOliveDependencies(); + + var builder = services.AddMvc(o => o.ModelBinderProviders.Insert(0, new OliveBinderProvider())) + + .AddJsonOptions(o => o.SerializerSettings.ContractResolver = new DefaultContractResolver()) + + .ConfigureApplicationPartManager(manager => + { + manager.FeatureProviders.RemoveWhere(x => x is MetadataReferenceFeatureProvider); + manager.FeatureProviders.Add(new ReferencesMetadataReferenceFeatureProvider()); + }); + + services.Configure(options => + options.ViewLocationExpanders.Add(GetViewLocationExpander())); + + AddIdentityAndStores(services).AddAuthentication(IdentityConstants.ApplicationScheme); + + services.ConfigureApplicationCookie(ConfigureApplicationCookie) + .AddDistributedMemoryCache() // Adds a default in-memory implementation of IDistributedCache. + .AddSession(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public virtual void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + InitializeDatabase(app, env); + + app.ConfigureOliveDependencies(env); + + ConfigureExceptionPage(app, env); + + app.UseAuthentication() + .UseStaticFiles() + .UseRequestLocalization(RequestLocalizationOptions) + .UseSession() + .UseMvc(ConfigureRoutes); + + WebTestManager.CreateReferenceDataBy(CreateReferenceData); + WebTestManager.InitiateTempDatabase(enforceRestart: false, mustRenew: false).Wait(); + } + + protected virtual void InitializeDatabase(IApplicationBuilder app, IHostingEnvironment env) + => Entity.InitializeDatabase(Entities.Data.Database.Instance); + + protected virtual void ConfigureExceptionPage(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) app.UseDeveloperExceptionPage().UseBrowserLink(); + else app.UseExceptionHandler("/Home/Error"); + } + + protected virtual CultureInfo GetRequestCulture() => CultureInfo.CurrentCulture; + + protected virtual RequestLocalizationOptions RequestLocalizationOptions + { + get + { + var culture = GetRequestCulture(); + + return new RequestLocalizationOptions + { + DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture(culture), + SupportedCultures = new List { culture }, + SupportedUICultures = new List { culture } + }; + } + } + + protected virtual void ConfigureRoutes(IRouteBuilder routes) { } + + protected virtual void ConfigureApplicationCookie(CookieAuthenticationOptions options) + { + options.AccessDeniedPath = "/Login"; + options.LoginPath = "/Login"; + } + + protected abstract IServiceCollection AddIdentityAndStores(IServiceCollection services); + + /// Invoked by the WebTestManager right after creating a new database. + protected abstract Task CreateReferenceData(); + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/ViewComponent.cs b/Olive.Mvc/Utilities/ViewComponent.cs new file mode 100644 index 000000000..81198ba48 --- /dev/null +++ b/Olive.Mvc/Utilities/ViewComponent.cs @@ -0,0 +1,43 @@ +namespace Olive.Mvc +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Mvc; + using Olive.Entities; + using Olive.Services.Globalization; + using Olive.Web; + + public abstract class ViewComponent : Microsoft.AspNetCore.Mvc.ViewComponent + { + protected static IDatabase Database => Entities.Data.Database.Instance; + + /// + /// Gets HTTP-specific information about an individual HTTP request. + /// + public new HttpContext HttpContext => base.HttpContext ?? Context.Http; + + protected new HttpRequest Request => HttpContext?.Request; + + public ActionResult Redirect(string url) => new RedirectResult(url); + + /// + /// Will return the translation of the specified phrase in the language specified in user's cookie (or default language). + /// + public static async Task Translate(string phrase) => await Translator.Translate(phrase); + + /// + /// Will return the translation of the specified validation exception's message in the language specified in user's cookie (or default language). + /// If the IsMessageTranslated property is set, it will return message without extra translation. + /// + public static async Task Translate(ValidationException exception) + { + if (exception.IsMessageTranslated) return exception.Message; + else return await Translate(exception.Message); + } + + /// + /// Will return the translation of the specified markup in the language specified in user's cookie (or default language). + /// + public static async Task TranslateHtml(string markup) => await Translator.TranslateHtml(markup); + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/ViewLocationExpander.cs b/Olive.Mvc/Utilities/ViewLocationExpander.cs new file mode 100644 index 000000000..74c846305 --- /dev/null +++ b/Olive.Mvc/Utilities/ViewLocationExpander.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Razor; + +namespace Olive.Mvc +{ + public class ViewLocationExpander : IViewLocationExpander + { + /// + /// Used to specify the locations that the view engine should search to + /// locate views. + /// + public virtual IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) + { + // {2} is area, {1} is controller,{0} is the action + var partialViewLocationFormats = new[] { + "~/Views/Modules/{0}.cshtml", + "~/Views/Layouts/{0}.cshtml", + "~/Views/Shared/{0}.cshtml" }; + + var viewLocationFormats = new[] { + "~/Views/Modules/{0}.cshtml", + "~/Views/Pages/{1}.cshtml", + "~/Views/Modules/{1}.cshtml" }; + + return context.IsMainPage ? viewLocationFormats : partialViewLocationFormats; + } + + public virtual void PopulateValues(ViewLocationExpanderContext context) => + context.Values["customviewlocation"] = nameof(ViewLocationExpander); + } +} \ No newline at end of file diff --git a/Olive.Mvc/Utilities/ViewRenderService.cs b/Olive.Mvc/Utilities/ViewRenderService.cs new file mode 100644 index 000000000..4feaa91f0 --- /dev/null +++ b/Olive.Mvc/Utilities/ViewRenderService.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Routing; + +namespace Olive.Mvc +{ + public interface IViewRenderService + { + Task RenderToStringAsync(string viewName, object model); + } + + public class ViewRenderService : IViewRenderService + { + IRazorViewEngine RazorViewEngine; + ITempDataProvider TempDataProvider; + IServiceProvider ServiceProvider; + + public ViewRenderService(IRazorViewEngine razorViewEngine, + ITempDataProvider tempDataProvider, + IServiceProvider serviceProvider) + { + RazorViewEngine = razorViewEngine; + TempDataProvider = tempDataProvider; + ServiceProvider = serviceProvider; + } + + public async Task RenderToStringAsync(string viewName, object model) + { + var httpContext = new DefaultHttpContext { RequestServices = ServiceProvider }; + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + + using (var sw = new StringWriter()) + { + var viewResult = RazorViewEngine.FindView(actionContext, viewName, isMainPage: false); + + if (viewResult.View == null) + throw new ArgumentException($"{viewName} does not match any available view"); + + var viewDictionary = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()) + { + Model = model + }; + + var viewContext = new ViewContext( + actionContext, + viewResult.View, + viewDictionary, + new TempDataDictionary(actionContext.HttpContext, TempDataProvider), + sw, + new HtmlHelperOptions() + ); + + await viewResult.View.RenderAsync(viewContext); + return sw.ToString(); + } + } + } +} \ No newline at end of file diff --git a/Olive.Web/Authentication/IAuthenticationProvider.cs b/Olive.Web/Authentication/IAuthenticationProvider.cs new file mode 100644 index 000000000..540773cc5 --- /dev/null +++ b/Olive.Web/Authentication/IAuthenticationProvider.cs @@ -0,0 +1,16 @@ +using System; +using System.Security.Principal; +using System.Threading.Tasks; + +namespace Olive.Web +{ + public interface IAuthenticationProvider + { + Task LogOn(IUser user, string domain, TimeSpan timeout, bool remember); + Task LogOff(IUser user); + Task LoginBy(string provider); + + IUser LoadUser(IPrincipal principal); + void PreRequestHandler(string path); + } +} diff --git a/Olive.Web/Authentication/IUser.cs b/Olive.Web/Authentication/IUser.cs new file mode 100644 index 000000000..fb34cbaa5 --- /dev/null +++ b/Olive.Web/Authentication/IUser.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; +using Olive.Entities; + +namespace Olive.Web +{ + public interface IUser : IEntity + { + IEnumerable GetRoles(); + } + + public static class IUserExtensions + { + /// + /// Determines whether this user has a specified role. + /// + public static bool IsInRole(this IUser user, string role) + { + if (user == null) return false; + else return user.GetRoles().Contains(role); + } + + /// + /// Determines if this user Is Authenticated. + /// + public static bool IsAuthenticated(this IUser user) + { + if (user == null) return false; + else return !user.IsNew; + } + } +} \ No newline at end of file diff --git a/Olive.Web/Authentication/UserInfoAccessorInitializer.cs b/Olive.Web/Authentication/UserInfoAccessorInitializer.cs new file mode 100644 index 000000000..8cd7488e8 --- /dev/null +++ b/Olive.Web/Authentication/UserInfoAccessorInitializer.cs @@ -0,0 +1,18 @@ +using System.Security.Principal; +using Olive.Entities; + +namespace Olive.Web +{ + [UserInfoAccessorInitializer] + public class UserInfoAccessorInitializer + { + public static void Initialize() => + DefaultApplicationEventManager.InitializeUseAccessors(GetUserPrincipal, GetUserIP); + + static IPrincipal GetUserPrincipal() => + Context.HttpContextAccessor.HttpContext.User; + + static string GetUserIP() => + Context.HttpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString(); + } +} diff --git a/Olive.Web/Authentication/UserServices.cs b/Olive.Web/Authentication/UserServices.cs new file mode 100644 index 000000000..235a93dce --- /dev/null +++ b/Olive.Web/Authentication/UserServices.cs @@ -0,0 +1,55 @@ +using System; +using System.ComponentModel; + +namespace Olive.Web +{ + [EditorBrowsable(EditorBrowsableState.Never)] + public static class UserServices + { + const int DEFAULT_AUTHENTICATION_TIMEOUT_IN_MINUTES = 20; + + public static IAuthenticationProvider AuthenticationProvider; + + static UserServices() + { + var provider = Config.Get("Authentication:Provider"); + if (provider.HasValue()) + AuthenticationProvider = (IAuthenticationProvider)Type.GetType(provider).CreateInstance(); + else + throw new Exception("The authentication provider is not specified."); + } + + public static void LogOn(this IUser user) => LogOn(user, domain: null, remember: false); + + static void LogOn(this IUser user, string domain) => LogOn(user, domain, remember: false); + + public static void LogOn(this IUser user, bool remember) => LogOn(user, null, remember); + + public static TimeSpan GetAuthenticationTimeout() + { + try + { + var minutes = Config.Get("Authentication:Timeout", 5); + + return TimeSpan.FromMinutes(minutes); + } + catch + { + // No Logging Neede + return TimeSpan.FromMinutes(DEFAULT_AUTHENTICATION_TIMEOUT_IN_MINUTES); + } + } + + static void LogOn(this IUser user, string domain, bool remember) => + AuthenticationProvider.LogOn(user, domain, GetAuthenticationTimeout(), remember); + + public static void LogOff(this IUser user) + { + AuthenticationProvider.LogOff(user); + + Context.HttpContextAccessor.HttpContext.Session.Perform(s => s.Clear()); + } + + public static void LoginBy(string provider) => AuthenticationProvider.LoginBy(provider); + } +} \ No newline at end of file diff --git a/Olive.Web/CookieAwareWebClient.cs b/Olive.Web/CookieAwareWebClient.cs new file mode 100644 index 000000000..8bdb5d8ca --- /dev/null +++ b/Olive.Web/CookieAwareWebClient.cs @@ -0,0 +1,24 @@ +using System; +using System.Net; + +namespace Olive.Web +{ + public class CookieAwareWebClient : WebClient + { + public CookieAwareWebClient() : this(new CookieContainer()) { } + + public CookieAwareWebClient(CookieContainer container) => CookieContainer = container; + + public CookieContainer CookieContainer { get; set; } + + protected override WebRequest GetWebRequest(Uri address) + { + var result = base.GetWebRequest(address); + + if (result is HttpWebRequest castRequest) + castRequest.CookieContainer = CookieContainer; + + return result; + } + } +} \ No newline at end of file diff --git a/Olive.Web/DI/Context.cs b/Olive.Web/DI/Context.cs new file mode 100644 index 000000000..5875025eb --- /dev/null +++ b/Olive.Web/DI/Context.cs @@ -0,0 +1,54 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Olive.Web +{ + public static class Context + { + static bool isInitialized; + + static IApplicationBuilder applicationBuilder; + + static IHttpContextAccessor httpContextAccessor; + + static IActionContextAccessor actionContextAccessor; + + static IHostingEnvironment hostingEnvironment; + + public static void Initialize( + IApplicationBuilder applicationBuilder, + IHostingEnvironment hostingEnvironment, + IHttpContextAccessor httpContextAccessor, + IActionContextAccessor actionContextAccessor) + { + Context.applicationBuilder = applicationBuilder; + Context.hostingEnvironment = hostingEnvironment; + Context.httpContextAccessor = httpContextAccessor; + Context.actionContextAccessor = actionContextAccessor; + + isInitialized = true; + } + + public static IApplicationBuilder ApplicationBuilder => applicationBuilder ?? throw GetNotInitializedException(); + + public static IHostingEnvironment HostingEnvironment => hostingEnvironment ?? throw GetNotInitializedException(); + + public static IHttpContextAccessor HttpContextAccessor => httpContextAccessor ?? throw GetNotInitializedException(); + + public static IActionContextAccessor ActionContextAccessor => actionContextAccessor ?? throw GetNotInitializedException(); + + public static HttpContext Http => HttpContextAccessor.HttpContext; + + public static HttpRequest Request => Http?.Request; + + public static HttpResponse Response => Http?.Response; + + static Exception GetNotInitializedException() => + new InvalidOperationException("HttpContextAccessorHelper is not initialized"); + + public static bool IsInitialized => isInitialized; + } +} diff --git a/Olive.Web/DI/OliveDependencies.cs b/Olive.Web/DI/OliveDependencies.cs new file mode 100644 index 000000000..9fccd3387 --- /dev/null +++ b/Olive.Web/DI/OliveDependencies.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.DependencyInjection; + +namespace Olive.Web +{ + public static class OliveDependencies + { + /// + /// Inject the required dependencies. + /// It should be called in Startup.ConfigureServices + /// + public static void Inject(IServiceCollection services) + { + services.AddSingleton(typeof(IHttpContextAccessor), typeof(HttpContextAccessor)); + services.AddSingleton(typeof(IActionContextAccessor), typeof(ActionContextAccessor)); + } + + /// + /// Inject the required dependencies. + /// It should be called in Startup.ConfigureServices + /// + public static void InjectOliveDependencies(this IServiceCollection services) => Inject(services); + + /// + /// Configure the helper classes. + /// It should be called in Startup.Configure + /// + public static void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + Context.Initialize( + app, + env, + app.ApplicationServices.GetService(), + app.ApplicationServices.GetService() + ); + } + + /// + /// Configure the helper classes. + /// It should be called in Startup.Configure + /// + public static void ConfigureOliveDependencies(this IApplicationBuilder app, IHostingEnvironment env) => Configure(app, env); + } +} diff --git a/Olive.Web/Extensions/@Misc.cs b/Olive.Web/Extensions/@Misc.cs new file mode 100644 index 000000000..3fbb4d8a6 --- /dev/null +++ b/Olive.Web/Extensions/@Misc.cs @@ -0,0 +1,212 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; +using Newtonsoft.Json; +using Olive.Entities; + +namespace Olive.Web +{ + public static partial class OliveExtensions + { + /// + /// First three bytes of GZip compressed Data + /// + readonly static byte[] GZipStarter = new byte[] { 31, 139, 8 }; + + public static async Task DownloadData(this WebClient client, string address, bool handleGzip) + { + if (!handleGzip) + return await client.DownloadDataTaskAsync(address); + + var result = await client.DownloadDataTaskAsync(address); + if (result != null && result.Length > 3 && result[0] == GZipStarter[0] && result[1] == GZipStarter[1] && result[2] == GZipStarter[2]) + { + // GZIP: + using (var stream = new System.IO.Compression.GZipStream(new MemoryStream(result), System.IO.Compression.CompressionMode.Decompress)) + { + var buffer = new byte[4096]; + using (var memory = new MemoryStream()) + { + while (true) + { + var count = await stream.ReadAsync(buffer, 0, buffer.Length); + if (count > 0) await memory.WriteAsync(buffer, 0, count); + else break; + } + + return memory.ToArray(); + } + } + } + else + { + return result; + } + } + + /// + /// Posts the specified data to a url and returns the response as string. + /// All properties of the postData object will be sent as individual FORM parameters to the destination. + /// + /// An anonymous object containing post data. + public static async Task Post(this WebClient webClient, string url, object postData) + { + if (postData == null) + throw new ArgumentNullException(nameof(postData)); + + var data = new Dictionary(); + data.AddFromProperties(postData); + + return await Post(webClient, url, data); + } + + /// + /// Gets the response data as string. + /// + public static async Task GetString(this WebResponse response) + { + using (var stream = response.GetResponseStream()) + { + using (var reader = new StreamReader(stream)) + return await reader.ReadToEndAsync(); + } + } + + /// + /// Gets the response data as string. + /// + public static async Task GetResponseString(this HttpWebRequest request) + { + using (var response = request.GetResponse()) + return await response.GetString(); + } + + /// + /// Posts the specified object as JSON data to this URL. + /// + public static async Task PostJson(this Uri url, object data) + { + var req = (HttpWebRequest)WebRequest.Create(url); + + req.Method = WebRequestMethods.Http.Post; + req.ContentType = "application/json"; + + using (var stream = new StreamWriter(await req.GetRequestStreamAsync())) + await stream.WriteAsync(JsonConvert.SerializeObject(data)); + + return await req.GetResponseString(); + } + + /// + /// Posts the specified data to this url and returns the response as string. + /// All items in the postData object will be sent as individual FORM parameters to the destination. + /// + public static async Task Post(this Uri url, object data, Action customiseClient = null) + { + using (var client = new WebClient()) + { + customiseClient?.Invoke(client); + + return await client.Post(url.ToString(), data); + } + } + + /// + /// Posts the specified data to this url and returns the response as string. + /// All items in the postData object will be sent as individual FORM parameters to the destination. + /// + public static async Task Post(this Uri url, Dictionary postData, Action customiseClient = null) + { + using (var client = new WebClient()) + { + customiseClient?.Invoke(client); + return await client.Post(url.ToString(), postData); + } + } + + /// + /// Posts the specified data to a url and returns the response as string. + /// All items in the postData object will be sent as individual FORM parameters to the destination. + /// + public static async Task Post(this WebClient webClient, string url, Dictionary postData) => + await Post(webClient, url, postData, Encoding.UTF8); + + /// + /// Posts the specified data to a url and returns the response as string. + /// + public static async Task Post(this WebClient webClient, string url, Dictionary postData, Encoding responseEncoding) + { + if (responseEncoding == null) + throw new ArgumentNullException(nameof(responseEncoding)); + + if (postData == null) + throw new ArgumentNullException(nameof(postData)); + + if (url.IsEmpty()) + throw new ArgumentNullException(nameof(url)); + + var responseBytes = await webClient.UploadValuesTaskAsync(url, postData.ToNameValueCollection()); + + try + { + return responseEncoding.GetString(responseBytes); + } + catch (WebException ex) + { + throw new Exception(await ex.GetResponseBody()); + } + } + + public static string ToAuditDataHtml(this IApplicationEvent applicationEvent, bool excludeIds = false) + { + if (applicationEvent.Event == "Insert" && applicationEvent.Data.OrEmpty().StartsWith("")) + { + // return applicationEvent.Data.To().Elements().Select(p => $"
    {p.Name}: {p.Value.HtmlEncode()}
    ").ToLinesString(); + + var insertData = applicationEvent.Data.To().Elements().ToArray(); + + if (excludeIds) + insertData = insertData.Except(x => x.Name.LocalName.EndsWith("Id") && insertData.Select(p => p.Name.LocalName).Contains(x.Name.LocalName.TrimEnd("Id"))) + .Except(x => x.Name.LocalName.EndsWith("Ids") && insertData.Select(p => p.Name.LocalName).Contains(x.Name.LocalName.TrimEnd("Ids"))).ToArray(); + + return insertData.Select(p => $"
    {p.Name.LocalName.ToLiteralFromPascalCase()}: {p.Value.HtmlEncode()}
    ").ToLinesString(); + } + + if (applicationEvent.Event == "Update" && applicationEvent.Data.OrEmpty().StartsWith("")) + { + var data = applicationEvent.Data.To(); + var old = data.Element("old"); + var newData = data.Element("new"); + var propertyNames = old.Elements().Select(x => x.Name.LocalName) + .Concat(newData.Elements().Select(x => x.Name.LocalName)).Distinct().ToArray(); + + if (excludeIds) + propertyNames = propertyNames.Except(p => p.EndsWith("Id") && propertyNames.Contains(p.TrimEnd("Id"))) + .Except(p => p.EndsWith("Ids") && propertyNames.Contains(p.TrimEnd("Ids"))).ToArray(); + + return propertyNames.Select(p => $"
    Changed {p.ToLiteralFromPascalCase()} from \"{ old.GetValue(p).HtmlEncode() }\" to \"{ newData.GetValue(p).HtmlEncode() }\"
    ").ToLinesString(); + } + + if (applicationEvent.Event == "Delete" && applicationEvent.Data.OrEmpty().StartsWith("")) + { + var data = applicationEvent.Data.To(); + var old = data.Element("old"); + + var propertyNames = old.Elements().Select(x => x.Name.LocalName).ToArray(); + + if (excludeIds) + propertyNames = propertyNames.Except(p => p.EndsWith("Id") && propertyNames.Contains(p.TrimEnd("Id"))) + .Except(p => p.EndsWith("Ids") && propertyNames.Contains(p.TrimEnd("Ids"))).ToArray(); + + return propertyNames.Select(p => $"
    {p.ToLiteralFromPascalCase()} was \"{old.GetValue(p).HtmlEncode() }\"
    ").ToLinesString(); + } + + return applicationEvent.Data.OrEmpty().HtmlEncode(); + } + } +} diff --git a/Olive.Web/Extensions/Exception.cs b/Olive.Web/Extensions/Exception.cs new file mode 100644 index 000000000..6a165336e --- /dev/null +++ b/Olive.Web/Extensions/Exception.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Olive.Web +{ + partial class OliveExtensions + { + /// + /// Returns a more complete text dump of this exception, than just its text. + /// + public static string ToFullMessage(this Exception error, string additionalMessage, bool includeStackTrace, bool includeData) + { + if (error == null) + throw new NullReferenceException("This exception object is null"); + var resultBuilder = new StringBuilder(); + resultBuilder.AppendLineIf(additionalMessage, additionalMessage.HasValue()); + var err = error; + while (err != null) + { + resultBuilder.AppendLine(err.Message); + if (includeData && err.Data != null) + { + resultBuilder.AppendLine("\r\nException Data:\r\n{"); + foreach (var i in err.Data) + resultBuilder.AppendLine(Olive.OliveExtensions.ToLogText(i).WithPrefix(" ")); + + resultBuilder.AppendLine("}"); + } + + if (err is ReflectionTypeLoadException) + { + foreach (var loaderEx in (err as ReflectionTypeLoadException).LoaderExceptions) + resultBuilder.AppendLine("Type load exception: " + loaderEx.ToFullMessage()); + } + + // try + // { + // resultBuilder.AppendLineIf((err as HttpUnhandledException)?.GetHtmlErrorMessage().TrimBefore("Server Error")); + // } + // catch + // { + // // No logging is needed + // } + + err = err.InnerException; + if (err != null) + { + resultBuilder.AppendLine(); + if (includeStackTrace) + resultBuilder.AppendLine("###############################################"); + resultBuilder.Append("Base issue: "); + } + } + + if (includeStackTrace && error.StackTrace.HasValue()) + { + var stackLines = error.StackTrace.Or("").Trim().ToLines(); + stackLines = stackLines.Except(l => l.Trim().StartsWith("at System.Data.")).ToArray(); + resultBuilder.AppendLine(stackLines.ToString("\r\n\r\n").WithPrefix("\r\n--------------------------------------\r\nSTACK TRACE:\r\n\r\n")); + } + + return resultBuilder.ToString(); + } + + public static async Task GetResponseBody(this WebException ex) + { + if (ex.Response == null) return null; + + using (var reader = new StreamReader(ex.Response.GetResponseStream())) + return await reader.ReadToEndAsync(); + } + } +} diff --git a/Olive.Web/Olive.Web.csproj b/Olive.Web/Olive.Web.csproj new file mode 100644 index 000000000..c784347ad --- /dev/null +++ b/Olive.Web/Olive.Web.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp2.0 + Olive.Web + Olive.Web + + + + ..\@Assemblies\ + ..\@Assemblies\netcoreapp2.0\Olive.Web.xml + 1701;1702;1705;1591;1573 + + + + + + + + + + + + + \ No newline at end of file diff --git a/Olive.Web/Package.nuspec b/Olive.Web/Package.nuspec new file mode 100644 index 000000000..6623780c7 --- /dev/null +++ b/Olive.Web/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Web + 1.0.3 + Olive Web + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Olive.Web/SystemExtensions/Common.cs b/Olive.Web/SystemExtensions/Common.cs new file mode 100644 index 000000000..5c2436b65 --- /dev/null +++ b/Olive.Web/SystemExtensions/Common.cs @@ -0,0 +1,567 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using Microsoft.AspNetCore.Http; +using Olive.Entities; + +namespace Olive.Web +{ + public static partial class OliveExtensions + { + const int HTTP_PORT_NUMBER = 80; + + const int HTTPS_PORT_NUMBER = 443; + + const int MOVED_PERMANENTLY_STATUS_CODE = 301; + + const int DEFAULT_DOWNLOAD_TIMEOUT = 60000; + + static readonly Range[] PrivateIpRanges = new[] { + //new Range(0u, 50331647u), // 0.0.0.0 to 2.255.255.255 + new Range(167772160u, 184549375u), // 10.0.0.0 to 10.255.255.255 + new Range(2130706432u, 2147483647u), // 127.0.0.0 to 127.255.255.255 + new Range(2851995648u, 2852061183u), // 169.254.0.0 to 169.254.255.255 + new Range(2886729728u, 2887778303u), // 172.16.0.0 to 172.31.255.255 + new Range(3221225984u, 3221226239u), // 192.0.2.0 to 192.0.2.255 + new Range(3232235520u, 3232301055u), // 192.168.0.0 to 192.168.255.255 + new Range(4294967040u, 4294967295u) // 255.255.255.0 to 255.255.255.255 + }; + + /// + /// Adds the specified query string setting to this Url. + /// + public static Uri AddQueryString(this Uri url, string key, string value) + { + var qs = url.GetQueryString(); + + qs.RemoveWhere(x => x.Key.Equals(key, StringComparison.OrdinalIgnoreCase)); + + qs.Add(key, value); + + return url.ReplaceQueryString(qs); + } + + /// + /// Gets the query string parameters of this Url. + /// + public static Dictionary GetQueryString(this Uri url) + { + var entries = System.Web.HttpUtility.ParseQueryString(url.Query); + return entries.AllKeys.ExceptNull().ToDictionary(a => a.ToLower(), a => entries[a]); + } + + #region Request.Get + + /// + /// Returns an object whose ID is given in query string with the key of "id". + /// + public static async Task Get(this HttpRequest request) where T : class, IEntity => await Get(request, "id"); + + /// + /// Gets the cookies sent by the client. + /// + public static IEnumerable> GetCookies(this HttpRequest request) + { + if (request.Cookies == null) return Enumerable.Empty>(); + + return request.Cookies.AsEnumerable(); + } + + /// + /// Gets the data with the specified type from QueryString[key]. + /// If the specified type is an entity, then the ID of that record will be read from query string and then fetched from database. + /// + public static async Task Get(this HttpRequest request, string key) => await DoGet(request, key, throwIfNotFound: true); + + static async Task DoGet(this HttpRequest request, string key, bool throwIfNotFound) + { + if (typeof(T).Implements()) + return await GetEntity(request, key, throwIfNotFound); + + else if (typeof(T) == typeof(string)) + return (T)(object)GetValue(request, key, ignoreRouteData: false); + + else if (typeof(T).IsValueType) + return GetValue(request, key); + + else throw new Exception("Request.Get() does not recognize the type of " + typeof(T).FullName); + } + + /// + /// Returns a string value specified in the request context. + /// + public static string GetValue(this HttpRequest request, string key, bool ignoreRouteData = true) + { + if (Context.ActionContextAccessor.ActionContext.HttpContext.Request != request) + throw new Exception("The given request is not match with ActionContext`s request."); + + return + request.Cookies[key].OrNullIfEmpty() ?? + (request.HasFormContentType ? request.Form[key].ToString().OrNullIfEmpty() : null) ?? + request.Query[key].ToString().OrNullIfEmpty() ?? + request.Headers[key].ToString().OrNullIfEmpty() ?? + (ignoreRouteData ? null : Context.ActionContextAccessor.ActionContext.RouteData.Values[key].ToStringOrEmpty()); + } + + /// + /// Returns a value specified in the request context. + /// + static T GetValue(this HttpRequest request, string key) + { + var data = GetValue(request, key, ignoreRouteData: false); + + if (data.IsEmpty()) + { + if (typeof(T).IsNullable() || typeof(T) == typeof(string)) return default(T); + else throw new Exception($"Request does not contain a value for '{key}'"); + } + + return data.To(); + } + + /// + /// Gets the record with the specified type. The ID of the record will be read from QueryString[key]. + /// + static async Task GetEntity(this HttpRequest request, string key, bool throwIfNotFound = true) + { + if (request == null) + { + if (Context.Http != null) + request = Context.Request; + else + throw new InvalidOperationException("Request.Get() can only be called inside an Http context."); + } + + if (key == ".") key = "." + typeof(T).Name; + + var value = request.GetValue(key); + if (value.IsEmpty()) return default(T); + + try { return (T)await Entity.Database.Get(value, typeof(T)); } + catch (Exception ex) + { + if (throwIfNotFound) + throw new InvalidOperationException($"Loading a {typeof(T).FullName} from the page argument of '{key}' failed.", ex); + else + return default(T); + } + } + + #endregion + + #region Request.GetOrDefault + + /// + /// Gets the record with the specified type. The ID of the record will be read from QueryString["id"]. + /// + public static async Task GetOrDefault(this HttpRequest request) => await GetOrDefault(request, "id"); + + /// + /// Gets the record with the specified type. The ID of the record will be read from QueryString[key]. + /// + public static async Task GetOrDefault(this HttpRequest request, string key) + { + if (request == null) + { + if (Context.Http != null) + request = Context.Request; + else + throw new InvalidOperationException("Request.GetOrDefault() can only be called inside an Http context."); + } + + if (key == ".") key = "." + typeof(T).Name; + + if (!request.Has(key)) return default(T); + + try { return await request.DoGet(key, throwIfNotFound: false); } + catch + { + // No Loging needed + return default(T); + } + } + + #endregion + + public static IEnumerable> GetList(this HttpRequest request, string key) where T : class, IEntity => GetList(request, key, ','); + + /// + /// Gets a list of objects of which Ids come in query string. + /// + /// The key of the query string element containing ids. + /// The sepeerator of Ids in the query string value. The default will be comma (","). + public static IEnumerable> GetList(this HttpRequest request, string key, char seperator) where T : class, IEntity + { + var ids = request.GetValue(key); + if (ids.IsEmpty()) + yield break; + else + foreach (var id in ids.Split(seperator)) + yield return Entity.Database.Get(id); + } + + /// + /// Finds the search keywords used by this user on Google that led to the current request. + /// + public static string FindSearchKeyword(this HttpRequest request) + { + var urlReferrer = request.Headers["Referer"].ToString(); + if (urlReferrer.IsEmpty()) return null; + + // Note: Only Google is supported for now: + + if (!urlReferrer.ToLower().Contains(".google.co")) + return null; + + foreach (var possibleQuerystringKey in new[] { "q", "as_q" }) + { + var queryString = urlReferrer.Split('?').Skip(1).FirstOrDefault(); + var query = queryString.TrimStart("?").Split('&').Trim(). + FirstOrDefault(p => p.StartsWith(possibleQuerystringKey + "=")); + + if (query.HasValue()) + return HttpUtility.UrlDecode(query.Substring(1 + possibleQuerystringKey.Length)); + } + + return null; + } + + /// + /// Gets the actual IP address of the user considering the Proxy and other HTTP elements. + /// + public static string GetIPAddress(this HttpRequest request) => request.HttpContext.Connection.RemoteIpAddress.ToString(); + + #region Private IPs + + /// + /// Determines if the given ip address is in any of the private IP ranges + /// + public static bool IsPrivateIp(string address) + { + if (address.IsEmpty()) return false; + + var bytes = IPAddress.Parse(address).GetAddressBytes(); + if (BitConverter.IsLittleEndian) + bytes = bytes.Reverse().ToArray(); + + var ip = BitConverter.ToUInt32(bytes, 0); + + return PrivateIpRanges.Any(range => range.Contains(ip)); + } + + #endregion + + /// + /// Writes the specified content wrapped in a DIV tag. + /// + public static void WriteLine(this HttpResponse response, string content) => + response.WriteAsync($"
    {content}
    ").RunSynchronously(); + + /// + /// Redirects the client to the specified URL with a 301 status (permanent). + /// + public static void RedirectPermanent(this HttpResponse response, string permanentUrl) + { + response.StatusCode = MOVED_PERMANENTLY_STATUS_CODE; + response.Headers.Add("Location", permanentUrl); + } + + /// + /// Removes the specified query string parameter. + /// + public static Uri RemoveEmptyQueryParameters(this Uri url) + { + var toRemove = url.GetQueryString().Where(x => x.Value.IsEmpty()).ToList(); + + foreach (var item in toRemove) url = url.RemoveQueryString(item.Key); + + return url; + } + + /// + /// Removes the specified query string parameter. + /// + public static Uri RemoveQueryString(this Uri url, string key) + { + var qs = url.GetQueryString(); + key = key.ToLower(); + if (qs.ContainsKey(key)) qs.Remove(key); + + return url.ReplaceQueryString(qs); + } + + /// + /// Removes all query string parameters of this Url and instead adds the specified ones. + /// + public static Uri ReplaceQueryString(this Uri baseUrl, Dictionary queryStringDictionary) + { + var r = new StringBuilder(); + + r.Append(baseUrl.Scheme); + + r.Append("://"); + + r.Append(baseUrl.Host); + + if (baseUrl.Port != HTTP_PORT_NUMBER && baseUrl.Port != HTTPS_PORT_NUMBER) r.Append(":" + baseUrl.Port); + + r.Append(baseUrl.AbsolutePath); + + var query = queryStringDictionary.Select(a => "{0}={1}".FormatWith(a.Key, a.Value.UrlEncode())).ToString("&"); + + if (query.HasValue()) + { + r.Append("?"); + r.Append(query); + } + + return new Uri(r.ToString()); + } + + /// + /// Adds the specified list to session state and returns a unique Key for that. + /// + public static string AddList(this ISession session, IEnumerable list) where T : IEntity => + AddList(session, list, TimeSpan.FromHours(1)); + + /// + /// Adds the specified list to session state and returns a unique Key for that. + /// + public static string AddList(this ISession session, IEnumerable list, TimeSpan timeout) where T : IEntity + { + var expiryDate = DateTime.Now.Add(timeout); + + var key = "L|" + Guid.NewGuid() + "|" + expiryDate.ToOADate(); + session.SetString(key, list.Where(x => x != null).Select(a => a.GetId()).ToString("|").Or(string.Empty)); + + var expiredKeys = session.Keys.Where(k => k.StartsWith("L|") && k.Split('|').Length == 3 && DateTime.FromOADate(k.Split('|').Last().To()) < DateTime.Now).ToArray(); + expiredKeys.Do(k => session.Remove(k)); + + return key; + } + + /// + /// Retrieves a list of objects specified by the session key which is previously generated by Session.AddList() method. + /// + public static async Task> GetList(this ISession session, string key) where T : Entity + { + if (key.IsEmpty()) + throw new ArgumentNullException(nameof(key)); + + if (key.Split('|').Length != 3) + throw new ArgumentException("Invalid list key specified. Bar character is expected."); + + var date = key.Split('|').Last().TryParseAs(); + + if (date == null) + throw new ArgumentException("Invalid list key specified. Data after Bar character should be OADate."); + + var ids = session.GetString(key); + if (ids == null) + throw new TimeoutException($"The list with the key {key} is expired and removed from the session."); + + return (await ids.Split('|').Select(async i => await Entity.Database.GetOrDefault(i)).AwaitAll()).ExceptNull(); + } + + /// + /// Runs the parallel select in the current HTTP context. + /// + public static ParallelQuery SelectInHttpContext(this ParallelQuery list, Func selector) + { + var httpContext = Context.HttpContextAccessor.HttpContext; + + return list.Select(x => { Context.HttpContextAccessor.HttpContext = httpContext; return selector(x); }); + } + + /// + /// Determines if the specified argument exists in the request (query string or form). + /// + public static bool Has(this HttpRequest request, string argument) + { + return request.Query.Keys.Contains(argument) || (request.HasFormContentType && request.Form.Keys.Contains(argument)); + } + + /// + /// Determines if the specified argument not exists in the request (query string or form). + /// + public static bool Lacks(this HttpRequest request, string argument) => !request.Has(argument); + + /// + /// Gets the root of the requested website. + /// + public static string GetWebsiteRoot(this HttpRequest request) => $"{request.Scheme}://{request.Host}/"; + + /// + /// Gets the raw url of the request. + /// + public static string ToRawUrl(this HttpRequest request) => + $"{request.PathBase}{request.Path}{request.QueryString}"; + + public static string ToPathAndQuery(this HttpRequest request) => + $"{request.Path}{request.QueryString}"; + + /// + /// Gets the absolute Uri of the request. + /// + public static string ToAbsoluteUri(this HttpRequest request) => + $"{request.GetWebsiteRoot().TrimEnd('/')}{request.PathBase}{request.Path}{request.QueryString}"; + + /// + /// Gets the absolute URL for a specified relative url. + /// + public static string GetAbsoluteUrl(this HttpRequest request, string relativeUrl) => + request.GetWebsiteRoot() + relativeUrl.TrimStart("/"); + + /// + /// Downloads the text in this URL. + /// + public static async Task Download(this Uri url, string cookieValue = null, int timeOut = DEFAULT_DOWNLOAD_TIMEOUT) + { + var request = (HttpWebRequest)WebRequest.Create(url); + + request.Timeout = timeOut; + + if (cookieValue.HasValue()) + { + request.CookieContainer = new CookieContainer(); + request.CookieContainer.SetCookies(url, cookieValue.OrEmpty()); + } + + using (var response = await request.GetResponseAsync()) + { + using (var stream = response.GetResponseStream()) + return await stream.ReadAllText(); + } + } + + /// + /// Downloads the data in this URL. + /// + public static async Task DownloadData(this Uri url, string cookieValue = null, int timeOut = DEFAULT_DOWNLOAD_TIMEOUT) + { + var request = (HttpWebRequest)HttpWebRequest.Create(url); + + request.Timeout = timeOut; + + if (cookieValue.HasValue()) + { + request.CookieContainer = new CookieContainer(); + request.CookieContainer.SetCookies(url, cookieValue.OrEmpty()); + } + + using (var response = await request.GetResponseAsync()) + { + using (var stream = response.GetResponseStream()) + return await stream.ReadAllBytes(); + } + } + + /// + /// Downloads the data in this URL. + /// + public static async Task DownloadBlob(this Uri url, string cookieValue = null, int timeOut = DEFAULT_DOWNLOAD_TIMEOUT) + { + var fileName = "File.Unknown"; + + if (url.IsFile) + fileName = url.ToString().Split('/').Last(); + + return new Blob(await url.DownloadData(cookieValue, timeOut), fileName); + } + + /// + /// Reads all text in this stream as UTF8. + /// + public static async Task ReadAllText(this Stream response) + { + string result = ""; + + // Pipes the stream to a higher level stream reader with the required encoding format. + using (var readStream = new StreamReader(response, Encoding.UTF8)) + { + var read = new char[256]; + // Reads 256 characters at a time. + int count = await readStream.ReadAsync(read, 0, read.Length); + + while (count > 0) + { + // Dumps the 256 characters on a string and displays the string to the console. + result += new string(read, 0, count); + + count = await readStream.ReadAsync(read, 0, read.Length); + } + } + + return result; + } + + /// + /// Gets the Html Encoded version of this text. + /// + public static string HtmlEncode(this string text) + { + if (text.IsEmpty()) return string.Empty; + + return HttpUtility.HtmlEncode(text); + } + + /// + /// Gets the Html Decoded version of this text. + /// + public static string HtmlDecode(this string text) + { + if (text.IsEmpty()) return string.Empty; + + return HttpUtility.HtmlDecode(text); + } + + /// + /// Gets the Url Encoded version of this text. + /// + public static string UrlEncode(this string text) + { + if (text.IsEmpty()) return string.Empty; + + return HttpUtility.UrlEncode(text); + } + + /// + /// Gets the Url Decoded version of this text. + /// + public static string UrlDecode(this string text) + { + if (text.IsEmpty()) return string.Empty; + + return HttpUtility.UrlDecode(text); + } + + /// + /// Properly sets a query string key value in this Uri, returning a new Uri object. + /// + public static Uri SetQueryString(this Uri uri, string key, object value) + { + var valueString = string.Empty; + + if (value != null) + { + if (value is IEntity) + valueString = (value as IEntity).GetId().ToString(); + else + valueString = value.ToString(); + } + + var pairs = HttpUtility.ParseQueryString(uri.Query); + + pairs[key] = valueString; + + var builder = new UriBuilder(uri) { Query = pairs.ToString() }; + + return builder.Uri; + } + } +} \ No newline at end of file diff --git a/Olive.Web/SystemExtensions/Http.cs b/Olive.Web/SystemExtensions/Http.cs new file mode 100644 index 000000000..792e1f84b --- /dev/null +++ b/Olive.Web/SystemExtensions/Http.cs @@ -0,0 +1,149 @@ +using System; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Olive.Entities; + +namespace Olive.Web +{ + partial class OliveExtensions + { + /// + /// Determines whether this is an Ajax call. + /// + public static bool IsAjaxRequest(this HttpRequest request) => request.IsAjaxCall(); + + /// + /// Determines whether this is an Ajax call. + /// + public static bool IsAjaxCall(this HttpRequest request) => request.Headers["X-Requested-With"] == "XMLHttpRequest"; + + /// + /// Dispatches a binary data block back to the client. + /// + public static async Task Dispatch(this HttpResponse response, byte[] responseData, string fileName, string contentType = "Application/octet-stream") + { + if (responseData == null) + throw new ArgumentNullException(nameof(responseData)); + + if (fileName.IsEmpty()) + throw new ArgumentNullException(nameof(fileName)); + + response.Clear(); + response.ContentType = contentType; + + response.Headers.Add("Content-Disposition", $"attachment; filename=\"{fileName.Remove("\"").Replace(",", "-")}\""); + + await response.Body.WriteAsync(responseData, 0, responseData.Length); + await response.Body.FlushAsync(); + } + + /// + /// Dispatches a string back to the client as a file. + /// + public static void Dispatch(this HttpResponse response, string responseText, string fileName, string contentType = "Application/octet-stream", System.Text.Encoding encoding = null) + { + response.Clear(); + + response.Headers.Add("Cache-Control", "no-store"); + response.Headers.Add("Pragma", "no-cache"); + + if (fileName.HasValue()) + response.Headers.Add("Content-Disposition", $"attachment;filename={fileName.Replace(" ", "_")}"); + + response.ContentType = contentType; + + if (encoding != null) + response.WriteAsync(responseText, encoding).RunSynchronously(); + else + response.WriteAsync(responseText).RunSynchronously(); + } + + /// + /// Dispatches a file back to the client. + /// + /// If set to null, the same file name of the file will be used. + public static async Task Dispatch(this HttpResponse response, FileInfo responseFile, string fileName = null, string contentType = "Application/octet-stream") + { + if (responseFile == null) + throw new ArgumentNullException(nameof(responseFile)); + + if (fileName.IsEmpty()) + fileName = responseFile.Name; + + await Dispatch(response, await responseFile.ReadAllBytes(), fileName, contentType); + } + + /// + /// Dispatches a file back to the client. + /// + public static async Task Dispatch(this HttpResponse response, Blob blob, string contentType = "Application/octet-stream") + { + await Dispatch(response, await File.ReadAllBytesAsync(blob.LocalPath), blob.FileName, contentType); + } + + /// + /// Determines if this is a GET http request. + /// + public static bool IsGet(this HttpRequest request) => request.Method == System.Net.WebRequestMethods.Http.Get; + + /// + /// Determines if this is a POST http request. + /// + public static bool IsPost(this HttpRequest request) => request.Method == System.Net.WebRequestMethods.Http.Post; + + /// + /// Gets the currently specified return URL. + /// + public static string GetReturnUrl(this HttpRequest request) + { + var result = request.GetValue("ReturnUrl"); + + if (result.IsEmpty()) return string.Empty; + + if (result.StartsWith("http", StringComparison.OrdinalIgnoreCase) || + result.ToCharArray().ContainsAny('\'', '\"', '>', '<') || + result.ContainsAny(new[] { "//", ":" }, caseSensitive: false)) + throw new Exception("Invalid ReturnUrl."); + + return result; + } + + /// + /// Writes the specified message in the response and then ends the response. + /// + public static void EndWith(this HttpResponse response, string message, string mimeType = "text/html") + { + response.ContentType = mimeType; + response.WriteAsync(message).RunSynchronously(); + } + + /// + /// Reads the full content of a posted text file. + /// + public static async Task ReadAllText(this IFormFile file) + { + using (var reader = new StreamReader(file.OpenReadStream())) + return await reader.ReadToEndAsync(); + } + + public static bool IsLocal(this HttpRequest req) + { + var connection = req.HttpContext.Connection; + if (connection.RemoteIpAddress != null) + { + if (connection.LocalIpAddress != null) + return connection.RemoteIpAddress.Equals(connection.LocalIpAddress); + else + return IPAddress.IsLoopback(connection.RemoteIpAddress); + } + + // for in memory TestServer or when dealing with default connection info + if (connection.RemoteIpAddress == null && connection.LocalIpAddress == null) + return true; + + return false; + } + } +} \ No newline at end of file diff --git a/Olive.Web/UrlRewriting/IWebResource.cs b/Olive.Web/UrlRewriting/IWebResource.cs new file mode 100644 index 000000000..a20eb62f3 --- /dev/null +++ b/Olive.Web/UrlRewriting/IWebResource.cs @@ -0,0 +1,9 @@ +using Olive.Entities; + +namespace Olive.Web +{ + public interface IWebResource : IEntity + { + string GetUrl(); + } +} diff --git a/Olive.Web/UrlRewriting/UrlRewriting.cs b/Olive.Web/UrlRewriting/UrlRewriting.cs new file mode 100644 index 000000000..16f52cb76 --- /dev/null +++ b/Olive.Web/UrlRewriting/UrlRewriting.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Olive.Entities; + +namespace Olive.Web +{ + public static class UrlRewriting + { + static Dictionary> RewritingMapping = new Dictionary>(); + static Dictionary> ResourceLoaders = new Dictionary>(); + + /// + /// Creates a suitable string for urls. + /// + public static string Escape(string text) + { + if (text.IsEmpty()) return string.Empty; + + var r = new StringBuilder(); + + r.Append(string.Empty); + + foreach (var c in text) + { + if (c.IsLetterOrDigit()) r.Append(c); + switch (c) + { + case '-': + case '_': r.Append(c); break; + case ' ': r.Append('-'); break; + case '&': r.Append("-and-"); break; + default: break; // Keep it + } + } + + var result = r.ToString().Trim("-_".ToCharArray()); + + while (result.Contains("__")) + result = result.Replace("__", "_"); + + while (result.Contains("--")) + result = result.Replace("--", "-"); + + return result; + } + + public static void RegisterLoader(Func loaderMethod) => ResourceLoaders.Add(typeof(T), loaderMethod); + + static Func GetMapper(Type type) + { + for (var parent = type; parent != null; parent = parent.BaseType) + if (RewritingMapping.ContainsKey(type)) + return RewritingMapping[type]; + + throw new Exception("No Url Rewrite mapping has been specified for the type " + type.FullName); + } + + public static void Register(Func mapping) where T : IWebResource + { + if (!RewritingMapping.ContainsKey(typeof(T))) + RewritingMapping.Add(typeof(T), r => mapping?.Invoke((T)r)); + } + + public static string GetExecutionPath(IWebResource resource) + { + if (resource == null) + throw new ArgumentNullException(nameof(resource)); + + var path = GetMapper(resource.GetType())(resource); + + var queryString = Context.HttpContextAccessor.HttpContext.Request.Query; + if (queryString.Keys.Any()) + { + foreach (var key in queryString.Keys) + { + if (path.Contains(key + "=")) continue; + + if (!path.Contains("?")) path += "?"; + else if (!path.EndsWith("&")) path += "&"; + path += key + "=" + queryString[key]; + } + } + + return path; + } + + /// + /// Gets the Currently requested resource. + /// + public static async Task FindRequestedResource() => await FindRequestedResource(ignoreDomain: true); + + /// + /// Gets the Currently requested resource. + /// + public static async Task FindRequestedResource(bool ignoreDomain) + { + var path = Context.HttpContextAccessor.HttpContext.Request.Path.ToString(); + + if (!ignoreDomain) + { + if (path.StartsWith("/")) path = path.Substring(1); + path = Context.HttpContextAccessor.HttpContext.Request.GetWebsiteRoot() + path; + } + + IWebResource result; + + foreach (var type in RewritingMapping.Keys) + { + if (ResourceLoaders.ContainsKey(type)) + result = ResourceLoaders[type](path); + else + result = (await Entity.Database.Of(type).GetList()).OfType().FirstOrDefault(r => r.GetUrl().Equals(path, StringComparison.OrdinalIgnoreCase)); + + if (result != null) return result; + } + + return null; + } + + /// + /// Determines if this web resource's Url matches a given path. + /// + public static bool Matches(this IWebResource resource, string path) => + resource.GetUrl().Equals(path, StringComparison.OrdinalIgnoreCase); + } +} \ No newline at end of file diff --git a/Olive.Web/Web/CookieProperty.cs b/Olive.Web/Web/CookieProperty.cs new file mode 100644 index 000000000..78f38c94a --- /dev/null +++ b/Olive.Web/Web/CookieProperty.cs @@ -0,0 +1,194 @@ +namespace Olive.Web +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Olive.Entities; + + /// + /// Provides easy access to HTTP cookie data. + /// + public class CookieProperty + { + const string BAR_SCAPE = "[#*^BAR_SCAPE^*#]"; + + /// + /// Gets the value of the property sent from the client browser as a cookie. + /// + public static async Task Get() => await Get(null, default(T)); + + /// + /// Gets the value of a string property sent from the client browser as a cookie. + /// + public static async Task Get(string key) => await Get(key, null); + + /// + /// Gets the value of the property sent from the client browser as a cookie. + /// + public static async Task Get(T defaultValue) => await Get(null, defaultValue); + + /// + /// Gets the value of the property sent from the client browser as a cookie. + /// + public static async Task Get(string propertyName) => await Get(propertyName, default(T)); + + public static async Task> GetStrings(string propertyName) => + await Get>(propertyName, null); + + /// + /// Gets the value of the property sent from the client browser as a cookie. + /// + public static async Task Get(string propertyName, T defaultValue) + { + var key = propertyName.Or("Default.Value.For." + typeof(T).FullName); + + var value = Context.HttpContextAccessor.HttpContext.Request.Cookies[key]; + + if (!Context.HttpContextAccessor.HttpContext.Request.Cookies.ContainsKey(key)) + { + return defaultValue; + } + else if (typeof(T).Implements()) + { + var id = value.Contains('/') ? value.Split('/')[1] : value; // Remove class name prefix if exists + return (T)await Entity.Database.GetOrDefault(id, typeof(T)); + } + else if (typeof(T) == typeof(string)) + { + return (T)(object)value; + } + else if (typeof(T) == typeof(IEnumerable) || typeof(T) == typeof(string[])) + { + return (T)(object)value.Or("").Split('|').Trim().Select(p => p.Replace(BAR_SCAPE, "|")).ToArray(); + } + else if (typeof(T).Namespace.StartsWith("System")) + { + return (T)value.To(typeof(T)); + } + + throw new Exception("CookieProperty.Get() does not support T type of " + typeof(T).FullName); + } + + /// + /// Sets a specified value in the response cookie as well as request cookie. + /// + /// Specifies whether the cookie should be accessible via Javascript too, or Server (http) only. + public static void Set(T value, bool isHttpOnly = true) => Set(null, value, isHttpOnly); + + /// + /// Sets a specified value in the response cookie as well as request cookie. + /// + /// Specifies whether the cookie should be accessible via Javascript too, or Server (http) only. + public static void Set(string propertyName, T value, bool isHttpOnly = true) + { + var key = propertyName.Or("Default.Value.For." + typeof(T).FullName); + + var stringValue = value?.ToString(); + + if (value is IEntity) stringValue = (value as IEntity).GetFullIdentifierString(); + Set(key, stringValue, isHttpOnly); + } + + /// + /// Sets a specified list in the response cookie as well as request cookie. + /// + /// Specifies whether the cookie should be accessible via Javascript too, or Server (http) only. + public static void SetList(string propertyName, IEnumerable list, bool isHttpOnly = true) where T : IEntity + { + var key = propertyName.Or("Default.List.For." + typeof(T).FullName); + + if (list == null) + { + Set(key, string.Empty, isHttpOnly); + } + else + { + var stringValue = list.Except(n => n == null).Select(i => i.GetFullIdentifierString()).ToString("|"); + Set(key, stringValue, isHttpOnly); + } + } + + /// + /// Sets a specified list in the response cookie as well as request cookie. + /// + public static async Task> GetList() where T : IEntity => await GetList(null); + + /// + /// Gets a specified list in the response cookie as well as request cookie. + /// + public static async Task GetList(string propertyName) where T : IEntity + { + var key = propertyName.Or("Default.List.For." + typeof(T).FullName); + + var result = await Get(key); + if (result.IsEmpty()) return new T[0]; + + return result.Split('|').Select(x => ExtractItem(x)).Except(n => n == null).ToArray(); + } + + static T ExtractItem(string valueExpression) where T : IEntity + { + var id = valueExpression.Contains('/') ? valueExpression.Split('/')[1] : valueExpression; // Remove class name prefix if exists + return (T)(object)Entity.Database.GetOrDefault(id, typeof(T)); + } + + /// + /// Removes the specified cookie property. + /// + public static void Remove() => Set(default(T)); + + /// + /// Removes the specified cookie property. + /// + public static void Remove(string propertyName) => Set(propertyName, default(T)); + + /// + /// Removes the specified cookie property. + /// + public static void Remove(string propertyName) + { + var cookies = Context.HttpContextAccessor.HttpContext?.Response?.Cookies; + + if (cookies == null) return; + + cookies.Delete(propertyName); + } + + /// + /// Sets a specified value in the response cookie as well as request cookie. + /// + /// Specifies whether the cookie should be accessible via Javascript too, or Server (http) only. + public static void Set(string propertyName, IEnumerable strings, bool isHttpOnly = true) + { + strings = strings ?? new string[0]; + Set(propertyName, strings.Trim().Select(s => s.Replace("|", BAR_SCAPE)).ToString("|"), isHttpOnly); + } + + /// + /// Sets a specified value in the response cookie as well as request cookie. + /// + /// Specifies whether the cookie should be accessible via Javascript too, or Server (http) only. + public static void Set(string key, string value, bool isHttpOnly = true) + { + if (key.IsEmpty()) + throw new ArgumentNullException(nameof(key)); + + var cookies = Context.HttpContextAccessor.HttpContext?.Response?.Cookies; + + if (cookies == null) return; + + cookies.Append( + key, + value, + new CookieOptions + { + HttpOnly = isHttpOnly, + Expires = DateTime.Now.AddYears(10), + Secure = Context.HttpContextAccessor.HttpContext.Request.IsHttps + } + ); + } + } +} \ No newline at end of file diff --git a/Olive.Web/Web/HttpContextCache.cs b/Olive.Web/Web/HttpContextCache.cs new file mode 100644 index 000000000..c944a6ae0 --- /dev/null +++ b/Olive.Web/Web/HttpContextCache.cs @@ -0,0 +1,46 @@ +namespace Olive.Web +{ + using System; + + /// + /// Provides a HttpRequest level cache of objects. + /// + public static class HttpContextCache + { + /// + /// Gets a specified cached value from the current HttpContext. + /// If it doesn't exist, it will evaluate the provider expression to produce the value, adds it to cache, and returns it. + /// + public static TValue GetOrAdd(TKey key, Func valueProducer) + { + if (key == null) throw new ArgumentNullException(nameof(key)); + if (valueProducer == null) throw new ArgumentNullException(nameof(valueProducer)); + + var bag = Context.HttpContextAccessor.HttpContext?.Items; + if (bag == null) return valueProducer(); + + if (bag.ContainsKey(key)) return (TValue)bag[key]; + + var value = valueProducer(); + + if (!bag.ContainsKey(key)) bag[key] = value; + + return value; + } + + /// + /// Removes a specified cached object by its key from the current Http Context. + /// + public static void Remove(TKey key) + { + if (key == null) throw new ArgumentNullException("key"); + + var context = Context.HttpContextAccessor.HttpContext; + if (context == null) return; + + var bag = context.Items; + + if (bag.ContainsKey(key)) bag.Remove(key); + } + } +} \ No newline at end of file diff --git a/Olive.Web/Web/IWebRequestLog.cs b/Olive.Web/Web/IWebRequestLog.cs new file mode 100644 index 000000000..8b6daec16 --- /dev/null +++ b/Olive.Web/Web/IWebRequestLog.cs @@ -0,0 +1,41 @@ +using System; +using System.Threading.Tasks; +using Olive.Entities; + +namespace Olive.Web +{ + [LogEvents(false), CacheObjects(false)] + public interface IWebRequestLog : IEntity + { + string IP { get; set; } + double ProcessingTime { get; set; } + string RequestType { get; set; } + int ResponseLength { get; set; } + string SearchKeywords { get; } + string SessionId { get; set; } + DateTime Start { get; set; } + string Url { get; set; } + string UrlReferer { get; set; } + string UserAgent { get; set; } + } + + public static class WebRequestLogExtensions + { + public static async Task CountSessionRequests(this IWebRequestLog request) + { + if (request.SessionId.IsEmpty()) return 1; + return await Entity.Database.Count(x => x.SessionId == request.SessionId); + } + + public static async Task GetLastVisitedUrl(this IWebRequestLog request) + { + if (request.SessionId.IsEmpty()) return request.Url; + + return (await Entity.Database.GetList(x => x.SessionId == request.SessionId)).WithMax(x => x.Start)?.Url; + } + + public static TimeSpan GetDuration(this IWebRequestLog request) => TimeSpan.FromMilliseconds(request.ProcessingTime); + + public static async Task IsBouncedBack(this IWebRequestLog request) => await request.CountSessionRequests() == 1; + } +} diff --git a/Olive.Web/Web/SecureFileDispatcher.cs b/Olive.Web/Web/SecureFileDispatcher.cs new file mode 100644 index 000000000..3a81a0f2d --- /dev/null +++ b/Olive.Web/Web/SecureFileDispatcher.cs @@ -0,0 +1,160 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Olive.Entities; + +namespace Olive.Web +{ + public class SecureFileDispatcher + { + public static readonly AsyncEvent UnauthorisedFileRequested = + new AsyncEvent(); + + string[] PathParts; + + Type Type; + object Instance; + string Property; + HttpResponse Response; + Blob Blob; + IUser CurrentUser; + + /// + /// Creates a new SecureFileDispatcher instance. + /// + public SecureFileDispatcher(string path, IUser currentUser) + { + CurrentUser = currentUser; + + Response = Context.HttpContextAccessor.HttpContext.Response; + + PathParts = path.Split('/'); + + if (PathParts.Length < 2) + { + throw new Exception($"Invalid path specified: '{path}'"); + } + + FindRequestedProperty(); + + FindRequestedObject(); + } + + public async Task Dispatch() => await DispatchFile(await GetFile()); + + public async Task GetFile() + { + await EnsureSecurity(); + + var file = Blob.LocalPath; + + // Fall-back logic + if (!File.Exists(file)) + file = Blob.FallbackPaths.FirstOrDefault(File.Exists); + + return file.AsFile(); + } + + void FindRequestedProperty() + { + var typeName = PathParts[0].Split('.')[0]; + + Type = Entity.Database.GetRegisteredAssemblies().Select(a => a.GetExportedTypes().SingleOrDefault(t => t.Name == typeName)).ExceptNull().FirstOrDefault(); + if (Type == null) throw new Exception($"Invalid type name specified: '{typeName}'"); + + Property = PathParts[0].Split('.')[1]; + } + + void FindRequestedObject() + { + var idData = PathParts[1]; + + foreach (var key in new[] { ".", "/" }) + if (idData.Contains(key)) idData = idData.Substring(0, idData.IndexOf(key)); + + var id = idData.TryParseAs(); + if (id == null) throw new Exception($"Invalid object ID specified: '{idData}'"); + + Instance = Entity.Database.Get(id.Value, Type); + if (Instance == null) throw new Exception($"Invalid {Type.FullName} ID specified: '{id}'"); + + Blob = EntityManager.ReadProperty(Instance, Property) as Blob; + } + + async Task EnsureSecurity() + { + try + { + var method = Type.GetMethod("Is" + Property + "VisibleTo", BindingFlags.Public | BindingFlags.Instance); + if (method == null) + { + throw new Exception(Type.FullName + ".Is" + Property + "VisibleTo() method is not defined."); + } + + if (method.GetParameters().Count() != 1 || !method.GetParameters().Single().ParameterType.Implements()) + throw new Exception(Type.FullName + "." + method.Name + "() doesn't accept a single argument that implements IUser"); + + var result = (Task)method.Invoke(Instance, new object[] { CurrentUser }); + if (!await result) + throw new Exception("You are not authorised to view the requested file."); + } + catch (Exception ex) + { + if (UnauthorisedFileRequested.IsHandled()) + { + await UnauthorisedFileRequested.Raise(new UnauthorisedRequestEventArgs + { + Exception = ex, + Instance = Instance as IEntity, + Property = Type.GetProperty(Property) + }); + } + else + { + Response.Clear(); + Response.WriteAsync("

    File access issue

    ").RunSynchronously(); + Log.Error("Invalid secure file access: " + PathParts.ToString("/"), ex); + + Response.WriteLine("Invalid file request. Please contact your I.T. support."); + Response.WriteLine(ex.Message); + } + } + } + + async Task DispatchFile(FileInfo file) + { + if (!file.Exists()) + { + Response.Clear(); + Response.WriteAsync("File does not exist: " + file).RunSynchronously(); + return; + } + + var fileName = Blob.FileName.Or(file.Name); + var contentType = file.Extension.OrEmpty().TrimStart(".").ToLower().Or("Application/octet-stream"); + + await Response.Dispatch(await file.ReadAllBytes(), fileName, contentType); + } + + public class UnauthorisedRequestEventArgs : EventArgs + { + /// + /// A property of type Blob which represents the requested file property. + /// + public PropertyInfo Property; + + /// + /// The object on which the blob property was requested. + /// + public IEntity Instance; + + /// + /// The security error raised by M# framework. + /// + public Exception Exception; + } + } +} \ No newline at end of file diff --git a/Olive.Web/Web/WebRequestLogMiddleware.cs b/Olive.Web/Web/WebRequestLogMiddleware.cs new file mode 100644 index 000000000..30e98646f --- /dev/null +++ b/Olive.Web/Web/WebRequestLogMiddleware.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using static Olive.Web.WebRequestLogService; + +namespace Olive.Web +{ + public class WebRequestLogMiddleware + { + readonly RequestDelegate Next; + readonly IHttpContextAccessor HttpContextAccessor; + + public WebRequestLogMiddleware(RequestDelegate next, IHttpContextAccessor httpContextAccessor) + { + Next = next; + HttpContextAccessor = httpContextAccessor; + } + + public async Task Invoke(HttpContext context) + { + CurrentRequestLog = WebRequestLogFactory?.Invoke(); + CurrentRequestLog.SessionId = context.Session.Id; + + await Next.Invoke(context); + + await CurrentRequestLog.Perform(async x => await x.Record(HttpContextAccessor.HttpContext)); + } + } + + public static class WebRequestLogMiddlewareExtension + { + public static IApplicationBuilder UseWebRequestLogMiddleware(this IApplicationBuilder builder) => + builder.UseMiddleware(); + } +} diff --git a/Olive.Web/Web/WebRequestLogService.cs b/Olive.Web/Web/WebRequestLogService.cs new file mode 100644 index 000000000..6893978e2 --- /dev/null +++ b/Olive.Web/Web/WebRequestLogService.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Web; +using Microsoft.AspNetCore.Http; +using Olive.Entities; + +namespace Olive.Web +{ + /// + /// Provides services to web request log objects. + /// + public static class WebRequestLogService + { + static bool IsEnabled = Config.Get("Enable.Request.Logging", defaultValue: false); + + #region Factory + + /// + /// Specifies a factory to instantiate WebRequestLog objects. + /// + public static Func WebRequestLogFactory = CreateWebRequestLog; + + static Type concreteWebRequestLogType; + static IWebRequestLog CreateWebRequestLog() + { + if (concreteWebRequestLogType != null) + { + var result = Activator.CreateInstance(concreteWebRequestLogType) as IWebRequestLog; + result.Start = LocalTime.Now; + return result; + } + + var possible = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => a.References(typeof(IWebRequestLog).Assembly)) + .SelectMany(a => a.GetExportedTypes().Where(t => t.Implements())).ToList(); + + if (possible.Count == 0) + throw new Exception("No type in the currently loaded assemblies implements IWebRequestLog."); + + if (possible.Count > 1) + throw new Exception("More than one type in the currently loaded assemblies implement IWebRequestLog:" + possible.Select(x => x.FullName).ToString(" and ")); + + concreteWebRequestLogType = possible.Single(); + return CreateWebRequestLog(); + } + + #endregion + + /// + /// Records this web request log in the provided http context. + /// + public static async Task Record(this IWebRequestLog log, HttpContext context) + { + try + { + await DoRecord(log, context); + } + catch (Exception ex) + { + Log.Error("Could not record Web Request Log", ex); + } + } + + static async Task DoRecord(IWebRequestLog log, HttpContext context) + { + log.ProcessingTime = LocalTime.Now.Subtract(log.Start).TotalMilliseconds; + + log.Url = context.Request.ToRawUrl(); + log.UserAgent = context.Request.Headers["User-Agent"].ToString(); + log.IP = context.Connection.RemoteIpAddress.ToString(); + log.RequestType = context.Request.Method; + log.UrlReferer = context.Request.Headers["Referer"].ToString(); + log.SessionId = context.Session?.Id; + + if (context.Response.ContentLength.HasValue) + log.ResponseLength = (int)(context.Response.ContentLength.Value / 1024); + + await Entity.Database.Save(log); + } + + public static string FindSearchKeywords(this IWebRequestLog log) + { + if (log.UrlReferer.IsEmpty()) return null; + + // var url = UrlReferer.ToLower(); + + if (!log.UrlReferer.ToLower().Contains(".google.co")) + return null; + + foreach (var possibleQuerystringKey in new[] { "q", "as_q" }) + { + var query = new Uri(log.UrlReferer).Query.TrimStart("?").OrEmpty().Split('&').Trim().FirstOrDefault(p => p.StartsWith(possibleQuerystringKey + "=")); + + if (query.HasValue()) + { + return HttpUtility.UrlDecode(query.Substring(1 + possibleQuerystringKey.Length)); + } + } + + return log.UrlReferer; + } + + /// + /// Gets the number of requests made in the same session. + /// + public static async Task CountRequestsInSession(this IWebRequestLog log) => + await Entity.Database.Count(r => r.SessionId == log.SessionId); + + /// + /// Gest the last url visited in this session. + /// + public static async Task GetLastVisitedUrl(this IWebRequestLog log) + { + return (await Entity.Database.GetList(r => r.SessionId == log.SessionId)).WithMax(r => r.Start).Url; + } + + /// + /// Gets the value of a query string key. + /// + public static string GetData(this IWebRequestLog log, string key) + { + var query = new Uri(log.Url).Query?.TrimStart("?").Split('&').FirstOrDefault(p => p.StartsWith(key + "=")); + + if (query.IsEmpty()) + return null; + else + return HttpUtility.UrlDecode(query.Substring(1 + key.Length)); + } + + /// + /// Gets the first request of every session that has had an activity during the last 10 minutes. + /// + public static async Task> FindRecentSessions(TimeSpan since) + { + var startDate = LocalTime.Now.Subtract(since); + + var sessions = (await Entity.Database.GetList(r => r.Start > startDate)).GroupBy(r => r.SessionId); + + return sessions.Select(session => session.WithMin(r => r.Start)).ToArray(); + } + + public static IWebRequestLog CurrentRequestLog + { + get { return Context.HttpContextAccessor.HttpContext.Items["Current.Request.Log"] as IWebRequestLog; } + set { Context.HttpContextAccessor.HttpContext.Items.Add("Current.Request.Log", value); } + } + } +} \ No newline at end of file diff --git a/Olive.sln b/Olive.sln new file mode 100644 index 000000000..affd1fc62 --- /dev/null +++ b/Olive.sln @@ -0,0 +1,178 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2005 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive", "Olive\Olive.csproj", "{EF844D85-7C45-4725-B033-DBC6111C5F7F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EB802F83-E581-4C4B-9B16-7D00D66C9329}" + ProjectSection(SolutionItems) = preProject + GCop.json = GCop.json + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Entities", "Olive.Entities\Olive.Entities.csproj", "{81D870C8-EA2A-46DA-A936-0D4D520B2C32}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Entities.Data", "Olive.Entities.Data\Olive.Entities.Data.csproj", "{35B19C59-D221-47AF-AAC6-F9CEB0953FFB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Web", "Olive.Web\Olive.Web.csproj", "{EA922ABB-FF72-4C92-B5BE-C4FEAF5AC168}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{943E7871-2EEA-4C97-857B-16443CB240E6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.Compression", "Services\Olive.Services.Compression\Olive.Services.Compression.csproj", "{AB221943-A14F-4817-BAA6-A6829841EA51}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.CSV", "Services\Olive.Services.CSV\Olive.Services.CSV.csproj", "{50ABA031-EAD6-4554-AC73-8BCA56C637B2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.Email", "Services\Olive.Services.Email\Olive.Services.Email.csproj", "{FCE33030-315F-4102-87B9-E36B21F666B0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.Excel", "Services\Olive.Services.Excel\Olive.Services.Excel.csproj", "{E6C2B0FC-8C65-4AD9-A50F-21484D0E9F06}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.GeoLocation", "Services\Olive.Services.GeoLocation\Olive.Services.GeoLocation.csproj", "{253531A7-A460-4D61-9A85-A3E29B33AA94}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.Globalization", "Services\Olive.Services.Globalization\Olive.Services.Globalization.csproj", "{5EE54E7F-07C7-4966-9EFA-5E2252206B77}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.PDF", "Services\Olive.Services.PDF\Olive.Services.PDF.csproj", "{5B7D4B8F-66B2-4A74-A42F-37298CF1878F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.SMS", "Services\Olive.Services.SMS\Olive.Services.SMS.csproj", "{2CCA47DA-038F-4C63-8E7F-9F4A9296BB44}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.Integration", "Services\Olive.Services.Integration\Olive.Services.Integration.csproj", "{0199C138-414E-46AF-8898-3724D3331F4C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.Drawing", "Services\Olive.Services.Drawing\Olive.Services.Drawing.csproj", "{55390C42-F8DE-4089-B079-74E18714236D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.IpFilter", "Services\Olive.Services.IpFilter\Olive.Services.IpFilter.csproj", "{816FFC81-38E0-442C-8236-742551AC8AFC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.ImpersonationSession", "Services\Olive.Services.ImpersonationSession\Olive.Services.ImpersonationSession.csproj", "{E66479DB-792B-4A2F-AA03-DEAAB128CE94}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.Testing", "Services\Olive.Services.Testing\Olive.Services.Testing.csproj", "{4C446033-19E3-4D88-B4A2-AE2D5190AF41}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Services.TaskAutomation", "Services\Olive.Services.TaskAutomation\Olive.Services.TaskAutomation.csproj", "{4EA5F66A-38DD-4CA8-B797-18A5CE083BFA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Mvc", "Olive.Mvc\Olive.Mvc.csproj", "{08ADF968-0FFF-4E3C-88EC-E04E2FA94D13}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.RemovalCandidates", "Mvc.RemovalCandidates\Mvc.RemovalCandidates.csproj", "{D6CB3117-1CB6-4263-AE89-37483A0379B9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Entities.Data.SqlServer", "Olive.Entities.Data.SqlServer\Olive.Entities.Data.SqlServer.csproj", "{8BE6BB58-FFF0-4057-9027-94CF7B96A0E3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Olive.Entities.Data.MySql", "Olive.Entities.Data.MySql\Olive.Entities.Data.MySql.csproj", "{8614E659-EA39-4D64-B014-A4ED525EE045}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OliveVSIX", "OliveVSIX\OliveVSIX.csproj", "{83FFCB26-B836-4E43-8F16-3FC0B29A7572}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EF844D85-7C45-4725-B033-DBC6111C5F7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF844D85-7C45-4725-B033-DBC6111C5F7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF844D85-7C45-4725-B033-DBC6111C5F7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF844D85-7C45-4725-B033-DBC6111C5F7F}.Release|Any CPU.Build.0 = Release|Any CPU + {81D870C8-EA2A-46DA-A936-0D4D520B2C32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {81D870C8-EA2A-46DA-A936-0D4D520B2C32}.Debug|Any CPU.Build.0 = Debug|Any CPU + {81D870C8-EA2A-46DA-A936-0D4D520B2C32}.Release|Any CPU.ActiveCfg = Release|Any CPU + {81D870C8-EA2A-46DA-A936-0D4D520B2C32}.Release|Any CPU.Build.0 = Release|Any CPU + {35B19C59-D221-47AF-AAC6-F9CEB0953FFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35B19C59-D221-47AF-AAC6-F9CEB0953FFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35B19C59-D221-47AF-AAC6-F9CEB0953FFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35B19C59-D221-47AF-AAC6-F9CEB0953FFB}.Release|Any CPU.Build.0 = Release|Any CPU + {EA922ABB-FF72-4C92-B5BE-C4FEAF5AC168}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA922ABB-FF72-4C92-B5BE-C4FEAF5AC168}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA922ABB-FF72-4C92-B5BE-C4FEAF5AC168}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA922ABB-FF72-4C92-B5BE-C4FEAF5AC168}.Release|Any CPU.Build.0 = Release|Any CPU + {AB221943-A14F-4817-BAA6-A6829841EA51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB221943-A14F-4817-BAA6-A6829841EA51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB221943-A14F-4817-BAA6-A6829841EA51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB221943-A14F-4817-BAA6-A6829841EA51}.Release|Any CPU.Build.0 = Release|Any CPU + {50ABA031-EAD6-4554-AC73-8BCA56C637B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50ABA031-EAD6-4554-AC73-8BCA56C637B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50ABA031-EAD6-4554-AC73-8BCA56C637B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50ABA031-EAD6-4554-AC73-8BCA56C637B2}.Release|Any CPU.Build.0 = Release|Any CPU + {FCE33030-315F-4102-87B9-E36B21F666B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCE33030-315F-4102-87B9-E36B21F666B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCE33030-315F-4102-87B9-E36B21F666B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCE33030-315F-4102-87B9-E36B21F666B0}.Release|Any CPU.Build.0 = Release|Any CPU + {E6C2B0FC-8C65-4AD9-A50F-21484D0E9F06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E6C2B0FC-8C65-4AD9-A50F-21484D0E9F06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E6C2B0FC-8C65-4AD9-A50F-21484D0E9F06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E6C2B0FC-8C65-4AD9-A50F-21484D0E9F06}.Release|Any CPU.Build.0 = Release|Any CPU + {253531A7-A460-4D61-9A85-A3E29B33AA94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {253531A7-A460-4D61-9A85-A3E29B33AA94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {253531A7-A460-4D61-9A85-A3E29B33AA94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {253531A7-A460-4D61-9A85-A3E29B33AA94}.Release|Any CPU.Build.0 = Release|Any CPU + {5EE54E7F-07C7-4966-9EFA-5E2252206B77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5EE54E7F-07C7-4966-9EFA-5E2252206B77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5EE54E7F-07C7-4966-9EFA-5E2252206B77}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5EE54E7F-07C7-4966-9EFA-5E2252206B77}.Release|Any CPU.Build.0 = Release|Any CPU + {5B7D4B8F-66B2-4A74-A42F-37298CF1878F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B7D4B8F-66B2-4A74-A42F-37298CF1878F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B7D4B8F-66B2-4A74-A42F-37298CF1878F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B7D4B8F-66B2-4A74-A42F-37298CF1878F}.Release|Any CPU.Build.0 = Release|Any CPU + {2CCA47DA-038F-4C63-8E7F-9F4A9296BB44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2CCA47DA-038F-4C63-8E7F-9F4A9296BB44}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2CCA47DA-038F-4C63-8E7F-9F4A9296BB44}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2CCA47DA-038F-4C63-8E7F-9F4A9296BB44}.Release|Any CPU.Build.0 = Release|Any CPU + {0199C138-414E-46AF-8898-3724D3331F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0199C138-414E-46AF-8898-3724D3331F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0199C138-414E-46AF-8898-3724D3331F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0199C138-414E-46AF-8898-3724D3331F4C}.Release|Any CPU.Build.0 = Release|Any CPU + {55390C42-F8DE-4089-B079-74E18714236D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55390C42-F8DE-4089-B079-74E18714236D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55390C42-F8DE-4089-B079-74E18714236D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55390C42-F8DE-4089-B079-74E18714236D}.Release|Any CPU.Build.0 = Release|Any CPU + {816FFC81-38E0-442C-8236-742551AC8AFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {816FFC81-38E0-442C-8236-742551AC8AFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {816FFC81-38E0-442C-8236-742551AC8AFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {816FFC81-38E0-442C-8236-742551AC8AFC}.Release|Any CPU.Build.0 = Release|Any CPU + {E66479DB-792B-4A2F-AA03-DEAAB128CE94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E66479DB-792B-4A2F-AA03-DEAAB128CE94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E66479DB-792B-4A2F-AA03-DEAAB128CE94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E66479DB-792B-4A2F-AA03-DEAAB128CE94}.Release|Any CPU.Build.0 = Release|Any CPU + {4C446033-19E3-4D88-B4A2-AE2D5190AF41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C446033-19E3-4D88-B4A2-AE2D5190AF41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C446033-19E3-4D88-B4A2-AE2D5190AF41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C446033-19E3-4D88-B4A2-AE2D5190AF41}.Release|Any CPU.Build.0 = Release|Any CPU + {4EA5F66A-38DD-4CA8-B797-18A5CE083BFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EA5F66A-38DD-4CA8-B797-18A5CE083BFA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EA5F66A-38DD-4CA8-B797-18A5CE083BFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EA5F66A-38DD-4CA8-B797-18A5CE083BFA}.Release|Any CPU.Build.0 = Release|Any CPU + {08ADF968-0FFF-4E3C-88EC-E04E2FA94D13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08ADF968-0FFF-4E3C-88EC-E04E2FA94D13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08ADF968-0FFF-4E3C-88EC-E04E2FA94D13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08ADF968-0FFF-4E3C-88EC-E04E2FA94D13}.Release|Any CPU.Build.0 = Release|Any CPU + {D6CB3117-1CB6-4263-AE89-37483A0379B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D6CB3117-1CB6-4263-AE89-37483A0379B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D6CB3117-1CB6-4263-AE89-37483A0379B9}.Release|Any CPU.Build.0 = Release|Any CPU + {8BE6BB58-FFF0-4057-9027-94CF7B96A0E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BE6BB58-FFF0-4057-9027-94CF7B96A0E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BE6BB58-FFF0-4057-9027-94CF7B96A0E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BE6BB58-FFF0-4057-9027-94CF7B96A0E3}.Release|Any CPU.Build.0 = Release|Any CPU + {8614E659-EA39-4D64-B014-A4ED525EE045}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8614E659-EA39-4D64-B014-A4ED525EE045}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8614E659-EA39-4D64-B014-A4ED525EE045}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8614E659-EA39-4D64-B014-A4ED525EE045}.Release|Any CPU.Build.0 = Release|Any CPU + {83FFCB26-B836-4E43-8F16-3FC0B29A7572}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83FFCB26-B836-4E43-8F16-3FC0B29A7572}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83FFCB26-B836-4E43-8F16-3FC0B29A7572}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83FFCB26-B836-4E43-8F16-3FC0B29A7572}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {AB221943-A14F-4817-BAA6-A6829841EA51} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {50ABA031-EAD6-4554-AC73-8BCA56C637B2} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {FCE33030-315F-4102-87B9-E36B21F666B0} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {E6C2B0FC-8C65-4AD9-A50F-21484D0E9F06} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {253531A7-A460-4D61-9A85-A3E29B33AA94} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {5EE54E7F-07C7-4966-9EFA-5E2252206B77} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {5B7D4B8F-66B2-4A74-A42F-37298CF1878F} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {2CCA47DA-038F-4C63-8E7F-9F4A9296BB44} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {0199C138-414E-46AF-8898-3724D3331F4C} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {55390C42-F8DE-4089-B079-74E18714236D} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {816FFC81-38E0-442C-8236-742551AC8AFC} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {E66479DB-792B-4A2F-AA03-DEAAB128CE94} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {4C446033-19E3-4D88-B4A2-AE2D5190AF41} = {943E7871-2EEA-4C97-857B-16443CB240E6} + {4EA5F66A-38DD-4CA8-B797-18A5CE083BFA} = {943E7871-2EEA-4C97-857B-16443CB240E6} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B8459027-6FAF-42E6-B8A8-4887D7730704} + EndGlobalSection +EndGlobal diff --git a/Olive/-Extensions/@Misc.cs b/Olive/-Extensions/@Misc.cs new file mode 100644 index 000000000..0f6035b03 --- /dev/null +++ b/Olive/-Extensions/@Misc.cs @@ -0,0 +1,120 @@ +using System; +using System.ComponentModel; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; + +namespace Olive +{ + /// + /// Provides extensions methods to Standard .NET types. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public static partial class OliveExtensions + { + const int MAXIMUM_ATTEMPTS = 3; + const int ATTEMPT_PAUSE = 50 /*Milisseconds*/; + + /// + /// Shortens this GUID. + /// + public static ShortGuid Shorten(this Guid guid) => new ShortGuid(guid); + + static async Task TryHard(FileSystemInfo fileOrFolder, Func> func, string error) + { + var result = default(T); + await TryHard(fileOrFolder, async () => { result = await func(); }, error); + return result; + } + + static async Task TryHard(FileSystemInfo fileOrFolder, Func func, string error) + { + var attempt = 0; + + Exception problem = null; + + while (attempt <= MAXIMUM_ATTEMPTS) + { + try + { + await func?.Invoke(); + return; + } + catch (Exception ex) + { + problem = ex; + + // Remove attributes: + try { fileOrFolder.Attributes = FileAttributes.Normal; } + catch + { + // No logging needed + } + + attempt++; + + // Pause for a short amount of time (to allow a potential external process to leave the file/directory). + await Task.Delay(ATTEMPT_PAUSE); + } + } + + throw new IOException(error.FormatWith(fileOrFolder.FullName), problem); + } + + /// + /// Will set the Position to zero, and then copy all bytes to a memory stream's buffer. + /// + public static async Task ReadAllBytes(this Stream stream) + { + using (var memoryStream = new MemoryStream()) + { + stream.Position = 0; + await stream.CopyToAsync(memoryStream); + return memoryStream.ToArray(); + } + } + + /// + /// Returns a nullable value wrapper object if this value is the default for its type. + /// + public static T? NullIfDefault(this T @value, T defaultValue = default(T)) where T : struct + { + if (value.Equals(defaultValue)) return null; + + return @value; + } + + /// + /// Gets the full path of a file or directory from a specified relative path. + /// + public static string GetPath(this AppDomain applicationDomain, params string[] relativePathSections) + { + var result = applicationDomain.BaseDirectory; + + foreach (var path in relativePathSections) + if (path.HasValue()) + result = Path.Combine(result, path.Replace('/', Path.DirectorySeparatorChar)); + + return result; + } + + public static Assembly LoadAssembly(this AppDomain domain, string assemblyName) + { + var result = domain.GetAssemblies().FirstOrDefault(a => a.FullName == assemblyName); + if (result != null) return result; + + // Nothing found with exact name. Try with file name. + var fileName = assemblyName.EnsureEndsWith(".dll", caseSensitive: false); + + var file = domain.GetPath(fileName).AsFile(); + if (file.Exists()) + return Assembly.Load(AssemblyName.GetAssemblyName(file.FullName)); + + // Maybe absolute file? + if (File.Exists(fileName)) + return Assembly.Load(AssemblyName.GetAssemblyName(fileName)); + + throw new Exception($"Failed to find the requrested assembly: '{assemblyName}'"); + } + } +} \ No newline at end of file diff --git a/Olive/-Extensions/Boolean.cs b/Olive/-Extensions/Boolean.cs new file mode 100644 index 000000000..d48fdab66 --- /dev/null +++ b/Olive/-Extensions/Boolean.cs @@ -0,0 +1,26 @@ +namespace Olive +{ + partial class OliveExtensions + { + public static string ToString(this bool? value, string format) => ("{0:" + format + "}").FormatWith(value); + + /// + /// Returns Yes or No string depending on whether the result is true of false. + /// + public static string ToYesNoString(this bool value, string yes = "Yes", string no = "No") => value ? yes : no; + + /// + /// Returns Yes or No string depending on whether the result is true of false. + /// + public static string ToYesNoString(this bool? value, string yes = "Yes", string no = "No") => + value.HasValue ? ToYesNoString(value.Value) : string.Empty; + + public static int CompareTo(this bool? @this, bool? another) + { + if (@this == another) return 0; + if (another == null) return @this.Value ? 1 : -1; + if (@this == null) return another.Value ? -1 : 1; + return @this.Value.CompareTo(another.Value); + } + } +} diff --git a/Olive/-Extensions/DataTable.cs b/Olive/-Extensions/DataTable.cs new file mode 100644 index 000000000..ba5c60f40 --- /dev/null +++ b/Olive/-Extensions/DataTable.cs @@ -0,0 +1,189 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Olive +{ + partial class OliveExtensions + { + /// + /// Casts this data table's records into a list of typed objects. + /// + public static IEnumerable CastTo(this DataTable dataTable) where T : new() => CastTo(dataTable, null); + + /// + /// Casts this data table's records into a list of typed objects. + /// An anonymouse object containing property mapping information. + /// e.g.: new {Property1 = "Property name in CSV", Property2 = "...", set_Property1 = new Func<string, object>(text => Client.Parse(value)) } + /// + public static IEnumerable CastTo(this DataTable dataTable, object propertyMappings) where T : new() => + CastAsDictionary(dataTable, propertyMappings).Select(i => i.Key).ToList(); + + /// + /// Casts this data table's records into a list of typed objects. + /// An anonymouse object containing property mapping information. + /// e.g.: new {Property1 = "Property name in CSV", Property2 = "...", set_Property1 = new Func<string, object>(text => Client.Parse(value)) } + /// + public static Dictionary CastAsDictionary(this DataTable data, object propertyMappings) where T : new() + { + if (propertyMappings != null) + foreach (var p in propertyMappings.GetType().GetProperties()) + { + if (p.PropertyType == typeof(string)) continue; + + if (p.PropertyType == typeof(Func)) + { + if (!p.Name.StartsWith("set_")) + throw new ArgumentException("Property convertors must start with 'set_{property name}'"); + + continue; + } + + throw new ArgumentException($"Unrecognized value for the property {p.PropertyType} of the specified propertyMappings"); + } + + var mappings = FindPropertyMappings(typeof(T), data.Columns, propertyMappings); + + var convertors = new Dictionary>(); + if (propertyMappings != null) + convertors = propertyMappings.GetType().GetProperties().Where(p => p.PropertyType == typeof(Func)) + .ToDictionary(p => p.Name.Substring(4), p => (Func)p.GetValue(propertyMappings)); + + var result = new Dictionary(); + + foreach (DataRow record in data.Rows) + { + var item = ParseObject(record, mappings, convertors); + result.Add(item, record); + } + + return result; + } + + /// + /// Finds the property mappings for the specified target type, CSV column names and user declared mappings. + /// + static Dictionary FindPropertyMappings(Type targetType, DataColumnCollection columns, object declaredMappings) + { + var result = new Dictionary(); + + if (declaredMappings != null) + { + foreach (var property in declaredMappings.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)) + { + if (property.Name.StartsWith("set_")) + { + if (!result.ContainsKey(property.Name.TrimStart("set_"))) + result.Add(property.Name.TrimStart("set_"), null); + continue; + } + + // Validate property name: + var propertyInTarget = targetType.GetProperty(property.Name); + if (propertyInTarget == null) + throw new Exception(targetType.FullName + " does not have a property named " + property.Name); + + if (!propertyInTarget.CanWrite) + throw new Exception($"{targetType.FullName}.{property.Name} property is read-only."); + + var mappedName = (string)property.GetValue(declaredMappings); + result[property.Name] = mappedName; + } + } + + var columnNames = columns.Cast().Select(c => c.ColumnName).ToArray(); + + foreach (var property in targetType.GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)) + { + if (!property.CanWrite) continue; + + if (result.ContainsKey(property.Name) && result[property.Name] != null) + continue; // Already added in explicit mappings. + + // Otherwise, if a column with that name is available, then that's it: + var potential = columnNames.Where(c => c.Replace(" ", "").ToLower() == property.Name.ToLower()); + if (potential.IsSingle()) + result[property.Name] = potential.Single(); + + else if (potential.Any()) + { + throw new Exception("The specified data contains multiple potential matches for the property '{0}'. The potentially matched columns found: {1}. You must use explicit mappings in this case." + .FormatWith(property.Name, potential.Select(c => $"'{c}'").ToString(", "))); + } + } + + return result; + } + + /// + /// Creates an object of the specified type with the specified data and property mappings. + /// + static T ParseObject(DataRow dataContainer, Dictionary propertyMappings, Dictionary> convertors) + { + var result = Activator.CreateInstance(); + + foreach (var mapping in propertyMappings) + { + var property = result.GetType().GetProperty(mapping.Key); + + string data; + + if (mapping.Value == null) + // The setter for this property is identified, while no property mapping is specified. + data = null; + else + data = dataContainer[mapping.Value]?.ToString().TrimOrNull(); + + try + { + object dataToSet; + + if (convertors.ContainsKey(mapping.Key)) + dataToSet = convertors[mapping.Key](data); + else + dataToSet = data.To(property.PropertyType); + + property.SetValue(result, dataToSet); + } + catch (Exception ex) + { + throw new Exception($"Could not set the value of the property '{mapping.Key}' from the value of '{data}'.", ex); + } + } + + return result; + } + + /// + /// Gets the CSV data equivalent to this data table. + /// + public static string ToCSV(this DataTable table) + { + var result = new StringBuilder(); + for (int i = 0; i < table.Columns.Count; i++) + { + result.Append(table.Columns[i].ColumnName); + result.Append(i == table.Columns.Count - 1 ? "\n" : ","); + } + + foreach (DataRow row in table.Rows) + { + for (int i = 0; i < table.Columns.Count; i++) + { + result.Append(row[i].ToString()); + result.Append(i == table.Columns.Count - 1 ? "\n" : ","); + } + } + + return result.ToString(); + } + + /// + /// Gets the rows of this data table in a LINQ-able format.. + /// + public static IEnumerable GetRows(this DataTable dataTable) => dataTable.Rows.Cast(); + } +} diff --git a/Olive/-Extensions/DateTime.cs b/Olive/-Extensions/DateTime.cs new file mode 100644 index 000000000..b8c2c38a7 --- /dev/null +++ b/Olive/-Extensions/DateTime.cs @@ -0,0 +1,893 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Olive +{ + partial class OliveExtensions + { + const int WEEK_DAYS_COUNT = 7; + const int CHRISTMAS_DAY = 25; + const int MINUS_THREE = -3; + const int MINUS_SIX = -6; + const int MAC_DAY_COUNT_IN_MONTH = 31; + const int HOURS_IN_A_DAY = 24; + + #region EasterMondays + + static DateTime[] EasterMondays = new[]{ +new DateTime(1950,04,10), +new DateTime(1951,03,26), +new DateTime(1952,04,14), +new DateTime(1953,04,06), +new DateTime(1954,04,19), +new DateTime(1955,04,11), +new DateTime(1956,04,02), +new DateTime(1957,04,22), +new DateTime(1958,04,07), +new DateTime(1959,03,30), +new DateTime(1960,04,18), +new DateTime(1961,04,03), +new DateTime(1962,04,23), +new DateTime(1963,04,15), +new DateTime(1964,03,30), +new DateTime(1965,04,19), +new DateTime(1966,04,11), +new DateTime(1967,03,27), +new DateTime(1968,04,15), +new DateTime(1969,04,07), +new DateTime(1970,03,30), +new DateTime(1971,04,12), +new DateTime(1972,04,03), +new DateTime(1973,04,23), +new DateTime(1974,04,15), +new DateTime(1975,03,31), +new DateTime(1976,04,19), +new DateTime(1977,04,11), +new DateTime(1978,03,27), +new DateTime(1979,04,16), +new DateTime(1980,04,07), +new DateTime(1981,04,20), +new DateTime(1982,04,12), +new DateTime(1983,04,04), +new DateTime(1984,04,23), +new DateTime(1985,04,08), +new DateTime(1986,03,31), +new DateTime(1987,04,20), +new DateTime(1988,04,04), +new DateTime(1989,03,27), +new DateTime(1990,04,16), +new DateTime(1991,04,01), +new DateTime(1992,04,20), +new DateTime(1993,04,12), +new DateTime(1994,04,04), +new DateTime(1995,04,17), +new DateTime(1996,04,08), +new DateTime(1997,03,31), +new DateTime(1998,04,13), +new DateTime(1999,04,05), +new DateTime(2000,04,24), +new DateTime(2001,04,16), +new DateTime(2002,04,01), +new DateTime(2003,04,21), +new DateTime(2004,04,12), +new DateTime(2005,03,28), +new DateTime(2006,04,17), +new DateTime(2007,04,09), +new DateTime(2008,03,24), +new DateTime(2009,04,13), +new DateTime(2010,04,05), +new DateTime(2011,04,25), +new DateTime(2012,04,09), +new DateTime(2013,04,01), +new DateTime(2014,04,21), +new DateTime(2015,04,06), +new DateTime(2016,03,28), +new DateTime(2017,04,17), +new DateTime(2018,04,02), +new DateTime(2019,04,22), +new DateTime(2020,04,13), +new DateTime(2021,04,05), +new DateTime(2022,04,18), +new DateTime(2023,04,10), +new DateTime(2024,04,01), +new DateTime(2025,04,21), +new DateTime(2026,04,06), +new DateTime(2027,03,29), +new DateTime(2028,04,17), +new DateTime(2029,04,02), +new DateTime(2030,04,22), +new DateTime(2031,04,14), +new DateTime(2032,03,29), +new DateTime(2033,04,18), +new DateTime(2034,04,10), +new DateTime(2035,03,26), +new DateTime(2036,04,14), +new DateTime(2037,04,06), +new DateTime(2038,04,26), +new DateTime(2039,04,11), +new DateTime(2040,04,02), +new DateTime(2041,04,22), +new DateTime(2042,04,07), +new DateTime(2043,03,30), +new DateTime(2044,04,18), +new DateTime(2045,04,10), +new DateTime(2046,03,26), +new DateTime(2047,04,15), +new DateTime(2048,04,06), +new DateTime(2049,04,19), +new DateTime(2050,04,11), +new DateTime(2051,04,03), +new DateTime(2052,04,22), +new DateTime(2053,04,07), +new DateTime(2054,03,30), +new DateTime(2055,04,19), +new DateTime(2056,04,03), +new DateTime(2057,04,23), +new DateTime(2058,04,15), +new DateTime(2059,03,31), +new DateTime(2060,04,19), +new DateTime(2061,04,11), +new DateTime(2062,03,27), +new DateTime(2063,04,16), +new DateTime(2064,04,07), +new DateTime(2065,03,30), +new DateTime(2066,04,12), +new DateTime(2067,04,04), +new DateTime(2068,04,23), +new DateTime(2069,04,15), +new DateTime(2070,03,31), +new DateTime(2071,04,20), +new DateTime(2072,04,11), +new DateTime(2073,03,27), +new DateTime(2074,04,16), +new DateTime(2075,04,08), +new DateTime(2076,04,20), +new DateTime(2077,04,12), +new DateTime(2078,04,04), +new DateTime(2079,04,24), +new DateTime(2080,04,08), +new DateTime(2081,03,31), +new DateTime(2082,04,20), +new DateTime(2083,04,05), +new DateTime(2084,03,27), +new DateTime(2085,04,16), +new DateTime(2086,04,01), +new DateTime(2087,04,21), +new DateTime(2088,04,12), +new DateTime(2089,04,04), +new DateTime(2090,04,17), +new DateTime(2091,04,09), +new DateTime(2092,03,31), +new DateTime(2093,04,13), +new DateTime(2094,04,05), +new DateTime(2095,04,25), +new DateTime(2096,04,16), +new DateTime(2097,04,01), +new DateTime(2098,04,21), +new DateTime(2099,04,13)}; + + #endregion EasterMondays + + /// + /// Dictionary that contains exceptional dates for Early May Bank Holiday. + /// Key: Year, Value: Exceptional Date for that year. + /// + static Dictionary EarlyMayExceptions = new Dictionary(); + + #region SpringBankHolidayExceptions + + /// + /// Dictionary that contains exceptional dates for Spring Bank Holiday. + /// Key: Year, Value: Exceptional Date for that year. + /// + static Dictionary SpringBankHolidayExceptions = new Dictionary + { + {2012,new DateTime(2012,06,04)} + }; + + #endregion SpringBankHolidayExceptions + + #region LateSummerBankHolidayExceptions + + /// + /// Dictionary that contains exceptional dates for Last Summer Bank Holiday. + /// Key: Year, Value: Exceptional Date for that year. + /// + static Dictionary LateSummerBankHolidayExceptions = new Dictionary(); + + #endregion LateSummerBankHolidayExceptions + + #region AdditionalBankHolidays + + /// + /// Dictionary that contains exceptional dates for Last Summer Bank Holiday. + /// Key: Year, Value: Exceptional Date for that year. + /// + static Dictionary AdditionalBankHolidays = new Dictionary + { + {2012,new[]{new DateTime(2012,6,5)}} + }; + + #endregion AdditionalBankHolidays + + /// + /// Determines if a specified date is an English national holiday or weekend. + /// + public static bool IsEnglishHoliday(this DateTime date) + { + date = date.Date; // drop time. + + if (date.IsWeekend()) return true; + + // 1 January - New Year's Day + if (date == GetActualHolidayDate(new DateTime(date.Year, 1, 1))) + return true; + + // 1st Monday in May Early May Bank Holiday + if (date == GetEarlyMayBankHoliday(date.Year)) + return true; + + // Last Monday in May Spring Bank Holiday + if (date == GetSpringBankHoliday(date.Year)) + return true; + + // Last Monday in August Late Summer Bank Holiday + if (date == GetLateSummerBankHoliday(date.Year)) + return true; + + // December 25 Christmas Day + if (date == GetActualHolidayDate(new DateTime(date.Year, 12, CHRISTMAS_DAY))) + return true; + + // December 26 Boxing Day + if (date == GetBoxingDay(date.Year)) + return true; + + try + { + var easterMonday = GetEasterMonday(date.Year); + + // Easter Monday + if (date == easterMonday) + return true; + + // Good Friday + if (date == easterMonday.AddDays(MINUS_THREE)) + return true; + } + catch { /* No logging needed. out of supported range*/ } + + // Additional Holidays + if (IsAdditionalBankHoliday(date)) + return true; + + return false; + } + + /// + /// Check if Date it is Additional bank holiday in that year + /// + /// the date to check + static bool IsAdditionalBankHoliday(DateTime date) + { + if (AdditionalBankHolidays.ContainsKey(date.Year)) + return AdditionalBankHolidays[date.Year].Contains(date); + + return false; + } + + /// + /// Gets the first upcoming specified week day. + /// + public static DateTime GetUpcoming(this DateTime date, DayOfWeek dayOfWeek, bool skipToday = false) + { + if (skipToday) date = date.Date.AddDays(1); + else date = date.Date; + + while (true) + { + if (date.DayOfWeek == dayOfWeek) return date; + + date = date.AddDays(1); + } + } + + /// + /// Gets the last occurance of the specified week day. + /// + public static DateTime GetLast(this DateTime date, DayOfWeek dayOfWeek, bool skipToday = false) + { + date = date.Date; + + if (skipToday) date = date.AddDays(-1); + + while (true) + { + if (date.DayOfWeek == dayOfWeek) return date; + + date = date.AddDays(-1); + } + } + + /// + /// Get Early May Bank Holiday Date for the required year + /// + /// the year to check if in that year there is an exception to the normal bank holiday rule + static DateTime GetEarlyMayBankHoliday(int year) + { + if (EarlyMayExceptions.ContainsKey(year)) + { + return EarlyMayExceptions[year]; + } + else + { + return GetFirstWeekdayIn(year, 5 /* May */, DayOfWeek.Monday); + } + } + + /// + /// Get Spring Bank Holiday Date for the required year + /// + /// the year to check if in that year there is an exception to the normal bank holiday rule + static DateTime GetSpringBankHoliday(int year) + { + if (SpringBankHolidayExceptions.ContainsKey(year)) + return SpringBankHolidayExceptions[year]; + else + return GetLastWeekdayIn(year, 5, DayOfWeek.Monday); + } + + /// + /// Get Late Summer Bank Holiday Date for the required year + /// + /// the year to check if in that year there is an exception to the normal bank holiday rule + static DateTime GetLateSummerBankHoliday(int year) + { + if (LateSummerBankHolidayExceptions.ContainsKey(year)) + return LateSummerBankHolidayExceptions[year]; + else + return GetLastWeekdayIn(year, (int)CalendarMonth.August, DayOfWeek.Monday); + } + + static DateTime GetEasterMonday(int year) + { + var result = EasterMondays.FirstOrDefault(d => d.Year == year); + + if (result == DateTime.MinValue) + throw new ArgumentException("GetEasterMonday() is not supported for the year: " + year); + + return result; + } + + /// + /// Get Boxing Day Holiday Date for the required year + /// + /// the year to check if in that year there is an exception to the normal bank holiday rule + static DateTime GetBoxingDay(int year) + { + var christmasDay = new DateTime(year, 12, CHRISTMAS_DAY); + var result = GetActualHolidayDate(new DateTime(year, 12, CHRISTMAS_DAY + 1)); + + if (christmasDay.IsWeekend()) + result = result.AddDays(1); + + return result; + } + + static DateTime GetActualHolidayDate(DateTime originalDay) + { + var result = originalDay; + while (result.IsWeekend()) + result = result.AddDays(1); + + return result; + } + + static DateTime GetFirstWeekdayIn(int year, int month, DayOfWeek weekday) + { + for (var day = new DateTime(year, month, 1); ; day = day.AddDays(1)) + if (day.DayOfWeek == weekday) return day; + } + + static DateTime GetLastWeekdayIn(int year, int month, DayOfWeek weekday) + { + for (var day = new DateTime(year, month, 1).AddMonths(1).AddDays(-1); ; day = day.AddDays(-1)) + if (day.DayOfWeek == weekday) return day; + } + + public static bool IsOlderThan(this DateTime source, TimeSpan span) => (LocalTime.Now - source) > span; + + public static bool IsNewerThan(this DateTime source, TimeSpan span) => (LocalTime.Now - source) < span; + + public static bool IsAfterOrEqualTo(this DateTime date, DateTime otherDate) => date >= otherDate; + + public static bool IsBeforeOrEqualTo(this DateTime date, DateTime otherDate) => date <= otherDate; + + /// + /// Determines whether this day is in the same week (Monday to Sunday) as the specified other date. + /// + public static bool IsInSameWeek(this DateTime day, DateTime other) + { + day = day.Date; + + var beginningOfWeek = day.GetBeginningOfWeek(); + + if (other < beginningOfWeek) return false; + if (other >= beginningOfWeek.AddDays(WEEK_DAYS_COUNT)) return false; + + return true; + } + + /// + /// Determines whether this day is in the same month as the specified other date. + /// + public static bool IsInSameMonth(this DateTime day, DateTime other) => day.Month == other.Month && day.Year == other.Year; + + /// + /// Gets the number of days in this year. + /// + public static int DaysInYear(this DateTime date) => new DateTime(date.Year, 12, MAC_DAY_COUNT_IN_MONTH).DayOfYear; + + /// + /// Gets the number of days in this month. + /// + public static int DaysInMonth(this DateTime date) => DateTime.DaysInMonth(date.Year, date.Month); + + /// + /// Gets the mid-night of Monday of this week. + /// + public static DateTime GetBeginningOfWeek(this DateTime day) => day.Date.GetLast(DayOfWeek.Monday, skipToday: false); + + /// + /// Gets one tick before the start of next week. + /// + public static DateTime GetEndOfWeek(this DateTime date, DayOfWeek startOfWeek = DayOfWeek.Monday) => + date.GetUpcoming(startOfWeek, skipToday: true).AddTicks(-1); + + /// + /// Gets the mid-night of the first day of this month. + /// + public static DateTime GetBeginningOfMonth(this DateTime day) => new DateTime(day.Year, day.Month, 1); + + /// + /// Gets the end of this day (one tick before the next day). + /// + public static DateTime EndOfDay(this DateTime date) + { + try + { + return date.Date.AddDays(1).AddTicks(-1); + } + catch (ArgumentOutOfRangeException) + { + return DateTime.MaxValue; + } + } + + public static DateTime? EndOfDay(this DateTime? date) => date?.EndOfDay(); + + /// + /// Determines whether this date is in the future. + /// + public static bool IsInTheFuture(this DateTime date) => date > LocalTime.Now; + + /// + /// Determines whether this date is in the future. + /// + public static bool IsTodayOrFuture(this DateTime date) => date.Date >= LocalTime.Today; + + /// + /// Determines whether this date is in the future. + /// + public static bool IsToday(this DateTime date) => date.Date == LocalTime.Today; + + public static bool IsInThePast(this DateTime date) => date < LocalTime.Now; + + public static bool IsAfter(this DateTime date, DateTime otherDate) => date > otherDate; + + public static bool IsBefore(this DateTime date, DateTime otherDate) => date < otherDate; + + /// + /// E.g. 4am or 6:30pm. + /// + public static string ToSmallTime(this DateTime date) => date.ToString("h:mm").TrimEnd(":00") + date.ToString("tt").ToLower(); + + public static bool IsWeekend(this DateTime value) => value.DayOfWeek == DayOfWeek.Sunday || value.DayOfWeek == DayOfWeek.Saturday; + + public static DateTime AddWorkingDays(this DateTime date, int days, bool considerEnglishBankHolidays = true) + { + if (days == 0) return date; + + var result = date; + + if (days > 0) + for (int day = 0; day < days; day++) + result = result.NextWorkingDay(considerEnglishBankHolidays); + else + for (int day = 0; day < -days; day++) + result = result.PreviousWorkingDay(considerEnglishBankHolidays); + + return result; + } + + public static string ToTimeDifferenceString(this DateTime? date, int precisionParts = 2, bool longForm = true) + { + if (date == null) return null; + return ToTimeDifferenceString(date.Value, precisionParts, longForm); + } + + public static string ToTimeDifferenceString(this DateTime date) => ToTimeDifferenceString(date, longForm: true); + + public static string ToTimeDifferenceString(this DateTime date, bool longForm) => ToTimeDifferenceString(date, 2, longForm); + + public static string ToTimeDifferenceString(this DateTime date, int precisionParts) => ToTimeDifferenceString(date, precisionParts, longForm: true); + + public static string ToTimeDifferenceString(this DateTime date, int precisionParts, bool longForm) + { + var now = LocalTime.Now; + + if (now == date) + { + if (longForm) + return "Just now"; + else + return "Now"; + } + + if (now > date) + return now.Subtract(date).ToNaturalTime(precisionParts, longForm) + " ago"; + else + return date.Subtract(now).ToNaturalTime(precisionParts, longForm) + " later"; + } + + public static string ToString(this DateTime? value, string format) => ($"{{0:{format}}}").FormatWith(value); + + /// + /// Gets the next working day. + /// + public static DateTime NextWorkingDay(this DateTime date, bool considerEnglishHolidays = true) + { + var result = date.AddDays(1); + + if (considerEnglishHolidays) + while (result.IsEnglishHoliday()) + result = result.AddDays(1); + else + while (result.IsWeekend()) + result = result.AddDays(1); + + return result; + } + + /// + /// Gets the days between this day and the specified other day. + /// It will remove TIME information. + /// + public static IEnumerable GetDaysInBetween(this DateTime day, DateTime other, bool inclusive = false) + { + day = day.Date; + other = other.Date; + + var from = day <= other ? day : other; + var to = day > other ? day : other; + + var count = (int)to.Subtract(from).TotalDays; + + if (!inclusive) count--; + else count++; + + if (count < 1) return new DateTime[0]; + + var numbers = Enumerable.Range(inclusive ? 0 : 1, count); + + var result = numbers.Select(i => from.AddDays(i)); + + if (day > other) + return result.Reverse(); + else + return result; + } + + /// + /// Gets the previous working day. + /// + public static DateTime PreviousWorkingDay(this DateTime date, bool considerEnglishHolidays = true) + { + var result = date.AddDays(-1); + + if (considerEnglishHolidays) + while (result.IsEnglishHoliday()) + result = result.AddDays(-1); + else + while (result.IsWeekend()) + result = result.AddDays(-1); + + return result; + } + + public static string ToFriendlyDateString(this DateTime date) + { + string formattedDate; + if (date.Date == DateTime.Today) + formattedDate = "Today"; + + else if (date.Date == DateTime.Today.AddDays(-1)) + formattedDate = "Yesterday"; + + else if (date.Date > DateTime.Today.AddDays(MINUS_SIX)) + // *** Show the Day of the week + formattedDate = date.ToString("dddd"); + + else + formattedDate = date.ToString("MMMM dd, yyyy"); + + // append the time portion to the output + formattedDate += " @ " + date.ToString("t").ToLower(); + return formattedDate; + } + + /// + /// Determines whether this date is between two sepcified dates. + /// + public static bool IsBetween(this DateTime date, DateTime from, DateTime to, bool includingEdges = true) + { + if (from > to) + throw new ArgumentException("\"From\" date should be smaller than or equal to \"To\" date."); + + if (date < from || date > to) return false; + + if (!includingEdges) + if (date == from || date == to) return false; + + return true; + } + + /// + /// Calculates the total working times in the specified duration which are between the two specified day-hours. + /// This can be used to calculate working hours in a particular duration. + /// + public static TimeSpan CalculateTotalWorkingHours(this DateTime date, TimeSpan period, TimeSpan workingStartTime, TimeSpan workingEndTime, bool considerEnglishBankHolidays = true) + { + if (period < TimeSpan.Zero) + throw new ArgumentException("duration should be a positive time span."); + + if (workingStartTime < TimeSpan.Zero || workingStartTime >= TimeSpan.FromHours(HOURS_IN_A_DAY)) + throw new ArgumentException("fromTime should be greater than or equal to 0, and smaller than 24."); + + if (workingEndTime <= TimeSpan.Zero || workingEndTime > TimeSpan.FromHours(HOURS_IN_A_DAY)) + throw new ArgumentException("toTime should be greater than 0, and smaller than or equal to 24."); + + var result = TimeSpan.Zero; + + // var inclusiveTimeSpan = toTime - fromTime; + + var workingTimesInday = new List>(); + if (workingEndTime > workingStartTime) + { + workingTimesInday.Add(new KeyValuePair(workingStartTime, workingEndTime)); + } + else + { + workingTimesInday.Add(new KeyValuePair(workingEndTime, TimeSpan.FromDays(1))); + workingTimesInday.Add(new KeyValuePair(TimeSpan.Zero, workingStartTime)); + } + + // For each working day in the range, calculate relevant times + for (var day = date.Date; day < date.Add(period); day = day.AddWorkingDays(1, considerEnglishBankHolidays)) + { + foreach (var range in workingTimesInday) + { + var from = day.Add(range.Key); + + if (from < date) from = date; + + var to = day.Add(range.Value); + if (to < date) continue; + + if (to > date.Add(period)) to = date.Add(period); + + var amount = to - from; + + if (amount < TimeSpan.Zero) continue; + + result += amount; + } + } + + return result; + } + + /// + /// Returns the Date of the beginning of Quarter for this DateTime value (time will be 00:00:00). + /// + public static DateTime GetBeginningOfQuarter(this DateTime date) + { + var startMonths = new[] { 1, 4, 7, 10 }; + + for (int i = startMonths.Length - 1; i >= 0; i--) + { + var beginningOfQuarter = new DateTime(date.Year, startMonths[i], 1); + if (date >= beginningOfQuarter) + return beginningOfQuarter; + } + + return DateTime.MinValue; + } + + /// + /// Returns the Date of the end of Quarter for this DateTime value (time will be 11:59:59). + /// + public static DateTime GetEndOfQuarter(this DateTime date) => + date.GetBeginningOfQuarter().AddMonths(4).GetBeginningOfQuarter().AddTicks(-1); + + /// + /// Returns the Date of the end of Quarter for this DateTime value (time will be 11:59:59). + /// + public static DateTime GetEndOfMonth(this DateTime date) => date.GetBeginningOfMonth().AddMonths(1).AddTicks(-1); + + public static bool IsLastDayOfMonth(this DateTime date) => date.Date == date.GetEndOfMonth().Date; + + /// + /// Gets the last date with the specified month and day. + /// + public static DateTime GetLast(this DateTime date, CalendarMonth month, int day) + { + date = date.Date; // cut time + + var thisYear = new DateTime(date.Year, (int)month, day); + + if (date >= thisYear) return thisYear; + else return new DateTime(date.Year - 1, (int)month, day); + } + + /// + /// Gets the last date with the specified month and day. + /// + public static DateTime GetNext(this DateTime date, CalendarMonth month, int day) + { + date = date.Date; // cut time + + var thisYear = new DateTime(date.Year, (int)month, day); + + if (date >= thisYear) return new DateTime(date.Year + 1, (int)month, day); + else return thisYear; + } + + /// + /// Returns the Date of the end of Quarter for this DateTime value (time will be 11:59:59). + /// + public static DateTime GetEndOfYear(this DateTime date) => new DateTime(date.Year + 1, 1, 1).AddTicks(-1); + + /// + /// Gets the minimum value between this date and a specified other date. + /// + public static DateTime Min(this DateTime date, DateTime other) + { + if (other < date) + return other; + else + return date; + } + + /// + /// Gets the maximum value between this date and a specified other date. + /// + public static DateTime Max(this DateTime date, DateTime other) + { + if (other > date) + return other; + else + return date; + } + + /// + /// Adds the specified number of weeks and returns the result. + /// + public static DateTime AddWeeks(this DateTime date, int numberofWeeks) => date.AddDays(WEEK_DAYS_COUNT * numberofWeeks); + + /// + /// Gets the latest date with the specified day of week and time that is before (or same as) this date. + /// + public static DateTime GetLast(this DateTime date, DayOfWeek day, TimeSpan timeOfDay) + { + var result = date.GetLast(day).Add(timeOfDay); + + if (result > date) + { + return result.AddWeeks(-1); + } + else + { + return result; + } + } + + /// + /// Returns the local time equivalent of this UTC date value based on the TimeZone specified in Localtime.TimeZoneProvider. + /// Use this instead of ToLocalTime() so you get control over the TimeZone. + /// + public static DateTime? ToLocal(this DateTime? utcValue) => utcValue?.ToLocal(); + + /// + /// Returns the local time equivalent of this UTC date value based on the TimeZone specified in Localtime.CurrentTimeZone(). + /// Use this instead of ToLocalTime() so you get control over the TimeZone. + /// + public static DateTime ToLocal(this DateTime utcValue) => utcValue.ToLocal(LocalTime.CurrentTimeZone()); + + public static DateTime ToLocal(this DateTime utcValue, TimeZoneInfo timeZone) => + new DateTime(utcValue.Ticks, DateTimeKind.Local).Add(timeZone.GetUtcOffset(utcValue)); + + /// + /// Returns the equivalent Universal Time (UTC) of this local date value. + /// + public static DateTime? ToUniversal(this DateTime? localValue) => localValue?.ToUniversal(); + + /// + /// Returns the equivalent Universal Time (UTC) of this local date value. + /// + public static DateTime ToUniversal(this DateTime localValue) => + localValue.ToUniversal(sourceTimezone: LocalTime.CurrentTimeZone()); + + /// + /// Returns the equivalent Universal Time (UTC) of this local date value. + /// + public static DateTime ToUniversal(this DateTime localValue, TimeZoneInfo sourceTimezone) => + new DateTime(localValue.Ticks, DateTimeKind.Utc).Subtract(sourceTimezone.BaseUtcOffset); + + /// + /// Rounds this up to the nearest whole second. + /// + public static DateTime RoundToSecond(this DateTime @this) => @this.Round(1.Seconds()); + + /// + /// Rounds this up to the nearest whole minute. + /// + public static DateTime RoundToMinute(this DateTime @this) => @this.Round(1.Minutes()); + + /// + /// Rounds this up to the nearest whole hour. + /// + public static DateTime RoundToHour(this DateTime @this) => @this.Round(1.Hours()); + + /// + /// Rounds this up to the nearest interval (e.g. second, minute, hour, etc). + /// + public static DateTime Round(this DateTime dateTime, TimeSpan nearest) + { + if (nearest == TimeSpan.Zero) return dateTime; + + var remainder = dateTime.Ticks % nearest.Ticks; + + var result = dateTime.AddTicks(-remainder); + + if (remainder >= nearest.Ticks / 2) result = result.Add(nearest); + + return result; + } + + public static int CompareTo(this DateTime? @this, DateTime? another) + { + if (@this == another) return 0; + if (another == null) return 1; + if (@this == null) return -1; + return @this.Value.CompareTo(another.Value); + } + } + + public enum CalendarMonth + { + January = 1, + February = 2, + March = 3, + April = 4, + May = 5, + June = 6, + July = 7, + August = 8, + September = 9, + October = 10, + November = 11, + December = 12 + } +} \ No newline at end of file diff --git a/Olive/-Extensions/Delegates.cs b/Olive/-Extensions/Delegates.cs new file mode 100644 index 000000000..a26dfff05 --- /dev/null +++ b/Olive/-Extensions/Delegates.cs @@ -0,0 +1,86 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Olive +{ + partial class OliveExtensions + { + /// + /// Invokes the specified action for the specified number of times. + /// + /// The action to execute. + /// The number of times to try running the action. + /// The time to wait before every two retries. + /// The action to run every time the method invokation fails. You can use this to log the error. + public static void Invoke(this Action action, int retries, TimeSpan waitBeforeRetries, Action onEveryError = null) + { + var asFunction = new Func(() => { action?.Invoke(); return null; }); + asFunction.Invoke(retries, waitBeforeRetries, onEveryError); + } + + /// + /// Invokes the specified function for the specified number of times. + /// + /// The function to evaluate. + /// The number of times to try running the action. + /// The time to wait before every two retries. + /// The action to run every time the method invokation fails. You can use this to log the error. + public static T Invoke(this Func function, int retries, TimeSpan waitBeforeRetries, Action onEveryError = null) + { + if (retries < 2) + throw new Exception("retries should be greater than 2."); + + var count = 0; + + while (true) + { + try + { + return function(); + } + catch (Exception ex) + { + count++; + + onEveryError?.Invoke(ex); + + if (count == retries) + { + // Give up: + throw; + } + + Thread.Sleep(waitBeforeRetries); + } + } + } + + public static async Task WithTimeout(this Task task, TimeSpan timeout, Action success = null, Action timeoutAction = null) + { + if (await Task.WhenAny(task, Task.Delay(timeout)) == task) success?.Invoke(); + + else + { + if (timeoutAction == null) throw new TimeoutException("The task didn't complete within " + timeout + "ms"); + else timeoutAction(); + } + } + + public static async Task WithTimeout(this Task task, TimeSpan timeout, Action success = null, Func timeoutAction = null) + { + if (await Task.WhenAny(task, Task.Delay(timeout)) == task) + { + success?.Invoke(); + await task; + return task.GetAwaiter().GetResult(); + } + + else + { + if (timeoutAction == null) throw new TimeoutException("The task didn't complete within " + timeout + "ms"); + else { return timeoutAction(); } + } + } + } +} \ No newline at end of file diff --git a/Olive/-Extensions/DirectoryInfo.cs b/Olive/-Extensions/DirectoryInfo.cs new file mode 100644 index 000000000..3a00a08a8 --- /dev/null +++ b/Olive/-Extensions/DirectoryInfo.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Olive +{ + partial class OliveExtensions + { + /// + /// If specified as recursive and harshly, then it tries multiple times to delete this directory. + /// + public static async Task Delete(this DirectoryInfo directory, bool recursive, bool harshly) + { + if (directory == null) + throw new ArgumentNullException(nameof(directory)); + + if (!directory.Exists()) return; + + if (harshly && !recursive) + throw new ArgumentException("For deleting a folder harshly, the recursive option should also be specified."); + + if (!harshly) + { + directory.Delete(recursive); + return; + } + + // Otherwise, it is harsh and recursive: + try + { + // First attempt: Simple delete: + await Task.Factory.StartNew(() => directory.Delete(recursive: true)); + } + catch + { + // No loging is needed + // Normal attempt failed. Let's try it harshly! + await HarshDelete(directory); + } + } + + /// + /// Will try to delete a specified directory by first deleting its sub-folders and files. + /// + static async Task HarshDelete(DirectoryInfo directory) + { + if (!directory.Exists()) return; + + await TryHard(directory, async () => + { + await directory.GetFiles().Do(async (f) => await f.Delete(harshly: true)); + await directory.GetDirectories().Do(async (d) => await HarshDelete(d)); + await Task.Factory.StartNew(directory.Delete); + }, "The system cannot delete the directory, even after several attempts. Directory: {0}"); + } + + /// + /// Copies the entire content of a directory to a specified destination. + /// + public static async Task CopyTo(this DirectoryInfo source, DirectoryInfo destination, bool overwrite = false) => + await CopyTo(source, destination.FullName, overwrite); + + /// + /// Determines whether the file's contents start with MZ which is the signature for EXE files. + /// + public static bool HasExeContent(this FileInfo file) + { + var twoBytes = new byte[2]; + using (var fileStream = File.Open(file.FullName, FileMode.Open)) + { + try + { + fileStream.Read(twoBytes, 0, 2); + } + catch + { + // No logging is needed + return false; // No content + } + } + + return Encoding.UTF8.GetString(twoBytes) == "MZ"; + } + + /// + /// Copies the entire content of a directory to a specified destination. + /// + public static async Task CopyTo(this DirectoryInfo source, string destination, bool overwrite = false) + { + destination.AsDirectory().EnsureExists(); + + foreach (var file in source.GetFiles()) + await file.CopyTo(Path.Combine(destination, file.Name).AsFile(), overwrite); + + foreach (var sub in source.GetDirectories()) + await sub.CopyTo(Path.Combine(destination, sub.Name), overwrite); + } + + /// + /// Copies this file to a specified destination directiry with the original file name. + /// + public static async Task CopyTo(this FileInfo file, DirectoryInfo destinationDirectory, bool overwrite = false) => + await file.CopyTo(destinationDirectory.GetFile(file.Name), overwrite); + + public static string[] GetFiles(this DirectoryInfo folder, bool includeSubDirectories) + { + var result = new List(folder.GetFiles().Select(f => f.FullName)); + + if (includeSubDirectories) + foreach (var subFolder in folder.GetDirectories()) + result.AddRange(subFolder.GetFiles(includeSubDirectories: true)); + + return result.ToArray(); + } + + /// + /// Gets a file info with the specified name under this folder. That file does not have to exist already. + /// + public static FileInfo GetFile(this DirectoryInfo folder, string fileName) => + Path.Combine(folder.FullName, fileName).AsFile(); + + /// + /// Gets a subdirectory with the specified name. It does not need to exist necessarily. + /// + public static DirectoryInfo GetSubDirectory(this DirectoryInfo parent, string subdirectoryName) + { + if (subdirectoryName.IsEmpty()) + throw new ArgumentNullException("GetSubDirectory(name) expects a non-empty sub-directory name."); + + return new DirectoryInfo(Path.Combine(parent.FullName, subdirectoryName)); + } + + /// + /// Gets or creates a subdirectory with the specified name. + /// + public static DirectoryInfo GetOrCreateSubDirectory(this DirectoryInfo parent, string subdirectoryName) + { + var result = new DirectoryInfo(Path.Combine(parent.FullName, subdirectoryName)); + + result.Create(); + + return result; + } + + /// + /// Gets the subdirectory tree of this directory. + /// + public static IEnumerable GetDirectories(this DirectoryInfo parent, bool recursive) + { + if (!recursive) return parent.GetDirectories(); + else + { + var result = parent.GetDirectories().ToList(); + + foreach (var sub in parent.GetDirectories()) + result.AddRange(sub.GetDirectories(recursive: true)); + + return result; + } + } + + /// + /// Creates the directory if it doesn't already exist. + /// + public static DirectoryInfo EnsureExists(this DirectoryInfo folder) + { + if (!folder.Exists()) + Directory.CreateDirectory(folder.FullName); + + // if (!folder.Exists) folder.Create(); This has caching bug in the core .NET code :-( + + return folder; + } + + /// + /// Clears the specified folder by deleting all its sub-directories and files. + /// + public static async Task Clear(this DirectoryInfo folder, bool harshly = true) + { + if (!folder.Exists()) throw new Exception("The specified directory does not exist: " + folder.FullName); + await folder.GetFiles().Do(async (f) => await f.Delete(harshly)); + await folder.GetDirectories().Do(async (f) => await f.Delete(recursive: true, harshly: harshly)); + } + + /// + /// Determines whether this folder is empty of any files or sub-directories. + /// + public static bool IsEmpty(this DirectoryInfo folder) + { + if (folder.GetFiles().Any()) return false; + if (folder.GetDirectories().Any()) return false; + + return true; + } + } +} diff --git a/Olive/-Extensions/Double.cs b/Olive/-Extensions/Double.cs new file mode 100644 index 000000000..d92d4aa19 --- /dev/null +++ b/Olive/-Extensions/Double.cs @@ -0,0 +1,205 @@ +using System; + +namespace Olive +{ + partial class OliveExtensions + { + const int HALF_CIRCLE_DEGREES = 180; + + /// + /// Rounds this value. + /// + public static double Round(this double value, int digits) => + (double)Math.Round((decimal)value, digits, MidpointRounding.AwayFromZero); + + /// + /// Rounds this value. + /// + public static decimal Round(this decimal value, int digits) => + Math.Round(value, digits, MidpointRounding.AwayFromZero); + + /// + /// In mathematics and computer science, truncation is the term for limiting the number of digits right of the decimal point, by discarding the least significant ones. + /// Note that in some cases, truncating would yield the same result as rounding, but truncation does not round up or round down the digits; it merely cuts off at the specified digit. + /// + public static double Truncate(this double value, int places) + { + var multiplier = Math.Pow(10, (double)places); + + if (value > 0) + return Math.Floor(value * multiplier) / multiplier; + else + return Math.Ceiling(value * multiplier) / multiplier; + } + + public static string ToString(this double? value, string format) => ($"{{0:{format}}}").FormatWith(value); + + public static string ToString(this decimal? value, string format) => ($"{{0:{format}}}").FormatWith(value); + + /// + /// Drops the floating point digits from the end of the money string. + /// For example for 1500.00 it will yield "£1,500" and for 18.56 it will yield "£18.56". + /// + public static string ToShortMoneyString(this double value) => "{0:c}".FormatWith(value).TrimEnd(".00"); + + public static string ToShortMoneyString(this double? value) + { + if (value.HasValue) return value.ToShortMoneyString(); + else return string.Empty; + } + + /// + /// Drops the floating point digits from the end of the money string. + /// For example for 1500.00 it will yield "£1,500" and for 18.56 it will yield "£18.56". + /// + public static string ToInformalMoneyString(this double value) + { + var identifiers = new[] { "k", "m", "bn" }; + + for (var i = identifiers.Length; i > 0; i--) + { + var power = 2 + 3 * (i - 1); + + var figure = Math.Pow(10, power); + + if (value % figure == 0 && (value / figure) > 10) + { + value = value / figure * 10; + + if (value > 1000) + return value.ToShortMoneyString() + identifiers[i - 1]; + else + return value.ToShortMoneyString()[0].ToString() + value + identifiers[i - 1]; + } + } + + return value.ToShortMoneyString(); + } + + /// + /// Converts degree into radians. + /// + public static double ToRadians(this double degrees) => Math.PI * degrees / HALF_CIRCLE_DEGREES; + + /// + /// Return this value as a percentages the of the given total. + /// + public static double AsPercentageOf(this double value, double total, bool multiplyBy100 = true, int? roundTo = null) + { + var pc = value / total; + + if (double.IsNaN(pc) || double.IsInfinity(pc)) return 0d; + + if (multiplyBy100) pc = pc * 100d; + + if (roundTo.HasValue) pc = pc.Round(roundTo.Value); + + return pc; + } + + /// + /// Return this value as a percentages the of the given total. + /// + public static decimal AsPercentageOf(this decimal value, decimal total, bool multiplyBy100 = true, int? roundTo = null) + { + var pc = value / total; + + if (multiplyBy100) pc = pc * 100; + + if (roundTo.HasValue) + pc = Math.Round(pc, roundTo.Value); + + return pc; + } + + /// + /// Rounds up to nearest value. + /// + public static double RoundUpToNearest(this double value, double roundIntervals) + { + var remainder = value % roundIntervals; + if (remainder == 0) return value; + + return value + (roundIntervals - remainder); + } + + /// + /// Rounds up to nearest value. + /// + public static decimal RoundUpToNearest(this decimal value, decimal roundIntervals) + { + var remainder = value % roundIntervals; + if (remainder == 0) return value; + + return value + (roundIntervals - remainder); + } + + /// + /// Rounds down to nearest value with the intervals specified. + /// + public static double RoundDownToNearest(this double value, double roundIntervals) => value - (value % roundIntervals); + + /// + /// Rounds down to nearest value with the intervals specified. + /// + public static decimal RoundDownToNearest(this decimal value, decimal roundIntervals) => value - (value % roundIntervals); + + /// + /// Determines if this double value is almost equal to the specified other value. + /// This should be used instead of == or != operators due to the nature of double processing in .NET. + /// + /// Specifies the tolerated level of difference. + public static bool AlmostEquals(this double value, double otherValue, double tolerance = 0.00001) => + Math.Abs(value - otherValue) <= tolerance; + + /// + /// Determines if this float value is almost equal to the specified other value. + /// This should be used instead of == or != operators due to the nature of float processing in .NET. + /// + /// Specifies the tolerated level of difference. + public static bool AlmostEquals(this float value, float otherValue, float tolerance = 0.001f) => + Math.Abs(value - otherValue) <= tolerance; + + public static float LimitMax(this float value, float maxValue) => value > maxValue ? maxValue : value; + + public static float LimitMin(this float value, float minValue) => value < minValue ? minValue : value; + + public static float LimitWithin(this float value, float minValue, float maxValue) => value.LimitMin(minValue).LimitMax(maxValue); + + public static double LimitMax(this double value, double maxValue) => value > maxValue ? maxValue : value; + + public static double LimitMin(this double value, double minValue) => value < minValue ? minValue : value; + + public static double LimitWithin(this double value, double minValue, double maxValue) => value.LimitMin(minValue).LimitMax(maxValue); + + public static int LimitMax(this int value, int maxValue) => value > maxValue ? maxValue : value; + + public static int LimitMin(this int value, int minValue) => value < minValue ? minValue : value; + + public static int LimitWithin(this int value, int minValue, int maxValue) => value.LimitMin(minValue).LimitMax(maxValue); + + public static int CompareTo(this double? @this, double? another) + { + if (@this == another) return 0; + if (another == null) return @this < 0 ? -1 : 1; + if (@this == null) return another < 0 ? 1 : -1; + return @this.Value.CompareTo(another.Value); + } + + public static int CompareTo(this float? @this, float? another) + { + if (@this == another) return 0; + if (another == null) return @this < 0 ? -1 : 1; + if (@this == null) return another < 0 ? 1 : -1; + return @this.Value.CompareTo(another.Value); + } + + public static int CompareTo(this decimal? @this, decimal? another) + { + if (@this == another) return 0; + if (another == null) return @this < 0 ? -1 : 1; + if (@this == null) return another < 0 ? 1 : -1; + return @this.Value.CompareTo(another.Value); + } + } +} diff --git a/Olive/-Extensions/EmbeddedResource.cs b/Olive/-Extensions/EmbeddedResource.cs new file mode 100644 index 000000000..428300f9c --- /dev/null +++ b/Olive/-Extensions/EmbeddedResource.cs @@ -0,0 +1,76 @@ +namespace Olive +{ + using System; + using System.Text; + using System.Diagnostics; + using System.Reflection; + using System.Threading.Tasks; + + partial class OliveExtensions + { + /// + /// Gets the embedded resource name for a specified relative file path in the project. + /// If the resulting resource name does not exist in this assembly it will throw. + /// + /// The default namespace of your Visual Studio project. + /// For example MyRootFolder\MySubFolder\MyFile.cs (this is case sensitive). + public static string GetEmbeddedResourceName(this Assembly assembly, string rootNamespace, string fileRelativePath) + { + var result = rootNamespace + "." + fileRelativePath.Trim('/', '\\').Replace("/", "\\").Replace("\\", "."); + + using (var resource = assembly.GetManifestResourceStream(result)) + if (result == null) + throw new Exception($"The requested embedded resource '{result}' does not exist in the assembly '{assembly.FullName}'"); + + return result; + } + + /// The default namespace of your Visual Studio project. + /// For example MyRootFolder\MySubFolder\MyFile.cs (this is case sensitive). + public static Task ReadEmbeddedResource(this Assembly assembly, string rootNamespace, string fileRelativePath) + { + var resourceName = assembly.GetEmbeddedResourceName(rootNamespace, fileRelativePath); + return assembly.ReadEmbeddedResource(resourceName); + } + + public static async Task ReadEmbeddedResource(this Assembly assembly, string resourceName) + { + try + { + using (var stream = assembly.GetManifestResourceStream(resourceName)) + { + if (stream == null) + throw new Exception("There is no embedded resource named '" + resourceName + + "' in the assembly: " + assembly.FullName); + + return await stream.ReadAllBytes(); + } + } + catch (Exception ex) + { + Debug.WriteLine("Reading embedded resource failed: " + resourceName + Environment.NewLine + ex); + Debug.WriteLine("Available resources:\r\n" + assembly.GetManifestResourceNames().ToLinesString()); + throw; + } + } + + /// The default namespace of your Visual Studio project. + /// For example MyRootFolder\MySubFolder\MyFile.cs (this is case sensitive). + public static Task ReadEmbeddedTextFile(this Assembly assembly, string rootNamespace, string fileRelativePath) + { + var resourceName = assembly.GetEmbeddedResourceName(rootNamespace, fileRelativePath); + return assembly.ReadEmbeddedTextFile(resourceName); + } + + public static Task ReadEmbeddedTextFile(this Assembly assembly, string resourceName) + { + return assembly.ReadEmbeddedTextFile(resourceName, Encoding.UTF8); + } + + public static async Task ReadEmbeddedTextFile(this Assembly assembly, string resourceName, + Encoding encoding) + { + return encoding.GetString(await assembly.ReadEmbeddedResource(resourceName)); + } + } +} \ No newline at end of file diff --git a/Olive/-Extensions/Encryption.cs b/Olive/-Extensions/Encryption.cs new file mode 100644 index 000000000..c50af3df3 --- /dev/null +++ b/Olive/-Extensions/Encryption.cs @@ -0,0 +1,84 @@ +using System; +using System.Security.Cryptography; + +namespace Olive +{ + partial class OliveExtensions + { + public static void FromXmlString(this RSA rsa, string xmlString) + { + var parameters = new RSAParameters(); + + var xmlReader = xmlString.ToXmlReader(); + + Func getBytes = () => xmlReader.ReadContentAsString().ToBytes(); + + if (xmlReader.Name.Equals("RSAKeyValue")) + { + while (xmlReader.Read()) + { + switch (xmlReader.Name) + { + case "Modulus": + parameters.Modulus = getBytes(); + break; + + case "Exponent": + parameters.Exponent = getBytes(); + break; + + case "P": + parameters.P = getBytes(); + break; + + case "Q": + parameters.Q = getBytes(); + break; + + case "DP": + parameters.DP = getBytes(); + break; + + case "DQ": + parameters.DQ = getBytes(); + break; + + case "InverseQ": + parameters.InverseQ = getBytes(); + break; + + case "D": + parameters.D = getBytes(); + break; + + default: + throw new ArgumentException("Invalid XML RSA key."); + } + } + } + else + { + throw new ArgumentException("Invalid XML RSA key."); + } + + rsa.ImportParameters(parameters); + } + + public static string ToXmlString(this RSA rsa, bool includePrivateParameters) + { + var parameters = rsa.ExportParameters(includePrivateParameters); + + return + "" + + $"{parameters.Modulus.ToBase64String()}" + + $"{parameters.Exponent.ToBase64String()}" + + $"

    {parameters.P.ToBase64String()}

    " + + $"{parameters.Q.ToBase64String()}" + + $"{parameters.DP.ToBase64String()}" + + $"{parameters.DQ.ToBase64String()}" + + $"{parameters.InverseQ.ToBase64String()}" + + $"{parameters.D.ToBase64String()}" + + "
    "; + } + } +} diff --git a/Olive/-Extensions/Exception.cs b/Olive/-Extensions/Exception.cs new file mode 100644 index 000000000..7aece914e --- /dev/null +++ b/Olive/-Extensions/Exception.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Olive +{ + partial class OliveExtensions + { + public static string ToFullMessage(this Exception ex) + { + return ToFullMessage(ex, additionalMessage: null, includeStackTrace: false, includeSource: false, includeData: false); + } + + /// + /// Returns a more complete text dump of this exception, than just its text. + /// + public static string ToFullMessage(this Exception error, string additionalMessage, bool includeStackTrace, bool includeSource, bool includeData) + { + if (error == null) + throw new NullReferenceException("This exception object is null"); + var resultBuilder = new StringBuilder(); + resultBuilder.AppendLineIf(additionalMessage, additionalMessage.HasValue()); + var err = error; + while (err != null) + { + resultBuilder.AppendLine(err.Message); + if (includeData && err.Data != null && err.Data.Count > 0) + { + resultBuilder.AppendLine("\r\nException Data:\r\n{"); + foreach (var i in err.Data) + resultBuilder.AppendLine(ToLogText(i).WithPrefix(" ")); + + resultBuilder.AppendLine("}"); + } + + if (err is ReflectionTypeLoadException) + { + foreach (var loaderEx in (err as ReflectionTypeLoadException).LoaderExceptions) + resultBuilder.AppendLine("Type load exception: " + loaderEx.ToFullMessage()); + } + + err = err.InnerException; + if (err != null) + { + resultBuilder.AppendLine(); + if (includeStackTrace) + resultBuilder.AppendLine("###############################################"); + resultBuilder.Append("Base issue: "); + } + } + + if (includeStackTrace && error.StackTrace.HasValue()) + { + var stackLines = error.StackTrace.Or("").Trim().ToLines(); + stackLines = stackLines.Except(l => l.Trim().StartsWith("at System.Data.")).ToArray(); + resultBuilder.AppendLine(stackLines.ToString("\r\n\r\n").WithPrefix("\r\n--------------------------------------\r\nSTACK TRACE:\r\n\r\n")); + } + + return resultBuilder.ToString(); + } + + /// + /// It returns ToString for all objects except DictionaryEntries. + /// + public static string ToLogText(object item) + { + try + { + if (item is DictionaryEntry) + return ((DictionaryEntry)item).Get(x => x.Key + ": " + x.Value); + return item.ToString(); + } + catch + { + // No logging is needed + return "?"; + } + } + + /// + /// Creates a log-string from the Exception. + /// The result includes the stacktrace, innerexception et cetera, separated by . + /// + /// The exception to create the string from. + /// Additional message to place at the top of the string, maybe be empty or null. + public static string ToLogString(this Exception ex, string additionalMessage) + { + var r = new StringBuilder(); + r.AppendLine(ex.ToFullMessage(additionalMessage, includeStackTrace: true, includeSource: true, includeData: true)); + return r.ToString(); + } + + public static string ToLogString(this Exception ex) => ToLogString(ex, null); + + /// + /// Adds a piece of data to this exception. + /// + public static Exception AddData(this Exception exception, string key, object value) + { + if (value != null) + { + try + { + exception.Data.Add(key, value); + } + catch + { + // Not serializable + try + { + exception.Data.Add(key, value.ToString()); + } + catch + { + // No logging is needed + } + } + } + + return exception; + } + } +} \ No newline at end of file diff --git a/Olive/-Extensions/Expression.cs b/Olive/-Extensions/Expression.cs new file mode 100644 index 000000000..0e2b50528 --- /dev/null +++ b/Olive/-Extensions/Expression.cs @@ -0,0 +1,61 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; + +namespace Olive +{ + partial class OliveExtensions + { + /// + /// Determines whether this property info is the specified property (in lambda expression). + /// + public static bool Is(this PropertyInfo property, Expression> expression) + { + if (!typeof(T).IsA(property.DeclaringType)) return false; + return expression.GetPropertyPath() == property.Name; + } + + /// + /// Gets the property name for a specified expression. + /// + public static MemberInfo GetMember(this Expression> memberExpression) + { + var asMemberExpression = memberExpression.Body as MemberExpression; + + if (asMemberExpression == null) + { + // Maybe Unary: + asMemberExpression = (memberExpression.Body as UnaryExpression)?.Operand as MemberExpression; + } + + if (asMemberExpression == null) throw new Exception("Invalid expression"); + + return asMemberExpression.Member; + } + + public static PropertyInfo GetProperty(this Expression> property) => + property.GetMember() as PropertyInfo; + + /// + /// For example if the expression is (x => x.A.B) it will return A.B. + /// + public static string GetPropertyPath(this Expression expression) + { + if (expression is MemberExpression m) + { + var result = m.Member.Name; + + if (m.Expression.ToString().Contains(".")) + result = m.Expression.GetPropertyPath() + "." + result; + + return result; + } + + if (expression is LambdaExpression l) return l.Body.GetPropertyPath(); + + if (expression is UnaryExpression u) return u.Operand.GetPropertyPath(); + + throw new Exception("Failed to get the property name from this expression: " + expression); + } + } +} \ No newline at end of file diff --git a/Olive/-Extensions/FileInfo.cs b/Olive/-Extensions/FileInfo.cs new file mode 100644 index 000000000..db8925026 --- /dev/null +++ b/Olive/-Extensions/FileInfo.cs @@ -0,0 +1,380 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Olive +{ + partial class OliveExtensions + { + static readonly Encoding DefaultEncoding = CodePagesEncodingProvider.Instance.GetEncoding(1252); + + /// + /// Gets the entire content of this file. + /// + public static Task ReadAllBytes(this FileInfo file) + { + return TryHard(file, async () => await File.ReadAllBytesAsync(file.FullName), "The system cannot read the file: {0}"); + } + + /// + /// Gets the entire content of this file. + /// + public static async Task ReadAllText(this FileInfo file) => await ReadAllText(file, DefaultEncoding); + + public static string NameWithoutExtension(this FileInfo file) => Path.GetFileNameWithoutExtension(file.FullName); + + /// + /// Gets the entire content of this file. + /// + public static async Task ReadAllText(this FileInfo file, Encoding encoding) + { + Func> readFile = async () => + { + using (var stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + using (var reader = new StreamReader(stream, encoding)) + return await reader.ReadToEndAsync(); + } + }; + + return await TryHard(file, readFile, "The system cannot read the file: {0}"); + } + + /// + /// Will try to delete a specified directory by first deleting its sub-folders and files. + /// + /// If set to true, then it will try multiple times, in case the file is temporarily locked. + public static async Task Delete(this FileInfo file, bool harshly) + { + if (file == null) + throw new ArgumentNullException(nameof(file)); + + if (!file.Exists()) return; + + if (!harshly) + { + await Task.Factory.StartNew(file.Delete); + return; + } + + await TryHard(file, async () => await Task.Factory.StartNew(file.Delete), "The system cannot delete the file, even after several attempts. Path: {0}"); + } + + /// + /// Saves the specified content on this file. + /// + public static async Task WriteAllBytes(this FileInfo file, byte[] content) + { + if (!file.Directory.Exists()) + file.Directory.Create(); + + await TryHard(file, async () => await File.WriteAllBytesAsync(file.FullName, content), "The system cannot write the specified content on the file: {0}"); + } + + /// + /// Saves the specified content on this file using the Western European Windows Encoding 1252. + /// + public static async Task WriteAllText(this FileInfo file, string content) => await WriteAllText(file, content, DefaultEncoding); + + /// + /// Saves the specified content on this file. + /// Note: For backward compatibility, for UTF-8 encoding, it will always add the BOM signature. + /// + public static async Task WriteAllText(this FileInfo file, string content, Encoding encoding) + { + if (encoding == null) encoding = DefaultEncoding; + + file.Directory.EnsureExists(); + + if (encoding is UTF8Encoding) encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true); + + await File.WriteAllTextAsync(file.FullName, content, encoding); + } + + /// + /// Saves the specified content to the end of this file. + /// + public static async Task AppendAllText(this FileInfo file, string content) => await AppendAllText(file, content, DefaultEncoding); + + /// + /// Saves the specified content to the end of this file. + /// + public static async Task AppendLine(this FileInfo file, string content = null) => + await AppendAllText(file, content + Environment.NewLine, DefaultEncoding); + + /// + /// Saves the specified content to the end of this file. + /// + public static async Task AppendAllText(this FileInfo file, string content, Encoding encoding) + { + if (encoding == null) encoding = DefaultEncoding; + + file.Directory.EnsureExists(); + + await File.AppendAllTextAsync(file.FullName, content, encoding); + } + + /// + /// Copies this file onto the specified desination path. + /// + public static async Task CopyTo(this FileInfo file, FileInfo destinationPath, bool overwrite = true) + { + if (!overwrite && destinationPath.Exists()) return; + + var content = await file.ReadAllBytes(); + await destinationPath.WriteAllBytes(content); + } + + /// + /// Writes the specified content on this file, only when this file does not already have the same content. + /// + public static async Task WriteWhenDifferent(this FileInfo file, string newContent, Encoding encoding) + { + if (file.Exists()) + { + var oldContent = await file.ReadAllText(); + if (newContent == oldContent) + return false; + } + + await file.WriteAllText(newContent, encoding); + return true; + } + + /// + /// Determines whether or not this directory exists. + /// Note: The standard Exists property has a caching bug, so use this for accurate result. + /// + public static bool Exists(this DirectoryInfo folder) + { + if (folder == null) return false; + return Directory.Exists(folder.FullName); + } + + /// + /// Determines whether or not this file exists. + /// Note: The standard Exists property has a caching bug, so use this for accurate result. + /// + public static bool Exists(this FileInfo file) + { + if (file == null) return false; + return File.Exists(file.FullName); + } + + /// + /// Compresses this data into Gzip. + /// + public static async Task GZip(this byte[] data) + { + using (var outFile = new MemoryStream()) + { + using (var inFile = new MemoryStream(data)) + using (var Compress = new GZipStream(outFile, CompressionMode.Compress)) + await inFile.CopyToAsync(Compress); + + return outFile.ToArray(); + } + } + + /// + /// Compresses this string into Gzip. By default it will use UTF8 encoding. + /// + public static async Task GZip(this string data) => await GZip(data, Encoding.UTF8); + + /// + /// Compresses this string into Gzip. + /// + public static async Task GZip(this string data, Encoding encoding) => await encoding.GetBytes(data).GZip(); + + /// + /// Gets the total size of all files in this directory. + /// + public static long GetSize(this DirectoryInfo folder, bool includeSubDirectories = true) => + folder.GetFiles(includeSubDirectories).Sum(x => x.AsFile().Length); + + /// + /// Gets the size of this folder in human readable text. + /// + public static string GetSizeText(this DirectoryInfo folder, bool includeSubDirectories = true, int round = 1) => + folder.GetSize(includeSubDirectories).ToFileSizeString(round); + + /// + /// Gets the size of this file in human readable text. + /// + public static string GetSizeText(this FileInfo file, int round = 1) => file.Length.ToFileSizeString(round); + + /// + /// Detects the characters which are not acceptable in File System and replaces them with a hyphen. + /// + /// The character with which to replace invalid characters in the name. + public static string ToSafeFileName(this string name, char replacement = '-') + { + if (name.IsEmpty()) return string.Empty; + + var controlCharacters = name.Where(c => char.IsControl(c)); + + var invalidChars = new[] { '<', '>', ':', '"', '/', '\\', '|', '?', '*' }.Concat(controlCharacters); + + foreach (var c in invalidChars) + name = name.Replace(c, replacement); + + if (replacement.ToString().HasValue()) + name = name.KeepReplacing(replacement.ToString() + replacement, replacement.ToString()); + + return name.Summarize(255).TrimEnd("..."); + } + + /// + /// Executes this EXE file and returns the standard output. + /// + public static string Execute(this FileInfo exeFile, string args, bool waitForExit = true) => + Execute(exeFile, args, waitForExit, null); + + /// + /// Executes this EXE file and returns the standard output. + /// + public static string Execute(this FileInfo exeFile, string args, bool waitForExit, Action configuration) + { + var output = new StringBuilder(); + + var process = new Process + { + EnableRaisingEvents = true, + + StartInfo = new ProcessStartInfo + { + FileName = exeFile.FullName, + Arguments = args, + WorkingDirectory = exeFile.Directory.FullName, + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + StandardOutputEncoding = Encoding.UTF8, + StandardErrorEncoding = Encoding.UTF8 + } + }; + + configuration?.Invoke(process); + + process.ErrorDataReceived += (sender, e) => { if (e.Data.HasValue()) { output.AppendLine(e.Data); } }; + process.OutputDataReceived += (sender, e) => + { + if (e.Data != null) + { + output.AppendLine(e.Data); + } + }; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + if (waitForExit) + { + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new Exception($"Error running '{exeFile.FullName}':{output}"); + } + } + + return output.ToString(); + } + + /// + /// Gets the mime type based on the file extension. + /// + public static string GetMimeType(this FileInfo file) + { + switch (file.Extension.OrEmpty().TrimStart(".")) + { + case "doc": case "docx": return "application/msword"; + case "pdf": return "application/pdf"; + case "ppt": return "application/powerpoint"; + case "rtf": return "application/rtf"; + case "gz": return "application/x-gzip"; + case "zip": return "application/zip"; + case "mpga": case "mp2": return "audio/mpeg"; + case "ram": return "audio/x-pn-realaudio"; + case "ra": return "audio/x-realaudio"; + case "wav": return "audio/x-wav"; + case "gif": return "image/gif"; + case "jpeg": case "jpg": case "jpe": return "image/jpeg"; + case "png": return "image/png"; + case "tiff": case "tif": return "image/tiff"; + case "html": case "htm": return "text/html"; + case "txt": return "text/plain"; + case "mpeg": case "mpg": case "mpe": return "video/mpeg"; + case "mov": case "qt": return "video/quicktime"; + case "avi": return "video/avi"; + case "mid": return "audio/mid"; + case "midi": return "application/x-midi"; + case "divx": return "video/divx"; + case "webm": return "video/webm"; + case "wma": return "audio/x-ms-wma"; + case "mp3": return "audio/mp3"; + case "ogg": return "audio/ogg"; + case "rma": return "audio/rma"; + case "mp4": return "video/mp4"; + case "wmv": return "video/x-ms-wmv"; + case "f4v": return "video/x-f4v"; + case "ogv": return "video/ogg"; + case "3gp": return "video/3gpp"; + default: return "application/octet-stream"; + } + } + + /// + /// Gets the files in this folder. If this folder is null or non-existent it will return an empty array. + /// + public static IEnumerable GetFilesOrEmpty(this DirectoryInfo folder, string searchPattern) + { + if (folder == null || !folder.Exists()) + return Enumerable.Empty(); + + return folder.GetFiles(searchPattern); + } + + // public static async Task ReadAllTextAsync(this FileInfo file) + // { + // using (var reader = File.OpenText(file.FullName)) + // { + // return await reader.ReadToEndAsync(); + // } + // } + + // public static async Task ReadAllBytesAsync(this FileInfo file) + // { + // using (var reader = File.OpenRead(file.FullName)) + // { + // var result = new byte[file.Length]; + // await reader.ReadAsync(result, 0, result.Length); + // return result; + // } + // } + + // /// By default it will be UTF-8 + // public static async Task WriteAllTextAsync(this FileInfo file, string content, Encoding encoding = null) + // { + // if (encoding == null) + // encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true); + + // await file.WriteAllBytesAsync(encoding.GetBytes(content)); + // } + + // public static async Task WriteAllBytesAsync(this FileInfo file, byte[] data) + // { + // using (var writer = File.Create(file.FullName)) + // { + // await writer.WriteAsync(data, 0, data.Length); + // } + // } + } +} \ No newline at end of file diff --git a/Olive/-Extensions/Integer.cs b/Olive/-Extensions/Integer.cs new file mode 100644 index 000000000..ce677bb73 --- /dev/null +++ b/Olive/-Extensions/Integer.cs @@ -0,0 +1,205 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Olive +{ + partial class OliveExtensions + { + const int ONE_MILLION = 1000000, TWENTY = 20; + + /// Static mapping array, used by ToWordString for Units. + static readonly string[] NumberWordsUnits = new[] + { + "zero", "one", "two", "three", "four", "five", "six", + "seven", "eight", "nine", "ten", + "eleven", "twelve", "thirteen", "fourteen", "fifteen", + "sixteen", "seventeen", + "eighteen", "nineteen" + }; + + /// Static mapping array, used by ToWordString for Tens. + static readonly string[] NumberWordsTens = new[] + { + "zero", "ten", "twenty", "thirty", "forty", "fifty", + "sixty", "seventy", "eighty", + "ninety" + }; + + /// + /// Rounds up to nearest value with the intervals specified. + /// + public static int RoundUpToNearest(this int value, int roundIntervals) + { + var difference = roundIntervals - (value % roundIntervals); + return value + difference; + } + + /// + /// Rounds down to nearest value with the intervals specified. + /// + public static int RoundDownToNearest(this int value, int roundIntervals) => value - (value % roundIntervals); + + /// + /// Converts this number to a short textual representation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Base32Integer ToBase32(this int value) => new Base32Integer(value); + + public static int CompareTo(this int? @this, int? another) + { + if (@this == another) return 0; + if (another == null) return @this < 0 ? -1 : 1; + if (@this == null) return another < 0 ? 1 : -1; + return @this.Value.CompareTo(another.Value); + } + + public static string ToString(this int? value, string format) => ($"{{0:{format}}}").FormatWith(value); + + /// + /// To the word string. + /// + /// + /// Some awesome code from http://stackoverflow.com/questions/2729752/converting-numbers-in-to-words-c-sharp + /// + public static string ToWordString(this int number) + { + if (number == 0) return "zero"; + + if (number < 0) + return "minus " + ToWordString(Math.Abs(number)); + + string words = ""; + + if ((number / ONE_MILLION) > 0) + { + words += ToWordString(number / ONE_MILLION) + " million "; + number %= ONE_MILLION; + } + + if ((number / 1000) > 0) + { + words += ToWordString(number / 1000) + " thousand "; + number %= 1000; + } + + if ((number / 100) > 0) + { + words += ToWordString(number / 100) + " hundred "; + number %= 100; + } + + if (number > 0) + { + if (words != "") + words += "and "; + + if (number < TWENTY) + words += NumberWordsUnits[number]; + else + { + words += NumberWordsTens[number / 10]; + if ((number % 10) > 0) + words += "-" + NumberWordsUnits[number % 10]; + } + } + + return words; + } + + /// + /// Emits a user readable file size (including units). + /// + public static string ToFileSizeString(this long fileSize, string units, int round) + { + if ("MB".Equals(units, StringComparison.OrdinalIgnoreCase)) + return string.Format("{0:0.0} MB", Math.Round((double)fileSize / 0x100000, round)); + + var suffix = new[] { "B", "KB", "MB", "GB", "TB" }; + long index = 0; + + while (fileSize > 0x400 && index < suffix.Length) + { + fileSize = fileSize / 0x400; + index++; + } + + return string.Concat(fileSize, " ", suffix[index]); + } + + /// + /// Gets the size text for the given number of bytes. E.g. 4.5MB or 11KB. + /// + public static string ToFileSizeString(this int size, int round = 1) => ToFileSizeString((long)size, round); + + /// + /// Gets the size text for the given number of bytes. + /// + public static string ToFileSizeString(this long size, int round = 1) + { + var scale = new[] { "B", "KB", "MB", "GB", "TB" }; + if (size == 0) return "0" + scale[0]; + + var sign = Math.Sign(size); + size = Math.Abs(size); + + var place = Convert.ToInt32(Math.Floor(Math.Log(size, 1024))); + var num = Math.Round(size / Math.Pow(1024, place), round); + + return (sign * num) + scale[place]; + // return (Math.Sign(size) * num).ToString() + scale[place]; + } + + /// + /// Emits a user readable file size (including units). + /// + public static string ToFileSizeString(this int fileSize, string units, int round) => + ToFileSizeString((long)fileSize, units, round); + + /// + /// Return this value as a percentages the of the given total. + /// + /// Multiply this by 100. + /// Rounding decimals to. + public static double AsPercentageOf(this int value, int total, bool multiplyBy100 = true, int? roundTo = 0) => + AsPercentageOf((double)value, total, multiplyBy100, roundTo); + + /// + /// E.g. converts 1 to 1st. Or converts 13 to 13th. + /// + public static string ToOrdinal(this int number) + { + switch (number % 100) + { + case 11: + case 12: + case 13: + return number + "th"; + default: + // Other numbers are fine. + break; + } + + switch (number % 10) + { + case 1: + return number + "st"; + case 2: + return number + "nd"; + case 3: + return number + "rd"; + default: + return number + "th"; + } + } + + /// + /// Concerts this integer value to GUID. + /// + public static Guid ToGuid(this int value) + { + var bytes = new byte[16]; + BitConverter.GetBytes(value).CopyTo(bytes, 0); + return new Guid(bytes); + } + } +} \ No newline at end of file diff --git a/Olive/-Extensions/Linq.cs b/Olive/-Extensions/Linq.cs new file mode 100644 index 000000000..665963b83 --- /dev/null +++ b/Olive/-Extensions/Linq.cs @@ -0,0 +1,1288 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Olive +{ +#pragma warning disable GCop112 // This class is too large. Break its responsibilities down into more classes. + partial class OliveExtensions +#pragma warning restore GCop112 // This class is too large. Break its responsibilities down into more classes. + { + static Random RandomProvider = new Random(LocalTime.Now.TimeOfDay.Milliseconds); + + public static string ToString(this IEnumerable list, string seperator) + { + if (list == null) return "{NULL}"; + return ToString(list.Cast(), seperator); + } + + public static string ToFormatString(this IEnumerable list, string format, string seperator, string lastSeperator) => + list.Select(i => format.FormatWith(i)).ToString(seperator, lastSeperator); + + public static bool LacksKey(this IDictionary dictionary, TKey key) => + !dictionary.ContainsKey(key); + + public static bool Lacks(this IDictionary dictionary, object key) => !dictionary.Contains(key); + + public static bool Any(this IEnumerable list, Func predicate) => list.Any(predicate); + + public static string ToFormatString(this IEnumerable list, string format, string seperator) => + list.Select(i => format.FormatWith(i)).ToString(seperator); + + public static string ToString(this IEnumerable list, string seperator) => ToString(list, seperator, seperator); + + public static string ToString(this IEnumerable list, string seperator, string lastSeperator) + { + var result = new StringBuilder(); + + var items = list.Cast().ToArray(); + + for (int i = 0; i < items.Length; i++) + { + var item = items[i]; + + if (item == null) result.Append("{NULL}"); + else result.Append(item.ToString()); + + if (i < items.Length - 2) + result.Append(seperator); + + if (i == items.Length - 2) + result.Append(lastSeperator); + } + + return result.ToString(); + } + + public static int IndexOf(this IEnumerable list, T element) + { + if (list == null) + throw new NullReferenceException("No collection is given for the extension method IndexOf()."); + + if (list.Contains(element) == false) return -1; + + int result = 0; + foreach (var el in list) + { + if (el == null) + { + if (element == null) return result; + else continue; + } + + if (el.Equals(element)) return result; + result++; + } + + return -1; + } + + /// + /// Gets the index of the first item in this list which matches the specified criteria. + /// + public static int IndexOf(this IEnumerable list, Func criteria) + { + var result = 0; + + foreach (var item in list) + { + if (criteria(item)) return result; + + result++; + } + + return -1; + } + + public static void RemoveWhere(this IList list, Func selector) + { + lock (list) + { + var itemsToRemove = list.Where(selector).ToList(); + list.Remove(itemsToRemove); + } + } + + /// + /// Adds all items from a specified dictionary to this dictionary. + /// + public static void Add(this IDictionary dictionary, IDictionary items) + { + foreach (var item in items) + dictionary.Add(item.Key, item.Value); + } + + public static void RemoveWhere( + this IDictionary dictionary, + Func, bool> selector) + { + lock (dictionary) + { + var toRemove = dictionary.Where(selector).ToList(); + + foreach (var item in toRemove) dictionary.Remove(item.Key); + } + } + + public static void RemoveWhereKey(this IDictionary dictionary, Func selector) + { + lock (dictionary) + { + var toRemove = dictionary.Where(x => selector(x.Key)).ToList(); + + foreach (var item in toRemove) dictionary.Remove(item.Key); + } + } + + /// + /// Gets all items of this list except those meeting a specified criteria. + /// + /// Exclusion criteria + [EscapeGCop("It is the Except definition and so it cannot call itself")] + public static IEnumerable Except(this IEnumerable list, Func criteria) => list.Where(i => !criteria(i)); + + public static IEnumerable Except(this IEnumerable list, T item) => list.Except(new T[] { item }); + + [EscapeGCop("It is the Except definition and so it cannot call itself")] + public static IEnumerable Except(this IEnumerable list, params T[] items) + { + if (items == null) return list; + + return list.Where(x => !items.Contains(x)); + } + + public static IEnumerable Except(this IEnumerable list, List itemsToExclude) => list.Except(itemsToExclude.ToArray()); + + public static IEnumerable Except(this IEnumerable list, IEnumerable itemsToExclude) => + list.Except(itemsToExclude.ToArray()); + + public static IEnumerable Except(this IEnumerable list, IEnumerable itemsToExclude, bool alsoDistinct = false) + { + var result = list.Except(itemsToExclude.ToArray()); + + if (alsoDistinct) result = result.Distinct(); + + return result; + } + + public static IEnumerable Except(this IEnumerable list, IEnumerable itemsToExclude) => + list.Except(itemsToExclude.ToArray()); + + /// + /// Gets all Non-NULL items of this list. + /// + public static IEnumerable ExceptNull(this IEnumerable list) where T : class => list.Where(i => i != null); + + /// + /// Gets all Non-NULL items of this list. + /// + public static IEnumerable ExceptNull(this IEnumerable list) => + list.Where(i => i.HasValue).Select(x => x.Value); + + /// + /// Gets all Non-NULL items of this list. + /// + public static IEnumerable ExceptNull(this IEnumerable list) => + list.Where(i => i.HasValue).Select(x => x.Value); + + /// + /// Gets all Non-NULL items of this list. + /// + public static IEnumerable ExceptNull(this IEnumerable list) => + list.Where(i => i.HasValue).Select(x => x.Value); + + /// + /// Gets all Non-NULL items of this list. + /// + public static IEnumerable ExceptNull(this IEnumerable list) => + list.Where(i => i.HasValue).Select(x => x.Value); + + /// + /// Gets all Non-NULL items of this list. + /// + public static IEnumerable ExceptNull(this IEnumerable list) => list.Where(i => i.HasValue).Select(x => x.Value); + + /// + /// Gets all Non-NULL items of this list. + /// + public static IEnumerable ExceptNull(this IEnumerable list) => + list.Where(i => i.HasValue).Select(x => x.Value); + + /// + /// Gets all Non-NULL items of this list. + /// + public static IEnumerable ExceptNull(this IEnumerable list) => list.Where(i => i.HasValue).Select(x => x.Value); + + public static bool IsSingle(this IEnumerable list) => IsSingle(list, x => true); + + public static bool IsSingle(this IEnumerable list, Func criteria) + { + var visitedAny = false; + + foreach (var item in list.Where(criteria)) + { + if (visitedAny) return false; + visitedAny = true; + } + + return visitedAny; + } + + [EscapeGCop("This is for performance reasons.")] + public static bool IsSingle(this IEnumerable list, out T first) where T : class + { + first = null; + + foreach (var item in list) + if (first == null) first = item; + else return false; + + return first != null; + } + + [EscapeGCop("This is impossible to instantiate an interface so, we cannot use IEnumerable.")] + public static List Clone(this List list) + { + if (list == null) return list; + return new List(list); + } + + /// + /// Adds the specified list to the beginning of this list. + /// + public static IEnumerable Prepend(this IEnumerable list, IEnumerable prefix) => prefix.Concat(list); + + /// + /// Adds the specified item(s) to the beginning of this list. + /// + public static IEnumerable Prepend(this IEnumerable list, params T[] prefix) => prefix.Concat(list); + + /// + /// Performs an action for all items within the list. + /// + public static void Do(this IEnumerable list, ItemHandler action) + { + if (list == null) return; + + foreach (var item in list) + action?.Invoke(item); + } + + /// + /// Performs an action for all items within the list. + /// + public static async Task Do(this IEnumerable list, Func func) + { + if (list == null) return; + + foreach (var item in list) + await func?.Invoke(item); + } + + /// + /// Performs an action for all items within the list. + /// It will provide the index of the item in the list to the action handler as well. + /// + public static void Do(this IEnumerable list, Action action) + { + if (list == null) return; + + var index = 0; + + foreach (var item in list) + { + action?.Invoke(item, index); + index++; + } + } + + public delegate void ItemHandler(T arg); + + public static IEnumerable Concat(this IEnumerable list, T item) => list.Concat(new T[] { item }); + + public static void AddRange(this IList list, IEnumerable items) + { + foreach (T item in items) + list.Add(item); + } + + /// + /// Gets the minimum value of a specified expression in this list. If the list is empty, then the default value of the expression will be returned. + /// + public static R MinOrDefault(this IEnumerable list, Func expression) + { + if (list.None()) return default(R); + return list.Min(expression); + } + + /// + /// Gets the maximum value of a specified expression in this list. If the list is empty, then the default value of the expression will be returned. + /// + public static R MaxOrDefault(this IEnumerable list, Func expression) + { + if (list.None()) return default(R); + return list.Max(expression); + } + + /// + /// Gets the maximum value of the specified expression in this list. + /// If no items exist in the list then null will be returned. + /// + public static R? MaxOrNull(this IEnumerable list, Func expression) where R : struct + { + if (list.None()) return default(R?); + return list.Max(expression); + } + + /// + /// Gets the maximum value of the specified expression in this list. + /// If no items exist in the list then null will be returned. + /// + public static R? MaxOrNull(this IEnumerable list, Func expression) where R : struct => + list.MaxOrNull(item => (R?)expression(item)); + + /// + /// Gets the minimum value of the specified expression in this list. + /// If no items exist in the list then null will be returned. + /// + public static R? MinOrNull(this IEnumerable list, Func expression) where R : struct + { + if (list.None()) return default(R?); + return list.Min(expression); + } + + /// + /// Gets the minimum value of the specified expression in this list. + /// If no items exist in the list then null will be returned. + /// + public static R? MinOrNull(this IEnumerable list, Func expression) where R : struct => + list.MinOrNull(item => (R?)expression(item)); + + public static bool IsSubsetOf(this IEnumerable source, IEnumerable target) => target.ContainsAll(source); + + /// + /// Determines whether this list is equivalent to another specified list. Items in the list should be distinct for accurate result. + /// + public static bool IsEquivalentTo(this IEnumerable list, IEnumerable other) + { + if (list == null) list = new T[0]; + if (other == null) other = new T[0]; + + if (list.Count() != other.Count()) return false; + + foreach (var item in list) + if (!other.Contains(item)) return false; + return true; + } + + /// + /// Counts the number of items in this list matching the specified criteria. + /// + public static int Count(this IEnumerable list, Func criteria) => list.Count((x, i) => criteria(x, i)); + + /// + /// Picks an item from the list. + /// + public static T PickRandom(this IEnumerable list) + { + if (list.Any()) + { + var index = RandomProvider.Next(list.Count()); + return list.ElementAt(index); + } + else return default(T); + } + + public static IEnumerable PickRandom(this IEnumerable list, int number) + { + if (number < 1) throw new ArgumentException("number should be greater than 0."); + + var items = list as List ?? list.ToList(); + + if (number >= items.Count) number = items.Count; + + var randomIndices = RandomProvider.PickNumbers(number, 0, items.Count - 1); + + foreach (var index in randomIndices) + yield return items[index]; + } + + /// + /// Works as opposite of Contains(). + /// + public static bool Lacks(this IEnumerable list, T item) => !list.Contains(item); + + /// + /// Determines if this list lacks any item in the specified list. + /// + public static bool LacksAny(this IEnumerable list, IEnumerable items) => !list.ContainsAll(items); + + /// + /// Determines if this list lacks all items in the specified list. + /// + public static bool LacksAll(this IEnumerable list, IEnumerable items) => !list.ContainsAny(items.ToArray()); + + public static IEnumerable Randomize(this IEnumerable list) + { + if (list.None()) return new T[0]; + + var items = list.ToList(); + + return PickRandom(items, items.Count); + } + + /// + /// Returns a subset of the items in a given collection in a range including the items at lower and upper bounds. + /// + public static IEnumerable Take(this IEnumerable list, int lowerBound, int upperBound) + { + if (upperBound == 0) return Enumerable.Empty(); + if (lowerBound < 0) throw new ArgumentOutOfRangeException(nameof(lowerBound)); + if (upperBound < 0) throw new ArgumentOutOfRangeException(nameof(upperBound)); + + if (lowerBound > upperBound) + throw new ArgumentException("lower bound should be smaller than upper bound.", nameof(upperBound)); + + var result = new List(); + + var index = -1; + foreach (var item in list) + { + index++; + + if (index < lowerBound) continue; + if (index > upperBound) break; + result.Add(item); + } + + return result; + } + + public static IEnumerable Distinct(this IEnumerable list, Func selector) + { + var keys = new List(); + + foreach (var item in list) + { + var keyValue = selector(item); + + if (keys.Contains(keyValue)) continue; + + keys.Add(keyValue); + yield return item; + } + } + + /// + /// Determines of this list contains all items of another given list. + /// + public static bool ContainsAll(this IEnumerable list, IEnumerable items) => + items.All(i => list.Contains(i)); + + /// + /// Determines if this list contains any of the specified items. + /// + public static bool ContainsAny(this IEnumerable list, params T[] items) => list.Intersects(items); + + /// + /// Determines if none of the items in this list meet a given criteria. + /// + public static bool None(this IEnumerable list, Func criteria) => !list.Any(criteria); + + /// + /// Determines if this is null or an empty list. + /// + public static bool None(this IEnumerable list) + { + if (list == null) return true; + return !list.Any(); + } + + /// + /// Determines if this list intersects with another specified list. + /// + public static bool Intersects(this IEnumerable list, IEnumerable otherList) + { + var countList = (list as ICollection)?.Count; + var countOther = (otherList as ICollection)?.Count; + + if (countList == null || countOther == null || countOther < countList) + { + foreach (var item in otherList) + if (list.Contains(item)) + return true; + } + else + { + foreach (var item in list) + if (otherList.Contains(item)) + return true; + } + + return false; + } + + /// + /// Determines if this list intersects with another specified list. + /// + public static bool Intersects(this IEnumerable list, params T[] items) => list.Intersects((IEnumerable)items); + + /// + /// Selects the item with maximum of the specified value. + /// If this list is empty, NULL (or default of T) will be returned. + /// + public static T WithMax(this IEnumerable list, Func keySelector) + { + if (list.None()) return default(T); + return list.Aggregate((a, b) => Comparer.Default.Compare(keySelector(a), keySelector(b)) > 0 ? a : b); + } + + /// + /// Selects the item with minimum of the specified value. + /// + public static T WithMin(this IEnumerable list, Func keySelector) + { + if (list.None()) return default(T); + return list.Aggregate((a, b) => Comparer.Default.Compare(keySelector(a), keySelector(b)) < 0 ? a : b); + } + + /// + /// Gets the element after a specified item in this list. + /// If the specified element does not exist in this list, an ArgumentException will be thrown. + /// If the specified element is the last in the list, NULL will be returned. + /// + public static T GetElementAfter(this IEnumerable list, T item) where T : class + { + if (item == null) + throw new ArgumentNullException(nameof(item)); + + var index = list.IndexOf(item); + if (index == -1) + throw new ArgumentException("The specified item does not exist to this list."); + + if (index == list.Count() - 1) return null; + + return list.ElementAt(index + 1); + } + + /// + /// Gets the element before a specified item in this list. + /// If the specified element does not exist in this list, an ArgumentException will be thrown. + /// If the specified element is the first in the list, NULL will be returned. + /// + public static T GetElementBefore(this IEnumerable list, T item) where T : class + { + if (item == null) + throw new ArgumentNullException(nameof(item)); + + var index = list.IndexOf(item); + if (index == -1) + throw new ArgumentException("The specified item does not exist to this list."); + + if (index == 0) return null; + + return list.ElementAt(index - 1); + } + + public static void AddFormat(this IList list, string format, params object[] arguments) => + list.Add(string.Format(format, arguments)); + + public static void AddFormattedLine(this IList list, string format, params object[] arguments) => + list.Add(string.Format(format + Environment.NewLine, arguments)); + + public static void AddLine(this IList list, string text) => list.Add(text + Environment.NewLine); + + /// + /// Removes a list of items from this list. + /// + public static void Remove(this IList list, IEnumerable itemsToRemove) + { + if (itemsToRemove != null) + { + foreach (var item in itemsToRemove) + if (list.Contains(item)) + list.Remove(item); + } + } + + /// + /// Determines if all items in this collection are unique. + /// + public static bool AreItemsUnique(this IEnumerable collection) + { + if (collection.None()) return true; + + return collection.Distinct().Count() == collection.Count(); + } + + /// + /// Returns the union of this list with the specified other lists. + /// + + public static IEnumerable Union(this IEnumerable list, params IEnumerable[] otherLists) + { + var result = list; + + foreach (var other in otherLists) + result = Enumerable.Union(result, other); + + return result; + } + + /// + /// Returns the union of this list with the specified items. + /// + public static IEnumerable Union(this IEnumerable list, params T[] otherItems) => list.Union((IEnumerable)otherItems); + + public static IEnumerable Concat(this IEnumerable list, params IEnumerable[] otherLists) + { + var result = list; + + foreach (var other in otherLists) result = Enumerable.Concat(result, other); + + return result; + } + + /// + /// Gets the average of the specified expression on all items of this list. + /// If the list is empty, null will be returned. + /// + public static double? AverageOrDefault(this IEnumerable list, Func selector) + { + if (list.None()) return null; + else return list.Average(selector); + } + + /// + /// Gets the average of the specified expression on all items of this list. + /// If the list is empty, null will be returned. + /// + public static double? AverageOrDefault(this IEnumerable list, Func selector) + { + if (list.None()) return null; + else return list.Average(selector); + } + + /// + /// Gets the average of the specified expression on all items of this list. + /// If the list is empty, null will be returned. + /// + public static double? AverageOrDefault(this IEnumerable list, Func selector) + { + if (list.None()) return null; + else return list.Average(selector); + } + + /// + /// Gets the average of the specified expression on all items of this list. + /// If the list is empty, null will be returned. + /// + public static double? AverageOrDefault(this IEnumerable list, Func selector) + { + if (list.None()) return null; + else return list.Average(selector); + } + + /// + /// Gets the average of the specified expression on all items of this list. + /// If the list is empty, null will be returned. + /// + public static decimal? AverageOrDefault(this IEnumerable list, Func selector) + { + if (list.None()) return null; + else return list.Average(selector); + } + + /// + /// Gets the average of the specified expression on all items of this list. + /// If the list is empty, null will be returned. + /// + public static decimal? AverageOrDefault(this IEnumerable list, Func selector) + { + if (list.None()) return null; + else return list.Average(selector); + } + + /// + /// Trims all elements in this list and excludes all null and "empty string" elements from the list. + /// + public static IEnumerable Trim(this IEnumerable list) + { + if (list == null) return Enumerable.Empty(); + + return list.Except(v => string.IsNullOrWhiteSpace(v)).Select(v => v.Trim()).Where(v => v.HasValue()).ToArray(); + } + + /// + /// Determines whether this list of strings contains the specified string. + /// + public static bool Contains(this IEnumerable list, string instance, bool caseSensitive) + { + if (caseSensitive || instance.IsEmpty()) + return list.Contains(instance); + else return list.Any(i => i.HasValue() && i.Equals(instance, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Determines whether this list of strings contains the specified string. + /// + public static bool Lacks(this IEnumerable list, string instance, bool caseSensitive) => + !Contains(list, instance, caseSensitive); + + /// + /// Concats all elements in this list with Environment.NewLine. + /// + public static string ToLinesString(this IEnumerable list) => list.ToString(Environment.NewLine); + + /// + /// Gets the value with the specified key, or null. + /// + public static TValue TryGet(this IDictionary list, TKey key) + { + if (list.TryGetValue(key, out TValue result)) return result; + + return default(TValue); + } + + /// + /// Chops a list into same-size smaller lists. For example: + /// new int[] { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 }.Chop(5) + /// will return: { {1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15}, {16} } + /// + public static IEnumerable> Chop(this IEnumerable list, int chopSize) + { + if (chopSize == 0 || list.None()) + { + yield return list; + yield break; + } + + yield return list.Take(chopSize); + + if (list.Count() > chopSize) + { + var rest = list.Skip(chopSize); + + foreach (var item in Chop(rest, chopSize)) + yield return item; + } + } + + /// + /// Gets the keys of this dictionary. + /// + public static IEnumerable GetKeys(this IDictionary dictionary) => dictionary.Select(i => i.Key); + + /// + /// Returns the sum of a timespan selector on this list. + /// + public static TimeSpan Sum(this IEnumerable list, Func selector) + { + var result = TimeSpan.Zero; + foreach (var item in list) result += selector(item); + return result; + } + + /// + /// Returns the indices of all items which matche a specified criteria. + /// + public static IEnumerable AllIndicesOf(IEnumerable list, Func criteria) + { + int index = 0; + + foreach (var item in list) + { + if (criteria(item)) yield return index; + + index++; + } + } + + /// + /// Replaces the specified item in this list with the specified new item. + /// + public static void Replace(this IList list, T oldItem, T newItem) + { + list.Remove(oldItem); + list.Add(newItem); + } + + /// + /// Gets all values from this dictionary. + /// + public static IEnumerable GetAllValues(this IDictionary dictionary) + { + foreach (var item in dictionary) + yield return item.Value; + } + + /// + /// Gets all values from this dictionary. + /// + public static IEnumerable GetAllValues(this IEnumerable> dictionary) + { + foreach (var item in dictionary) + yield return item.Value; + } + + /// + /// Returns all elements of this list except those at the specified indices. + /// + public static IEnumerable ExceptAt(this IEnumerable list, params int[] indices) => + list.Where((item, index) => !indices.Contains(index)); + + /// + /// Returns all elements of this list except the last X items. + /// + public static IEnumerable ExceptLast(this IEnumerable list, int count = 1) + { + var last = list.Count(); + return list.ExceptAt(Enumerable.Range(last - count, count).ToArray()); + } + + /// + /// Returns all elements of this list except the first X items. + /// + public static IEnumerable ExceptFirst(this IEnumerable list, int count = 1) => + list.ExceptAt(Enumerable.Range(0, count).ToArray()); + + /// + /// Removes the nulls from this list. + /// + public static void RemoveNulls(this IList list) => list.RemoveWhere(i => i == null); + + /// + /// Tries to the remove an item with the specified key from this dictionary. + /// + public static K TryRemove(this System.Collections.Concurrent.ConcurrentDictionary list, T key) + { + if (list.TryRemove(key, out K result)) + return result; + else return default(K); + } + + /// + /// Tries to the remove an item with the specified key from this dictionary. + /// + public static K TryRemoveAt(this System.Collections.Concurrent.ConcurrentDictionary list, int index) + { + try + { + return list.TryRemove(list.Keys.ElementAt(index)); + } + catch + { + // No logging is needed + return default(K); + } + } + + /// + /// Determines whether this least contains at least the specified number of items. + /// This can be faster than calling "x.Count() >= N" for complex iterators. + /// + public static bool ContainsAtLeast(this System.Collections.IEnumerable list, int numberOfItems) + { + // Special case for List: + if (list is ICollection asList) return asList.Count >= numberOfItems; + + var itemsCount = 0; + var result = itemsCount == numberOfItems; + var enumerator = list.GetEnumerator(); + + while (enumerator.MoveNext()) + { + itemsCount++; + + if (itemsCount >= numberOfItems) return true; + } + + return result; + } + + /// + /// Converts this to a KeyValueCollection. + /// + public static NameValueCollection ToNameValueCollection(this IDictionary dictionary) + { + var result = new NameValueCollection(); + + foreach (var item in dictionary) + result.Add(item.Key.ToStringOrEmpty(), item.Value.ToStringOrEmpty()); + + return result; + } + + /// + /// Adds the properties of a specified [anonymous] object as items to this dictionary. + /// It ignores duplicate entries and null values. + /// + public static void AddFromProperties(this Dictionary dictionary, object data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + + foreach (var property in data.GetType().GetProperties()) + { + if (dictionary.ContainsKey(property.Name)) continue; + + var value = property.GetValue(data); + + if (value == null) continue; + + if (typeof(TValue) == typeof(string) && value.GetType() != typeof(string)) + value = value.ToStringOrEmpty(); + + if (!value.GetType().IsA(typeof(TValue))) + { + throw new Exception("The value on property '{0}' is of type '{1}' which cannot be casted as '{2}'." + .FormatWith(property.Name, value.GetType().FullName, typeof(TValue).FullName)); + } + + dictionary.Add(property.Name, (TValue)value); + } + } + + /// + /// Adds the specified key/value pair to this list. + /// + public static KeyValuePair Add(this IList> list, TKey key, TValue value) + { + var result = new KeyValuePair(key, value); + list.Add(result); + + return result; + } + + /// + /// Adds the specified items to this set. + /// + public static void AddRange(this HashSet set, IEnumerable items) + { + if (items == null) return; + + foreach (var item in items) + set.Add(item); + } + + /// + /// Dequeues all queued items in the right order. + /// + public static IEnumerable DequeueAll(this Queue @this) + { + while (@this.Count > 0) + yield return @this.Dequeue(); + } + + /// + /// Returns a HashSet of type T (use for performance in place of ToList()). + /// + public static HashSet ToHashSet(this IEnumerable collection) => new HashSet(collection); + + /// + /// Gets all indices of the specified item in this collection. + /// + public static IEnumerable AllIndicesOf(this IEnumerable all, T item) + { + var index = 0; + foreach (var i in all) + { + if (ReferenceEquals(item, null)) + { + if (ReferenceEquals(i, null)) yield return index; + } + else + { + if (item.Equals(i)) yield return index; + } + + index++; + } + } + + /// + /// Returns an empty collection if this collection is null. + /// + public static IEnumerable OrEmpty(this IEnumerable collection) => collection ?? Enumerable.Empty(); + + public static IEnumerable TakePage(this IEnumerable list, int? pageSize, int currentPage) + { + if (pageSize == null) return list; + + if (currentPage < 1) currentPage = 1; + + var startIndex = (pageSize.Value) * (currentPage - 1); + var endIndex = startIndex + pageSize.Value; + + if (currentPage > 1 && startIndex > list.Count()) + return TakePage(list, pageSize, 1); + + if (currentPage > 1) endIndex--; + + return list.Take(startIndex, endIndex); + } + + /// + /// Determines if the specified item exists in this list. + /// + public static bool Contains(this IEnumerable items, T? item) where T : struct + { + if (item == null) return false; + + return Enumerable.Contains(items, item.Value); + } + + /// + /// Determines if the specified item exists in this list. + /// + public static bool Lacks(this IEnumerable items, T? item) where T : struct => !items.Contains(item); + + /// + /// Determines if this item is in the specified list. + /// + public static bool IsAnyOf(this T? item, IEnumerable items) where T : struct + { + if (item == null) return false; + + return items.Contains(item.Value); + } + + /// + /// Specifies whether this list contains any of the specified values. + /// + public static bool ContainsAny(this IEnumerable list, params Guid?[] ids) => list.ContainsAny(ids.ExceptNull().ToArray()); + + /// + /// Finds the median of a list of integers + /// + public static int Median(this IEnumerable numbers) + { + numbers = numbers.ToList(); + + if (numbers.None()) + throw new ArgumentException("number list cannot be empty"); + + var ordered = numbers.OrderBy(i => i).ToList(); + + var middle = numbers.Count() / 2; + + if (numbers.Count() % 2 == 1) + return ordered.ElementAt(middle); + + // Return the average of the two middle numbers. + return (ordered.ElementAt(middle - 1) + ordered.ElementAt(middle)) / 2; + } + + /// + /// If this list is null or empty, then the specified alternative will be returned, otherwise this will be returned. + /// + public static IEnumerable Or(this IEnumerable list, IEnumerable valueIfEmpty) + { + if (list.None()) return valueIfEmpty; + else return list; + } + + public static Dictionary ToDictionary(this IEnumerable> items) => + items.ToDictionary(x => x.Key, x => x.Value); + + public static IEnumerable Except(this IEnumerable list, Type excludedType) + { + if (list == null) throw new NullReferenceException("No collection is given for the extension method Except()."); + + if (excludedType == null) throw new ArgumentNullException(nameof(excludedType)); + + var excludedTypeInfo = excludedType; + + if (excludedTypeInfo.IsClass) + { + foreach (var item in list) + { + if (item == null) yield return item; + + var type = item.GetType(); + + if (type == excludedType) continue; + else if (type.IsSubclassOf(excludedType)) continue; + else yield return item; + } + } + else if (excludedTypeInfo.IsInterface) + { + foreach (var item in list) + { + if (item == null) yield return item; + + var type = item.GetType(); + + if (type.Implements(excludedType)) continue; + else yield return item; + } + } + else throw new NotSupportedException("Except(System.Type) method does not recognize " + excludedType); + + // return list.Where(each => each == null || (each.GetType().IsAssignableFrom(excludedType) == false)); + } + + /// + /// Creates a list of the specified runtime type including all items of this collection. + /// + public static IEnumerable Cast(this IEnumerable list, Type type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + // Generic List is in mscorelib, just like System.String: + + var listType = typeof(string).Assembly.GetType("System.Collections.Generic.List`1").MakeGenericType(type); + var result = (IList)Activator.CreateInstance(listType); + + foreach (var item in list) + result.Add(item); + + return result; + } + + public static IEnumerable OrderBy(this IEnumerable list, string propertyName) + { + if (propertyName.IsEmpty()) + throw new ArgumentNullException(nameof(propertyName)); + + var property = typeof(TSource).GetProperty(propertyName); + if (property == null) + throw new ArgumentException("{0} is not a readable property of {1} type.".FormatWith(propertyName, typeof(TSource).FullName)); + + return list.OrderBy(new Func((new PropertyComparer(property)).ExtractValue)); + } + + /// + /// Sorts this list by the specified property name. + /// + public static IEnumerable OrderBy(this IEnumerable list, string propertyName) + { + if (propertyName.IsEmpty()) + throw new ArgumentNullException(nameof(propertyName)); + + if (propertyName.EndsWith(" DESC")) + return OrderByDescending(list, propertyName.TrimEnd(" DESC".Length)); + + Type itemType = null; + foreach (var item in list) + { + itemType = item.GetType(); + break; + } + + // Empty list: + if (itemType == null) return list; + + var property = itemType.GetProperty(propertyName); + if (property == null) throw new ArgumentException("Unusable property name specified:" + propertyName); + + var comparer = new PropertyComparer(property); + + var result = new ArrayList(); + + foreach (var item in list) + result.Add(item); + + result.Sort(comparer); + + return result; + } + + public static IEnumerable OrderByDescending(this IEnumerable list, string property) + { + if (string.IsNullOrEmpty(property)) throw new ArgumentNullException(nameof(property)); + + var result = new ArrayList(); + foreach (var item in list.OrderBy(property)) + result.Insert(0, item); + + return result; + } + + public static IEnumerable OrderByDescending(this IEnumerable list, string propertyName) + { + if (propertyName.IsEmpty()) throw new ArgumentNullException(nameof(propertyName)); + + var property = typeof(TSource).GetProperty(propertyName); + + if (property == null) + throw new ArgumentException($"{propertyName} is not a readable property of {typeof(TSource).FullName}."); + + return list.OrderByDescending(new Func((new PropertyComparer(property)).ExtractValue)); + } + + public static TValue GetOrDefault(this IDictionary dictionary, TKey key) + { + var keyType = typeof(TKey); + + if (keyType.IsValueType || keyType == typeof(string) || keyType == typeof(Type)) + { + if (dictionary.TryGetValue(key, out TValue result)) return result; + return default(TValue); + } + else + { + return dictionary.Keys.FirstOrDefault(k => k.Equals(key)).Get(k => dictionary[k]); + } + } + + public static T FirstOrDefault(this ICollection collection, Func selector) + { + foreach (var item in collection) + if (selector(item)) return item; + + return default(T); + } + + public static Task AwaitAll(this IEnumerable list, Func task) + { + var tasks = new List(); + + if (list != null) + foreach (var item in list) + tasks.Add(task?.Invoke(item) ?? Task.CompletedTask); + + return Task.WhenAll(tasks); + } + + public static async Task> AwaitAll(this IEnumerable list, Func> task) + { + var tasks = new List>(); + + if (list != null) + foreach (var item in list) + tasks.Add(task?.Invoke(item)); + + await Task.WhenAll(tasks); + + return tasks.Select(x => x.Result); + } + + public static async Task> AwaitAll(this IEnumerable> list) => await Task.WhenAll(list); + + public static async Task> ToList(this Task> list) => (await list).ToList(); + + public static async Task> Select( + this Task> list, + Func func) + { + var awaited = await list; + return awaited.Select(func); + } + + public static async Task> Select( + this Task> list, + Func> func) + { + var awaited = await list; + var resultTasks = awaited.Select(func); + return await Task.WhenAll(resultTasks); + } + } +} \ No newline at end of file diff --git a/Olive/-Extensions/Object.Get.cs b/Olive/-Extensions/Object.Get.cs new file mode 100644 index 000000000..fa2df4198 --- /dev/null +++ b/Olive/-Extensions/Object.Get.cs @@ -0,0 +1,261 @@ +using System; +using System.Threading.Tasks; + +namespace Olive +{ + partial class OliveExtensions + { + /// + /// Performs a specified action on this item if it is not null. If it is null, it simply ignores the action. + /// + public static void Perform(this T item, Action action) where T : class + { + if (item != null) action?.Invoke(item); + } + + /// + /// Performs a specified action on this item if it is not null. If it is null, it simply ignores the action. + /// + public static async Task Perform(this T item, Func func) where T : class + { + if (item != null) await func?.Invoke(item); + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static K Get(this T item, Func selector) + { + if (object.ReferenceEquals(item, null)) + return default(K); + else + { + try + { + return selector(item); + } + catch (NullReferenceException) + { + return default(K); + } + } + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static K? Get(this T item, Func selector) where K : struct + { + if (item == null) return null; + + try + { + return selector(item); + } + catch (NullReferenceException) + { + return default(K?); + } + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static K Get(TimeSpan? item, Func selector) + { + if (item == null) return default(K); + + try + { + return selector(item.Value); + } + catch (NullReferenceException) + { + return default(K); + } + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static Guid? Get(this T item, Func selector) where T : class + { + if (item == null) return null; + + try + { + return selector(item); + } + catch (NullReferenceException) + { + return null; + } + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static int? Get(this T item, Func selector) where T : class + { + if (item == null) return null; + + try + { + return selector(item); + } + catch (NullReferenceException) + { + return null; + } + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static double? Get(this T item, Func selector) where T : class + { + if (item == null) return null; + + try + { + return selector(item); + } + catch (NullReferenceException) + { + return null; + } + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static decimal? Get(this T item, Func selector) where T : class + { + if (item == null) return null; + + try + { + return selector(item); + } + catch (NullReferenceException) + { + return null; + } + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static bool? Get(this T item, Func selector) where T : class + { + if (item == null) return null; + + try + { + return selector(item); + } + catch (NullReferenceException) + { + return null; + } + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static string Get(this DateTime? item, Func selector) + { + if (item == null) return null; + + try + { + return selector?.Invoke(item); + } + catch (NullReferenceException) + { + return null; + } + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static byte? Get(this T item, Func selector) where T : class + { + if (item == null) return null; + + try + { + return selector(item); + } + catch (NullReferenceException) + { + return null; + } + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static DateTime? Get(this T item, Func selector) where T : class + { + if (item == null) return null; + + try + { + return selector(item); + } + catch (NullReferenceException) + { + return null; + } + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static DateTime? Get(this T item, Func selector) where T : class + { + if (item == null) return null; + + try + { + return selector(item); + } + catch (NullReferenceException) + { + return null; + } + } + + /// + /// Gets a specified member of this object. If this is null, null will be returned. Otherwise the specified expression will be returned. + /// + [System.Diagnostics.DebuggerStepThrough] + public static T Get(this DateTime? item, Func selector) where T : struct + { + if (item == null) return default(T); + + try + { + return selector(item); + } + catch (NullReferenceException) + { + return default(T); + } + } + } +} diff --git a/Olive/-Extensions/Random.cs b/Olive/-Extensions/Random.cs new file mode 100644 index 000000000..8960b7f67 --- /dev/null +++ b/Olive/-Extensions/Random.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; + +namespace Olive +{ + partial class OliveExtensions + { + const string CHAR_SET_READABLE = "ACEFGHJKLMNPQRTUVWXYZ0123456789"; + const string CHAR_SET_FULL = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + /// + /// Gets a random boolean value with the specified chance (0-100). + /// + public static bool NextBoolean(this Random random, double chance) + { + if (chance < 0 || chance > 100) throw new Exception("Chance should be between 0 and 100 percent."); + + return random.NextDouble() >= 1 - (chance / 100.0); + } + + /// + /// Gets a random boolean value. + /// + public static bool NextBoolean(this Random random) => NextBoolean(random, 50); + + /// + /// Generates and returns a Random alphanumeric string. + /// + /// Random instance. + /// Length of string to return + /// Pass true to miss-out letters that can be confused with numbers (BDIOS) + /// String instance containing random alphanumeric characters. + public static string NextAlphaNumericString(this Random rng, int length, bool omitConfusableCharacters = false) + { + if (length < 1) throw new ArgumentOutOfRangeException(nameof(length), "Length should be 1 or more."); + + var charSet = omitConfusableCharacters ? CHAR_SET_READABLE : CHAR_SET_FULL; + + var buffer = new char[length]; + var chLen = charSet.Length; + for (int i = 0; i < length; i++) + buffer[i] = charSet[rng.Next(chLen)]; + + return new string(buffer); + } + + /// + /// Returns [quantity] number of unique random integers within the given range. + /// + public static List PickNumbers(this Random randomProvider, int quantity, int minValue, int maxValue) + { + if (minValue > maxValue) throw new ArgumentException("Invalid min and Max value specified."); + + var possibleMaxQuantity = (maxValue - minValue) + 1; + + if (quantity > possibleMaxQuantity) + throw new ArgumentException($"There are not {quantity} unique numbers between {minValue} and {maxValue}."); + + var result = new List(); + + var quantityPicked = 0; + + while (quantityPicked < quantity) + { + var candidate = randomProvider.Next(minValue, maxValue + 1); + + if (result.Contains(candidate)) continue; + + result.Add(candidate); + quantityPicked++; + } + + return result; + } + } +} \ No newline at end of file diff --git a/Olive/-Extensions/Range.cs b/Olive/-Extensions/Range.cs new file mode 100644 index 000000000..c61365267 --- /dev/null +++ b/Olive/-Extensions/Range.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; + +namespace Olive +{ + partial class OliveExtensions + { + /// + /// Returns a formatted string based on this Range<DateTime> object and the given string format. + /// + public static string ToString(this Range @this, string format) + { + if (@this.From == DateTime.MinValue && @this.To == DateTime.MinValue) return string.Empty; + + if ("A".Equals(format, StringComparison.OrdinalIgnoreCase)) + { + if (@this.To != DateTime.MaxValue) return "{0:d}-{1:d}".FormatWith(@this.From, @this.To); + else return "{0:d}-...".FormatWith(@this.From); + } + + if ("F".Equals(format, StringComparison.OrdinalIgnoreCase)) + { + if (@this.To != DateTime.MaxValue) return "From {0:d} to {1:d}".FormatWith(@this.From, @this.To); + else return "From {0:d}".FormatWith(@this.From); + } + + if ("T".Equals(format, StringComparison.OrdinalIgnoreCase)) + { + if (@this.To != DateTime.MaxValue) return "{0:d} to {1:d}".FormatWith(@this.From, @this.To); + else return "{0:d}".FormatWith(@this.From); + } + + return @this.ToString(); + } + + /// + /// Gets all possible items in the range based on the specified intervals. + /// + public static IEnumerable GetIntervals(this Range range, double interval) + { + if (interval <= 0) throw new Exception("Interval should be a positive number."); + + for (var item = range.From; item <= range.To; item += interval) + yield return item; + } + + /// + /// Gets all possible items in the range based on the specified intervals. + /// + public static IEnumerable GetIntervals(this Range range, decimal interval) + { + if (interval <= 0) throw new Exception("Interval should be a positive number."); + + for (var item = range.From; item <= range.To; item += interval) + yield return item; + } + + /// + /// Gets all possible items in the range based on the specified intervals. + /// + public static IEnumerable GetIntervals(this Range range, int interval = 1) + { + if (interval <= 0) throw new Exception("Interval should be a positive number."); + + for (var item = range.From; item <= range.To; item += interval) + yield return item; + } + + /// + /// Merges adjecant items in this list if their gap is within the specified tolerance. + /// The result will be another list of ranges with potentially fewer (but larger) ranges. + /// Consider sorting the items before calling this method. + /// + public static IEnumerable> MergeAdjacents(this IEnumerable> items, TimeSpan tolerance) + { + Range last = null; + + foreach (var item in items) + { + if (last == null) + { + last = item; + continue; + } + + var difference = item.From.Subtract(last.To); + if (difference < TimeSpan.Zero) difference = -difference; + + if (difference > tolerance) + { + yield return last; + last = item; + } + else + { + last = new Range(last.From, item.To); + } + } + + if (last != null) yield return last; + } + } +} diff --git a/Olive/-Extensions/Reflection.cs b/Olive/-Extensions/Reflection.cs new file mode 100644 index 000000000..fb16ff081 --- /dev/null +++ b/Olive/-Extensions/Reflection.cs @@ -0,0 +1,440 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Olive +{ + partial class OliveExtensions + { + static ConcurrentDictionary, bool> TypeImplementsCache = new ConcurrentDictionary, bool>(); + + static SortedDictionary CacheData = new SortedDictionary(); + + static ConcurrentDictionary>> SubTypesCache = new ConcurrentDictionary>>(); + + static ConcurrentDictionary ProgrammingNameCache = new ConcurrentDictionary(); + + static ConcurrentDictionary AssemblyQualifiedNameCache = new ConcurrentDictionary(); + + public delegate T Method(); + + /// + /// Gets all parent types hierarchy for this type. + /// + public static IEnumerable GetParentTypes(this Type type) + { + var result = new List(); + + for (var @base = type.BaseType; @base != null; @base = @base.BaseType) + result.Add(@base); + + return result; + } + + /// + /// Determines whether this type inherits from a specified base type, either directly or indirectly. + /// + public static bool InhritsFrom(this Type type, Type baseType) + { + if (baseType == null) + throw new ArgumentNullException(nameof(baseType)); + + if (baseType.IsInterface) + return type.Implements(baseType); + + return type.GetParentTypes().Contains(baseType); + } + + public static bool Implements(this Type type) => Implements(type, typeof(T)); + + public static bool IsA(this Type type) => typeof(T).IsAssignableFrom(type); + + public static bool IsA(this Type thisType, Type type) => type.IsAssignableFrom(thisType); + + public static bool References(this Assembly assembly, Assembly anotherAssembly) + { + if (assembly == null) throw new NullReferenceException("assembly should not be null."); + if (anotherAssembly == null) throw new ArgumentNullException(nameof(anotherAssembly)); + + return assembly.GetReferencedAssemblies().Any(each => each.FullName.Equals(anotherAssembly.FullName)); + } + + public static string GetDisplayName(this Type input) + { + string displayName = input.Name; + + for (int i = displayName.Length - 1; i >= 0; i--) + { + if (displayName[i] == char.ToUpper(displayName[i])) + if (i > 0) + displayName = displayName.Insert(i, " "); + } + + return displayName; + } + + public static IEnumerable WithAllParents(this Type @this) + { + yield return @this; + + if (@this.BaseType != null) + foreach (var p in @this.BaseType.WithAllParents()) yield return p; + } + + /// + /// Retuns the name of this type in the same way that is used in C# programming. + /// + public static string GetCSharpName(this Type type, bool includeNamespaces = false) + { + if (type.GetGenericArguments().None()) return type.Name; + + return type.Name.TrimAfter("`", trimPhrase: true) + "<" + + type.GetGenericArguments().Select(t => t.GetCSharpName(includeNamespaces)).ToString(", ") + ">"; + } + + public static bool Implements(this Type type, Type interfaceType) + { + if (interfaceType == null) throw new ArgumentNullException(nameof(interfaceType)); + + var key = Tuple.Create(type, interfaceType); + + return TypeImplementsCache.GetOrAdd(key, t => + { + if (!interfaceType.IsInterface) + throw new ArgumentException($"The provided value for interfaceType, {interfaceType.FullName} is not an interface type."); + + if (t.Item1 == t.Item2) return true; + + var implementedInterface = t.Item1.GetInterface(t.Item2.FullName, ignoreCase: false); + + if (implementedInterface == null) return false; + else return implementedInterface.FullName == t.Item2.FullName; + }); + } + + /// + /// Gets the value of this property on the specified object. + /// + public static object GetValue(this PropertyInfo property, object @object) + { + try + { + return property.GetValue(@object, null); + } + catch (Exception ex) + { + throw new Exception($"Could not get the value of property '{property.DeclaringType.Name}.{property.Name}' " + + "on the specified instance: {ex.Message}", ex); + } + } + + /// + /// Set the value of this property on the specified object. + /// + public static void SetValue(this PropertyInfo property, object @object, object value) => property.SetValue(@object, value, null); + + /// + /// Adds the specified types pair to this type dictionary. + /// + public static void Add(this IDictionary typeDictionary) => typeDictionary.Add(typeof(T), typeof(K)); + + /// + /// Creates the instance of this type. + /// + public static object CreateInstance(this Type type, params object[] constructorParameters) => + Activator.CreateInstance(type, constructorParameters); + + /// + /// Determines whether it has a specified attribute applied to it. + /// + public static bool Defines(this MemberInfo member, bool inherit = true) where TAttribute : Attribute => + member.IsDefined(typeof(TAttribute), inherit); + + /// + /// Creates the instance of this type casted to the specified type. + /// + public static TCast CreateInstance(this Type type, params object[] constructorParameters) => + (TCast)Activator.CreateInstance(type, constructorParameters); + + public static T Cache(this MethodBase method, object[] arguments, Method methodBody) where T : class => + Cache(method, null, arguments, methodBody); + + /// + /// Determines if this type is a nullable of something. + /// + public static bool IsNullable(this Type type) + { + if (!type.IsGenericType) return false; + + if (type.GetGenericTypeDefinition() != typeof(Nullable<>)) return false; + + return true; + } + + public static T Cache(this MethodBase method, object instance, object[] arguments, Method methodBody) where T : class + { + var key = method.DeclaringType.GUID + ":" + method.Name; + if (instance != null) + key += instance.GetHashCode() + ":"; + + arguments?.Do(arg => key += arg.GetHashCode() + ":"); + + if (CacheData[key] == null) + { + var result = methodBody?.Invoke(); + CacheData[key] = result; + return result; + } + + return CacheData[key] as T; + } + + public static bool Is(this PropertyInfo property, string propertyName) + { + var type1 = property.DeclaringType; + var type2 = typeof(T); + + if (type1.IsA(type2) || type2.IsA(type1)) + return property.Name == propertyName; + else + return false; + } + + /// + /// Determines whether this type is static. + /// + public static bool IsStatic(this Type type) => type.IsAbstract && type.IsSealed; + + public static bool IsExtensionMethod(this MethodInfo method) => + method.GetCustomAttributes(inherit: false).Any(); + + // /// + // /// Gets all defined attributes of the specified type. + // /// + // public static TAttribute[] GetCustomAttributes1(this MemberInfo member, bool inherit = true) where TAttribute : Attribute + // { + // member.GetCustomAttributes() + + // var result = member.GetCustomAttributes(typeof(TAttribute), inherit); + // if (result == null) return new TAttribute[0]; + // else return result.Cast().ToArray(); + // } + + #region Sub-Types + + /// + /// Gets all types in this assembly that are directly inherited from a specified base type. + /// + public static IEnumerable GetSubTypes(this Assembly assembly, Type baseType) + { + var cache = SubTypesCache.GetOrAdd(assembly, a => new ConcurrentDictionary>()); + + return cache.GetOrAdd(baseType, bt => + { + try + { + return assembly.GetTypes().Where(t => t.BaseType == bt).ToArray(); + } + catch (ReflectionTypeLoadException ex) + { + throw new Exception("Could not load the types of the assembly '{0}'. Type-load exceptions: {1}".FormatWith(assembly.FullName, + ex.LoaderExceptions.Select(e => e.Message).Distinct().ToString(" | "))); + } + }); + } + + #endregion + + /// + /// Gets the full programming name of this type. Unlike the standard FullName property, it handles Generic types properly. + /// + public static string GetProgrammingName(this Type type) => + ProgrammingNameCache.GetOrAdd(type, x => GetProgrammingName(x, useGlobal: false)); + + /// + /// Gets the full programming name of this type. Unlike the standard FullName property, it handles Generic types properly. + /// + public static string GetProgrammingName(this Type type, bool useGlobal, bool useNamespace = true, bool useNamespaceForParams = true, bool useGlobalForParams = false) + { + if (type.GetGenericArguments().Any()) + { + return "global::".OnlyWhen(useGlobal && type.FullName != null) + + "{0}{1}<{2}>".FormatWith( + type.Namespace.OnlyWhen(useNamespace).WithSuffix("."), + type.Name.Remove(type.Name.IndexOf('`')), + type.GetGenericArguments().Select(t => t.GetProgrammingName(useGlobalForParams, useNamespaceForParams, useNamespaceForParams, useGlobalForParams)).ToString(", ")); + } + else + { + if (type.FullName == null) + { + // Generic parameter name: + return type.Name.TrimEnd("&"); + } + + return "global::".OnlyWhen(useGlobal) + type.Namespace.OnlyWhen(useNamespace).WithSuffix(".") + type.Name.Replace("+", ".").TrimEnd("&"); + } + } + + /// + /// Determines if this type is a generic class of the specified type. + /// + public static bool IsGenericOf(this Type type, Type genericType, params Type[] genericParameters) + { + if (!type.IsGenericType) return false; + + if (type.GetGenericTypeDefinition() != genericType) return false; + + var args = type.GetGenericArguments(); + + if (args.Length != genericParameters.Length) return false; + + for (var i = 0; i < args.Length; i++) + if (args[i] != genericParameters[i]) return false; + + return true; + } + + internal static bool IsAnyOf(this Type type, params Type[] types) + { + if (type == null) return types.Any(x => x == null); + + return types.Contains(type); + } + + public static string GetCachedAssemblyQualifiedName(this Type type) => + AssemblyQualifiedNameCache.GetOrAdd(type, x => x.AssemblyQualifiedName); + + public static MemberInfo GetPropertyOrField(this Type type, string name) => + type.GetProperty(name) ?? (MemberInfo)type.GetField(name); + + public static IEnumerable GetPropertiesAndFields(this Type type, BindingFlags flags) => + type.GetProperties(flags).Cast().Concat(type.GetFields(flags)); + + public static Type GetPropertyOrFieldType(this MemberInfo member) => + (member as PropertyInfo)?.PropertyType ?? (member as FieldInfo)?.FieldType; + + static IEnumerable GetReferencingAssemblies(Assembly anotherAssembly) => + AppDomain.CurrentDomain.GetAssemblies().Where(assebly => assebly.References(anotherAssembly)); + + public static IEnumerable SelectTypesByAttribute(this Assembly assembly, bool inherit) where T : Attribute => + assembly.GetExportedTypes().Where(t => t.IsDefined(typeof(T), inherit)); + + /// + /// Gets all types in the current appDomain which implement this interface. + /// + public static List FindImplementerClasses(this Type interfaceType) + { + if (!interfaceType.IsInterface) throw new InvalidOperationException(interfaceType.GetType().FullName + " is not an Interface."); + + var result = new List(); + + foreach (var assembly in GetReferencingAssemblies(interfaceType.Assembly)) + { + try + { + foreach (var type in assembly.GetTypes()) + { + if (type == interfaceType) continue; + if (!type.IsClass) continue; + + if (type.Implements(interfaceType)) + result.Add(type); + } + } + catch + { + // No logging is needed + // Can't load assembly + } + } + + return result; + } + + public static object GetObjectByPropertyPath(this Type type, object instance, string propertyPath) + { + if (propertyPath.Contains(".")) + { + var directProperty = type.GetProperty(propertyPath.TrimAfter(".")); + + if (directProperty == null) + throw new Exception(type.FullName + " does not have a property named '" + propertyPath.TrimAfter(".") + "'"); + + var associatedObject = directProperty.GetValue(instance); + if (associatedObject == null) return null; + + var remainingPath = propertyPath.TrimStart(directProperty.Name + "."); + return associatedObject.GetType().GetObjectByPropertyPath(associatedObject, remainingPath); + } + else + { + return type.GetProperty(propertyPath).GetValue(instance); + } + } + + /// + /// Creates a new thread and copies the current Culture and UI Culture. + /// + public static Thread CreateNew(this Thread thread, Action threadStart) => CreateNew(thread, threadStart, null); + + /// + /// Creates a new thread and copies the current Culture and UI Culture. + /// + public static Thread CreateNew(this Thread thread, Action threadStart, Action initializer) + { + var result = new Thread(new ThreadStart(threadStart)); + + initializer?.Invoke(result); + + return result; + } + + /// + /// Gets the default value for this type. It's equivalent to default(T). + /// + public static object GetDefaultValue(this Type type) + { + if (type.IsValueType) + return Activator.CreateInstance(type); + + return null; + } + + /// + /// If it specifies DisplayNameAttribute the value from that will be returned. + /// Otherwise it returns natural English literal text for the name of this member. + /// For example it coverts "ThisIsSomething" to "This is something". + /// + public static string GetDisplayName(this MemberInfo member) + { + var byAttribute = member.GetCustomAttribute()?.DisplayName; + return byAttribute.Or(() => member.Name.ToLiteralFromPascalCase()); + } + + /// + /// Determine whether this property is static. + /// + public static bool IsStatic(this PropertyInfo property) => (property.GetGetMethod() ?? property.GetSetMethod()).IsStatic; + + /// + /// It works similar to calling .Result property, but it forces a context switch to prevent deadlocks in UI and ASP.NET context. + /// + public static TResult AwaitResult(this Task task) => Task.Run(async () => await task).Result; + + public static TTArget GetTargetOrDefault(this WeakReference reference) + where TTArget : class + { + if (reference == null) return null; + + if (reference.TryGetTarget(out var result)) return result; + return null; + } + + internal static WeakReference GetWeakReference(this T item) where T : class => new WeakReference(item); + } +} diff --git a/Olive/-Extensions/String.Conversion.cs b/Olive/-Extensions/String.Conversion.cs new file mode 100644 index 000000000..eee8bd765 --- /dev/null +++ b/Olive/-Extensions/String.Conversion.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace Olive +{ + partial class OliveExtensions + { + public delegate bool TryParseProvider(string text, Type type, out object result); + + public static List TryParseProviders = new List(); + + /// + /// Determines whether this string can be converted to the specified type. + /// + public static bool Is(this string text) where T : struct => text.TryParseAs().HasValue; + + /// + /// Tries to parse this text to the specified type. + /// Returns null if parsing is not possible. + /// + public static T? TryParseAs(this string text) where T : struct + { + if (text.IsEmpty()) return default(T?); + + // Check common types first, for performance: + if (TryParseToCommonTypes(text, out T? result)) + return result; + + foreach (var parser in TryParseProviders) + if (parser(text, typeof(T), out object result2)) + return (T)result2; + + try { return (T)Convert.ChangeType(text, typeof(T)); } + catch + { + // No logging is needed + return null; + } + } + + [EscapeGCop("It is ok for trying methods to have out param.")] + static bool TryParseToCommonTypes(this string text, out T? result) where T : struct + { + result = null; + + if (typeof(T) == typeof(int)) + { + if (int.TryParse(text, out int tempResult)) result = (T)(object)tempResult; + + return true; + } + + if (typeof(T) == typeof(double)) + { + if (double.TryParse(text, out double tempResult)) result = (T)(object)tempResult; + + return true; + } + + if (typeof(T) == typeof(decimal)) + { + if (decimal.TryParse(text, out decimal tempResult)) result = (T)(object)tempResult; + + return true; + } + + if (typeof(T) == typeof(bool)) + { + if (bool.TryParse(text, out bool tempResult)) result = (T)(object)tempResult; + + return true; + } + + if (typeof(T) == typeof(DateTime)) + { + if (DateTime.TryParse(text, out DateTime tempResult)) result = (T)(object)tempResult; + + return true; + } + + if (typeof(T) == typeof(TimeSpan)) + { + if (TimeSpan.TryParse(text, out TimeSpan tempResult)) result = (T)(object)tempResult; + + return true; + } + + if (typeof(T) == typeof(Guid)) + { + if (Guid.TryParse(text, out Guid tempResult)) result = (T)(object)tempResult; + + return true; + } + + if (typeof(T).IsEnum) + { + if (Enum.TryParse(text, ignoreCase: true, result: out T tempResult)) result = (T)(object)tempResult; + + return true; + } + + if (typeof(T) == typeof(ShortGuid)) + { + try { result = (T)(object)ShortGuid.Parse(text); } + catch + { + // No logging is needed + return true; + } + } + + return false; + } + + /// + /// It converts this text to the specified data type. + /// It supports all primitive types, Enums, Guid, XElement, XDocument, Color, ... + /// + public static T To(this string text) => (T)To(text, typeof(T)); + + /// + /// Converts the value of this string object into the specified target type. + /// It supports all primitive types, Enums, Guid, XElement, XDocument, Color, ... + /// + public static object To(this string text, Type targetType) + { + try + { + return ChangeType(text, targetType); + } + catch (Exception ex) + { + throw new Exception($"Could not convert \"{text}\" to type { targetType.FullName}.", ex); + } + } + + static object ChangeType(string text, Type targetType) + { + if (targetType == typeof(string)) return text; + + if (text.IsEmpty()) + return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; + + // Check common types first, for performance: + if (TryParseToCommonTypes(text, targetType, out object result)) + return result; + + if (targetType.IsEnum) return Enum.Parse(targetType, text); + + if (targetType == typeof(XElement)) return XElement.Parse(text); + + if (targetType == typeof(XDocument)) return XDocument.Parse(text); + + if (targetType == typeof(ShortGuid)) return ShortGuid.Parse(text); + + foreach (var parser in TryParseProviders) + if (parser(text, targetType, out object result2)) + return result2; + + return Convert.ChangeType(text, targetType); + } + + [EscapeGCop("It is ok for trying methods to have out param.")] + static bool TryParseToCommonTypes(string text, Type targetType, out object result) + { + var actualTargetType = targetType; + + bool isNullable = targetType.IsNullable(); + + if (isNullable) + targetType = targetType.GetGenericArguments().Single(); + + result = null; + + try + { + if (targetType == typeof(int)) result = int.Parse(text); + + if (targetType == typeof(long)) result = long.Parse(text); + + if (targetType == typeof(double)) result = double.Parse(text); + + if (targetType == typeof(decimal)) result = decimal.Parse(text); + + if (targetType == typeof(bool)) result = bool.Parse(text); + + if (targetType == typeof(DateTime)) result = DateTime.Parse(text); + + if (targetType == typeof(Guid)) result = new Guid(text); + + if (targetType == typeof(TimeSpan)) + { + if (text.Is()) result = TimeSpan.FromTicks(text.To()); + else result = TimeSpan.Parse(text); + } + + return result != null; + } + catch + { + if (targetType.IsAnyOf(typeof(int), typeof(long))) + { + if (text.Contains(".") && text.TrimBefore(".", caseSensitive: true, trimPhrase: true).All(x => x == '0')) + result = text.TrimAfter(".").To(actualTargetType); + } + + if (isNullable) + return true; + else + throw; // Although it is a try method, it is ok to raise an exception. + } + } + } +} \ No newline at end of file diff --git a/Olive/-Extensions/String.cs b/Olive/-Extensions/String.cs new file mode 100644 index 000000000..f179b6fa9 --- /dev/null +++ b/Olive/-Extensions/String.cs @@ -0,0 +1,1384 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; + +namespace Olive +{ +#pragma warning disable GCop112 // This class is too large. Break its responsibilities down into more classes. + partial class OliveExtensions +#pragma warning restore GCop112 // This class is too large. Break its responsibilities down into more classes. + { + static byte UTF8SignatureFirstByte = 0xEF; + static byte UTF8SignatureSecondByte = 0xBB; + static byte UTF8SignatureThirdByte = 0xBF; + + static string[][] XMLEscapingChars = new string[][] + { + new string[]{ "&", "&" }, + new string[]{ "<", "<" }, + new string[]{ ">", ">" }, + new string[]{ "\"", """ }, + new string[]{ "'", "'" }, + }; + + /// + /// Array of unsafe characters that need to be replaced with their character code literals in a JavaScript string. + /// + static readonly char[] JsUnsafeCharacters = new[] { '\'', '\"' }; + + static ConcurrentDictionary LiteralFromPascalCaseCache = new ConcurrentDictionary(); + + /// + /// Removes the specified text from the start of this string instance. + /// + public static string TrimStart(this string text, string textToTrim) + { + if (text == null) text = string.Empty; + + if (textToTrim.IsEmpty() || text.IsEmpty()) return text; + + if (text.StartsWith(textToTrim)) return text.Substring(textToTrim.Length).TrimStart(textToTrim); + else return text; + } + + /// + /// Trims the end of this instance of string with the specified number of characters. + /// + public static string TrimEnd(this string text, int numberOfCharacters) + { + if (numberOfCharacters < 0) + throw new ArgumentException("numberOfCharacters must be greater than 0."); + + if (numberOfCharacters == 0) return text; + + if (text.IsEmpty() || text.Length <= numberOfCharacters) + return string.Empty; + + return text.Substring(0, text.Length - numberOfCharacters); + } + + /// + /// If this string object is null, it will return null. Otherwise it will trim the text and return it. + /// + public static string TrimOrNull(this string text) => text?.Trim(); + + /// + /// If this string object is null, it will return empty string. Otherwise it will trim the text and return it. + /// + public static string TrimOrEmpty(this string text) => text.TrimOrNull().OrEmpty(); + + public static bool IsNoneOf(this string text, params string[] items) => !text.IsAnyOf(items); + + /// + /// Returns a copy of this text converted to lower case. If it is null it will return empty string. + /// + public static string ToLowerOrEmpty(this string text) => text.OrEmpty().ToLower(); + + /// + /// Returns a copy of this text converted to upper case. If it is null it will return empty string. + /// + public static string ToUpperOrEmpty(this string text) => text.OrEmpty().ToUpper(); + + public static bool IsAnyOf(this string text, params string[] items) + { + if (text == null) return items.Any(x => x == null); + + return items.Contains(text); + } + + public static bool IsAnyOf(this string text, IEnumerable items) => IsAnyOf(text, items.ToArray()); + + public static string EnsureStart(this string text, string startText, bool caseSensitive = false) + { + if (startText.IsEmpty()) + throw new ArgumentNullException(nameof(startText)); + + if (text.IsEmpty()) return string.Empty; + + if (caseSensitive) + if (text.StartsWith(startText)) + return text; + + else + { + if (text.ToLower().StartsWith(startText.ToLower())) + return text; + } + + return startText + text; + } + + public static bool ContainsAll(this string text, string[] keywords, bool caseSensitive) + { + if (!caseSensitive) + { + text = (text ?? string.Empty).ToLower(); + + for (int i = 0; i < keywords.Length; i++) keywords[i] = keywords[i].ToLower(); + } + + foreach (var key in keywords) + if (!text.Contains(key)) return false; + + return true; + } + + /// + /// Determines whether this instance of string is null or empty. + /// + + public static bool IsEmpty(this string text) => string.IsNullOrEmpty(text); + + /// + /// Determines whether this instance of string is not null or empty. + /// + + public static bool HasValue(this string text) => !string.IsNullOrEmpty(text); + + /// + /// Will replace all line breaks with a BR tag and return the result as a raw html. + /// + public static string ToHtmlLines(this string text) => text.OrEmpty().ToLines().ToString("
    "); + + /// + /// Will join all items with a BR tag and return the result as a raw html. + /// + public static string ToHtmlLines(this IEnumerable items) => items.ToString("
    "); + + /// + /// Gets the same string if it is not null or empty. Otherwise it returns the specified default value. + /// + public static string Or(this string text, string defaultValue) + { + if (string.IsNullOrEmpty(text)) return defaultValue; + else return text; + } + + /// + /// Gets the same string if it is not null or empty. + /// Otherwise it invokes the specified default value provider and returns the result. + /// + public static string Or(this string text, Func defaultValueProvider) + { + if (string.IsNullOrEmpty(text)) return defaultValueProvider?.Invoke(); + else return text; + } + + /// + /// Gets the same string unless it is the same as the specified text. If they are the same, empty string will be returned. + /// + public static string Unless(this string text, string unwantedText) + { + if (text == unwantedText) return string.Empty; + else return text; + } + + /// + /// Summarizes the specified source. + /// + public static string Summarize(this string text, int maximumLength, bool enforceMaxLength) + { + var result = Summarize(text, maximumLength); + + if (enforceMaxLength && result.Length > maximumLength) + result = text.Substring(0, maximumLength - 3) + "..."; + + return result; + } + + /// + /// Summarizes the specified text. + /// + public static string Summarize(this string text, int maximumLength) + { + if (text.IsEmpty()) return text; + + if (text.Length > maximumLength) + { + text = text.Substring(0, maximumLength); + + var lastSpace = -1; + + foreach (char wordSeperator in " \r\n\t") + lastSpace = Math.Max(text.LastIndexOf(wordSeperator), lastSpace); + + if (lastSpace > maximumLength / 2) + text = text.Substring(0, lastSpace); + + text += "..."; + } + + return text; + } + + #region Count string + + public static string Count(this IEnumerable list, string objectTitle) + { + if (objectTitle.IsEmpty()) + objectTitle = SeparateAtUpperCases(typeof(T).Name); + + return objectTitle.ToCountString(list.Count()); + } + + public static string Count(this IEnumerable list, string objectTitle, string zeroQualifier) + { + if (objectTitle.IsEmpty()) + objectTitle = SeparateAtUpperCases(typeof(T).Name); + + return objectTitle.ToCountString(list.Count(), zeroQualifier); + } + + public static string ToCountString(this string name, int count) + { + var zeroQualifier = "no"; + + if (name.HasValue() && char.IsUpper(name[0])) + zeroQualifier = "No"; + + return ToCountString(name, count, zeroQualifier); + } + + public static string ToCountString(this string name, int count, string zeroQualifier) + { + name = name.Or("").Trim(); + if (name.IsEmpty()) + throw new Exception("'name' cannot be empty for ToCountString()."); + + if (count < 0) + throw new ArgumentException("count should be greater than or equal to 0."); + + if (count == 0) return zeroQualifier + " " + name; + else if (count == 1) return "1 " + name; + else return $"{count} {name.ToPlural()}"; + } + + public static string SeparateAtUpperCases(this string pascalCase) + { + var sb = new StringBuilder(); + for (int i = 0; i < pascalCase.Length; i++) + { + if (char.IsUpper(pascalCase[i]) && i > 0) + sb.Append(" "); + sb.Append(pascalCase[i]); + } + + return sb.ToString().ToLower(); + } + + public static string ToPlural(this string singular) + { + if (singular.IsEmpty()) return string.Empty; + + // Only change the last word: + var phrase = singular; + var prefix = ""; + if (phrase.Split(' ').Length > 1) + { + // Multi word, set prefix to anything but the last word: + prefix = phrase.Substring(0, phrase.LastIndexOf(" ")) + " "; + singular = phrase.Substring(phrase.LastIndexOf(" ") + 1); + } + + string plural; + var irregular = GetIrregularPlural(singular); + + if (irregular != "") + { + if (prefix == "") + irregular = char.ToUpper(irregular[0]) + irregular.Substring(1); + + plural = irregular; + } + else plural = GetRegularPlural(singular); + + return prefix + plural; + } + + static string GetRegularPlural(string singular) + { + var ending = char.ToLower(singular[singular.Length - 1]); + + char secondEnding; + if (singular.Length > 1) + secondEnding = char.ToLower(singular[singular.Length - 2]); + else + secondEnding = char.MinValue; + + if (ending == 's' || (secondEnding.ToString() + ending) == "ch" || (secondEnding.ToString() + ending) == "sh") + return singular + "es"; + + else if (ItNeedsIESForPlural(ending, secondEnding)) + return singular.Substring(0, singular.Length - 1) + "ies"; + + return singular + "s"; + } + + static bool ItNeedsIESForPlural(char ending, char secondEnding) + { + return ending == 'y' && + secondEnding != 'a' && + secondEnding != 'e' && + secondEnding != 'o' && + secondEnding != 'i' && + secondEnding != 'u'; + } + + [EscapeGCop("It is fine for this method to be long.")] + static string GetIrregularPlural(string singular) + { + singular = singular.ToLower(); + + switch (singular) + { + case "addendum": return "addenda"; + case "alga": return "algae"; + case "alumna": return "alumnae"; + case "alumnus": return "alumni"; + case "analysis": return "analyses"; + case "apparatus": return "apparatuses"; + case "appendix": return "appendices"; + case "axis": return "axes"; + case "bacillus": return "bacilli"; + case "bacterium": return "bacteria"; + case "basis": return "bases"; + case "beau": return "beaux"; + case "bison": return "bison"; + case "buffalo": return "buffaloes"; + case "bureau": return "bureaus"; + case "calf": return "calves"; + case "child": return "children"; + case "corps": return "corps"; + case "crisis": return "crises"; + case "criterion": return "criteria"; + case "curriculum": return "curricula"; + case "datum": return "data"; + case "deer": return "deer"; + case "die": return "dice"; + case "dwarf": return "dwarfs"; + case "diagnosis": return "diagnoses"; + case "echo": return "echoes"; + case "elf": return "elves"; + case "ellipsis": return "ellipses"; + case "embargo": return "embargoes"; + case "emphasis": return "emphases"; + case "erratum": return "errata"; + case "fireman": return "firemen"; + case "fish": return "fish"; + case "focus": return "focus"; + case "foot": return "feet"; + case "formula": return "formulas"; + case "fungus": return "fungi"; + case "genus": return "genera"; + case "goose": return "geese"; + case "half": return "halves"; + case "hero": return "heroes"; + case "hippopotamus": return "hippopotami"; + case "hoof": return "hoofs"; + case "hypothesis": return "hypotheses"; + case "index": return "indices"; + case "knife": return "knives"; + case "leaf": return "leaves"; + case "life": return "lives"; + case "loaf": return "loaves"; + case "louse": return "lice"; + case "man": return "men"; + case "matrix": return "matrices"; + case "means": return "means"; + case "medium": return "media"; + case "memorandum": return "memoranda"; + case "millennium": return "milennia"; + case "moose": return "moose"; + case "mosquito": return "mosquitoes"; + case "mouse": return "mice"; + case "nebula": return "nebulas"; + case "neurosis": return "neuroses"; + case "nucleus": return "nuclei"; + case "oasis": return "oases"; + case "octopus": return "octopi"; + case "ovum": return "ova"; + case "ox": return "oxen"; + case "paralysis": return "paralyses"; + case "parenthesis": return "parentheses"; + case "person": return "people"; + case "phenomenon": return "phenomena"; + case "potato": return "potatoes"; + case "scarf": return "scarfs"; + case "self": return "selves"; + case "series": return "series"; + case "sheep": return "sheep"; + case "shelf": return "shelves"; + case "scissors": return "scissors"; + case "species": return "species"; + case "stimulus": return "stimuli"; + case "stratum": return "strata"; + case "synthesis": return "syntheses"; + case "synopsis": return "synopses"; + case "tableau": return "tableaux"; + case "that": return "those"; + case "thesis": return "theses"; + case "thief": return "thieves"; + case "this": return "these"; + case "tomato": return "tomatoes"; + case "tooth": return "teeth"; + case "torpedo": return "torpedoes"; + case "vertebra": return "vertebrae"; + case "veto": return "vetoes"; + case "vita": return "vitae"; + case "watch": return "watches"; + case "wife": return "wives"; + case "wolf": return "wolves"; + case "woman": return "women"; + + default: return ""; + } + } + + #endregion + + /// + /// Trims some unnecessary text from the end of this string, if it exists. + /// + public static string TrimEnd(this string text, string unnecessaryText) => TrimEnd(text, unnecessaryText, caseSensitive: true); + + /// + /// Trims some unnecessary text from the end of this string, if it exists. + /// + /// By default it's TRUE. + public static string TrimEnd(this string text, string unnecessaryText, bool caseSensitive) + { + if (unnecessaryText.IsEmpty() || text.IsEmpty()) + return text.OrEmpty(); + + else if (text.EndsWith(unnecessaryText, caseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase)) + return text.TrimEnd(unnecessaryText.Length); + + else + return text; + } + + /// + /// Returns the last few characters of the string with a length + /// specified by the given parameter. If the string's length is less than the + /// given length the complete string is returned. If length is zero or + /// less an empty string is returned + /// + /// Number of characters to return + public static string Right(this string text, int length) + { + length = Math.Max(length, 0); + + if (text.Length > length) + return text.Substring(text.Length - length, length); + else + return text; + } + + /// + /// Returns the first few characters of the string with a length + /// specified by the given parameter. If the string's length is less than the + /// given length the complete string is returned. If length is zero or + /// less an empty string is returned + /// + /// Number of characters to return + public static string Left(this string text, int length) + { + length = Math.Max(length, 0); + + if (text.Length > length) + return text.Substring(0, length); + else + return text; + } + + public static string FormatWith(this string format, object arg, params object[] additionalArgs) + { + try + { + if (additionalArgs == null || additionalArgs.Length == 0) + return string.Format(format, arg); + else + return string.Format(format, new object[] { arg }.Concat(additionalArgs).ToArray()); + } + catch (Exception ex) + { + throw new FormatException("Cannot format the string '{0}' with the specified arguments.".FormatWith(format), ex); + } + } + + public static string GetLastChar(this string input) + { + if (input.HasValue()) + { + if (input.Length >= 1) + return input.Substring(input.Length - 1, 1); + else + return input; + } + else + return null; + } + + public static bool StartsWithAny(this string input, params string[] listOfBeginnings) + { + foreach (var option in listOfBeginnings) + if (input.StartsWith(option)) return true; + + return false; + } + + public static bool StartsWith(this string input, string other, bool caseSensitive) + { + if (other.IsEmpty()) return false; + + if (caseSensitive) return input.StartsWith(other); + else return input.StartsWith(other, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Gets whether this string item ends with any of the specified items. + /// + public static bool EndsWithAny(this string input, params string[] listOfEndings) + { + foreach (var option in listOfEndings) + if (input.EndsWith(option)) return true; + + return false; + } + + /// + /// Removes all Html tags from this html string. + /// + public static string RemoveHtmlTags(this string source) + { + if (source.IsEmpty()) return string.Empty; + + source = source + .Replace("
    ", Environment.NewLine) + .Replace("
    ", Environment.NewLine) + .Replace("
    ", Environment.NewLine) + .Replace("
    ", Environment.NewLine) + .Replace("

    ", Environment.NewLine); + + var from = new string[] { + """, "'", "&", "<", ">", " ", + "¡","¢","£","¤","¥","¦","§","¨", + "©","ª","«","¬","­","®","¯","°","±", + "²","³","´","µ","¶","·","¸","¹", + "º","»","¼","½","¾","¿","×", + "÷","À","Á","Â","Ã","Ä","Å", + "Æ","Ç","È","É","Ê","Ë","Ì", + "Í","Î","Ï","Ð","Ñ","Ò","Ó", + "Ô","Õ","Ö","Ø","Ù","Ú","Û", + "Ü","Ý","Þ","ß","à","á","â", + "ã","ä","å","æ","ç","è","é", + "ê","ë","ì","í","î","ï","ð","ñ", + "ò","ó","ô","õ","ö","ø","ù", + "ú","û","ü","ý","þ","ÿ"}; + + var to = new string[] { "\"", "'", "&", "<", ">", " ", + "¡","¢","£","¤","¥","¦","§","¨","©","ª","«","¬","-","®","¯","°","±","²", + "³","´","µ","¶","•","¸","¹","º","»","¼","½","¾","¿","×","÷","À","Á","Â", + "Ã","Ä","Å","Æ","Ç","È","É","Ê","Ë","Ì","Í","Î","Ï","Ð","Ñ","Ò","Ó","Ô", + "Õ","Ö","Ø","Ù","Ú","Û","Ü","Ý","Þ","ß","à","á","â","ã","ä","å","æ","ç", + "è","é","ê","ë","ì","í","î","ï","ð","ñ","ò","ó","ô","õ","ö","ø","ù","ú", + "û","ü","ý","þ","ÿ", + }; + + for (int i = 0; i < from.Length; i++) + source = source.Replace(from[i], to[i]); + + return Regex.Replace(source, @"<(.|\n)*?>", " ").Trim(); + } + + ///

    + /// Gets all indices of a specified string inside this text. + /// + public static IEnumerable AllIndicesOf(this string text, string pattern) + { + if (pattern == null) + throw new ArgumentNullException(nameof(pattern)); + + var result = new List(); + + int index = -1; + + do + { + index = text.IndexOf(pattern, index + 1); + if (index > -1) + result.Add(index); + } + while (index > -1); + + return result; + } + + /// + /// Returns this text with the specified prefix if this has a value. If this text is empty or null, it will return empty string. + /// + public static string WithPrefix(this string text, string prefix) + { + if (text.IsEmpty()) return string.Empty; + else return prefix + text; + } + + /// + /// Returns this text with the specified suffix if this has a value. If this text is empty or null, it will return empty string. + /// + public static string WithSuffix(this string text, string suffix) + { + if (text.IsEmpty()) return string.Empty; + else return text + suffix; + } + + /// + /// Wraps this text between the left and right wrappers, only if this has a value. + /// + public static string WithWrappers(this string text, string left, string right) + { + if (text.IsEmpty()) + return string.Empty; + + return left + text + right; + } + + /// + /// Repeats this text by the number of times specified. + /// + public static string Repeat(this string text, int times) => Repeat(text, times, null); + + /// + /// Repeats this text by the number of times specified, seperated with the specified seperator. + /// + public static string Repeat(this string text, int times, string seperator) + { + if (times < 0) throw new ArgumentOutOfRangeException(nameof(times), $"{nameof(times)} should be 0 or more."); + + if (times == 0) return string.Empty; + + var r = new StringBuilder(); + + for (var i = 1; i <= times; i++) + { + r.Append(text); + + if (seperator != null) r.Append(seperator); + } + + return r.ToString(); + } + + /// + /// Determines if this string value contains a specified substring. + /// + public static bool Contains(this string text, string subString, bool caseSensitive) + { + if (text == null && subString == null) + return true; + + if (text == null) return false; + + if (subString.IsEmpty()) return true; + + if (caseSensitive) + return text.Contains(subString); + else + return text.ToUpper().Contains(subString?.ToUpper()); + } + + /// + /// Removes the specified substrings from this string object. + /// + public static string Remove(this string text, string firstSubstringsToRemove, params string[] otherSubstringsToRemove) => + text.Remove(firstSubstringsToRemove).Remove(otherSubstringsToRemove); + + [EscapeGCop("It is the Except definition and so it cannot call itself")] + public static string Remove(this string text, string[] substringsToRemove) + { + if (text.IsEmpty()) return text; + + var result = text; + + foreach (var sub in substringsToRemove) + if (sub.HasValue()) + result = result.Replace(sub, string.Empty); + + return result; + } + + /// + /// Removes the specified substrings from this string object. + /// + [EscapeGCop("It is the Except definition and so it cannot call itself")] + public static string Remove(this string text, string substringToRemove) + { + if (text.IsEmpty()) return text; + + return text.Replace(substringToRemove, string.Empty); + } + + /// + /// Replaces all occurances of a specified phrase to a substitude, even if the original phrase gets produced again as the result of substitution. Note: It's an expensive call. + /// + public static string KeepReplacing(this string text, string original, string substitute) + { + if (text.IsEmpty()) return text; + + if (original == substitute) return text; // prevent loop + + while (text.Contains(original)) + text = text.Replace(original, substitute); + + return text; + } + + /// + /// Gets this same string when a specified condition is True, otherwise it returns empty string. + /// + public static string OnlyWhen(this string text, bool condition) + { + if (condition) + return text; + else + return string.Empty; + } + + /// + /// Gets this same string when a specified condition is False, otherwise it returns empty string. + /// + public static string Unless(this string text, bool condition) + { + if (condition) + return string.Empty; + else + return text; + } + + /// + /// Gets the lines of this string. + /// + public static string[] ToLines(this string text) + { + if (text == null) return new string[0]; + + return text.Split('\n').Select(l => l.Trim('\r')).ToArray(); + } + + /// + /// Indicates whether this character is categorized as an uppercase letter. + /// + public static bool IsUpper(this char character) => char.IsUpper(character); + + /// + /// Indicates whether this character is categorized as a lowercase letter. + /// + public static bool IsLower(this char character) => char.IsLower(character); + + /// + /// Indicates whether this character is categorized as a letter. + /// + public static bool IsLetter(this char character) => char.IsLetter(character); + + public static bool IsAnyOf(this char character, params char[] characters) => characters.Contains(character); + + /// + /// Indicates whether this character is categorized as digit. + /// + public static bool IsDigit(this char character) => char.IsDigit(character); + + /// + /// Indicates whether this character is categorized as White Space (space, tab, new line, etc). + /// + public static bool IsWhiteSpace(this char character) => char.IsWhiteSpace(character); + + /// + /// Indicates whether this character is categorized as a letter or digit. + /// + public static bool IsLetterOrDigit(this char character) => char.IsLetterOrDigit(character); + + /// + /// Converts the value of this character to its uppercase equivalent. + /// + public static char ToUpper(this char character) => char.ToUpper(character); + + /// + /// Converts the value of this character to its lowercase equivalent. + /// + public static char ToLower(this char character) => char.ToLower(character); + + /// + /// If this expression is null, returns an empty string. Otherwise, it returns the ToString() of this instance. + /// + public static string ToStringOrEmpty(this object @object) + { + if (@object == null) + return string.Empty; + else + return @object.ToString().Or(string.Empty); + } + + /// + /// Determines whether this string object does not contain the specified phrase. + /// + public static bool Lacks(this string text, string phrase, bool caseSensitive = false) + { + if (text.IsEmpty()) + return phrase.HasValue(); + + return !text.Contains(phrase, caseSensitive); + } + + /// + /// Determines whether this string object does not contain any of the specified phrases. + /// + public static bool LacksAll(this string text, params string[] phrases) => + LacksAll(text, caseSensitive: false, phrases: phrases); + + /// + /// Determines whether this string object does not contain any of the specified phrases. + /// + public static bool LacksAll(this string text, bool caseSensitive, params string[] phrases) + { + if (text.IsEmpty()) return true; + + return phrases.None(p => p.HasValue() && text.Contains(p, caseSensitive)); + } + + /// + /// Returns natural English literal text for a specified pascal case string value. + /// For example it coverts "ThisIsSomething" to "This is something". + /// + public static string ToLiteralFromPascalCase(this string pascalCaseText) + { + if (pascalCaseText.IsEmpty()) return string.Empty; + + return LiteralFromPascalCaseCache.GetOrAdd(pascalCaseText, source => + { + var parts = new List(); + var lastPart = ""; + + foreach (var c in source) + { + if (c.IsUpper() && lastPart.HasValue()) + { + parts.Add(lastPart); + lastPart = ""; + } + + lastPart += c; + } + + parts.Add(lastPart); + + var result = parts.First() + parts.Skip(1).Select(a => a.Length < 2 ? a : " " + a.ToLower()).ToString(""); + return result.Trim(); + }); + } + + /// + /// Returns the all-lower-case version of this list. + /// + public static IEnumerable ToLower(this IEnumerable list) => list.ExceptNull().Select(i => i.ToLower()); + + /// + /// Returns the all-upper-case version of this list. + /// + public static IEnumerable ToUpper(this IEnumerable list) => list.ExceptNull().Select(i => i.ToUpper()); + + /// + /// Gets the UTF8-with-signature bytes of this text. + /// + public static byte[] GetUtf8WithSignatureBytes(this string text) + { + var bytes = System.Text.Encoding.UTF8.GetBytes(text); + + // Add signature: + var result = new byte[bytes.Length + 3]; + + // Utf-8 signature code: BOM + result[0] = UTF8SignatureFirstByte; + result[1] = UTF8SignatureSecondByte; + result[2] = UTF8SignatureThirdByte; + + bytes.CopyTo(result, 3); + + return result; + } + + /// + /// Converts this array of bytes to a Base64 string. + /// + public static string ToBase64String(this byte[] value) + { + if (value == null) return null; + + return Convert.ToBase64String(value); + } + + /// + /// Converts this Base64 string to an array of bytes. + /// + public static byte[] ToBytes(this string value) + { + if (value.IsEmpty()) return new byte[0]; + + return Convert.FromBase64String(value); + } + + /// + /// Converts this string to an array of bytes with the given encoding. + /// + public static byte[] ToBytes(this string value, Encoding encoding) => encoding.GetBytes(value); + + /// + /// Determines whether this text contains any of the specified keywords. + /// If the keywords list contains a null or empty string, it throws an exception. If you wish to ignore those, use .Trim() on your keywords list. + /// + public static bool ContainsAny(this string text, IEnumerable keywords, bool caseSensitive = true) + { + if (keywords == null) + throw new ArgumentNullException(nameof(keywords)); + + if (text.IsEmpty()) return false; + + foreach (var key in keywords) + { + if (key.IsEmpty()) throw new ArgumentException($"nameof(keywords) contains a null or empty string element."); + + if (text.Contains(key, caseSensitive)) + return true; + } + + return false; + } + + /// + /// Splits this list of string items by a specified separator into a number of smaller lists of string. + /// + public static IEnumerable> Split(this IEnumerable list, string separator) + { + var currentArray = new List(); + + foreach (var item in list) + { + if (item == separator) + { + if (currentArray.Count > 0) + { + yield return currentArray; + currentArray = new List(); + } + } + else + currentArray.Add(item); + } + } + + /// + /// Converts this path into a file object. + /// + public static System.IO.FileInfo AsFile(this string path) => new System.IO.FileInfo(path); + + /// + /// Converts this path into a Uri object. + /// + public static Uri AsUri(this string path) => new Uri(path); + + /// + /// Converts this path into a directory object. + /// + public static System.IO.DirectoryInfo AsDirectory(this string path) => new System.IO.DirectoryInfo(path); + + /// + /// Gets the Xml Encoded version of this text. + /// + public static string XmlEncode(this string text) + { + if (text.IsEmpty()) return string.Empty; + + foreach (var set in XMLEscapingChars) + text = text.Replace(set[0], set[1]); + + return text; + } + + /// + /// Gets the Xml Decoded version of this text. + /// + public static string XmlDecode(this string text) + { + if (text.IsEmpty()) return string.Empty; + + foreach (var set in XMLEscapingChars) + text = text.Replace(set[1], set[0]); + + return text; + } + + /// + /// Creates a hash of a specified clear text with a mix of MD5 and SHA1. + /// + public static string CreateHash(this string clearText, object salt = null) + { + var firstHash = clearText.CreateMD5Hash(); + + firstHash = $"«6\"£k&36 2{firstHash}mmñÃ5d*"; + + firstHash += salt.ToStringOrEmpty(); + + return firstHash.CreateSHA1Hash(); + } + + /// + /// Creates MD5 hash of this text + /// Specifies whether a hex-compatible string is expected. + /// + public static string CreateMD5Hash(this string clearText, bool asHex = false) + { + var value = MD5.Create().ComputeHash(UnicodeEncoding.UTF8.GetBytes(clearText)); + + if (asHex) + return BitConverter.ToString(value).Remove("-"); + else + return Convert.ToBase64String(value); + } + + /// + /// Creates MD5 hash of this text + /// + public static string CreateMD5Hash(this string clearText) => + Convert.ToBase64String(MD5.Create().ComputeHash(UnicodeEncoding.UTF8.GetBytes(clearText))); + + /// + /// Creates SHA1 hash of this text + /// + public static string CreateSHA1Hash(this string clearText) + { + return Convert.ToBase64String(SHA1.Create().ComputeHash(UnicodeEncoding.UTF8.GetBytes(clearText))).TrimEnd('='); + } + + public static IEnumerable Split(this string text, int chunkSize) + { + if (text.HasValue()) + { + if (text.Length > chunkSize) + { + yield return text.Substring(0, chunkSize); + foreach (var part in text.Substring(chunkSize).Split(chunkSize)) + yield return part; + } + else yield return text; + } + } + + public static string Substring(this string text, int fromIndex, string toText) + { + var toIndex = text.IndexOf(toText, fromIndex + 1); + + if (fromIndex == -1) return string.Empty; + + if (toIndex == -1) return string.Empty; + + if (toIndex < fromIndex) return string.Empty; + + return text.Substring(fromIndex, toIndex - fromIndex); + } + + public static string Substring(this string text, string from, string to, bool inclusive) => + Substring(text, from, to, inclusive, caseSensitive: true); + + public static string Substring(this string text, string from, string to, bool inclusive, bool caseSensitive) + { + var comparison = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; + + var fromIndex = text.IndexOf(from, comparison); + var toIndex = text.IndexOf(to, fromIndex + from.Length + 1, comparison); + + if (fromIndex == -1) + return string.Empty; + + if (toIndex == -1) + return string.Empty; + + if (toIndex < fromIndex) + return string.Empty; + + if (inclusive) toIndex += to.Length; + else fromIndex += from.Length; + + return text.Substring(fromIndex, toIndex - fromIndex); + } + + public static string ToString(this byte[] data, Encoding encoding) => encoding.GetString(data); + + /// + /// Escapes all invalid characters of this string to it's usable as a valid json constant. + /// + public static string ToJsonText(this string source) + { + if (source.IsEmpty()) return string.Empty; + + return source.Replace("\\", "\\\\").Replace("\r", "\\r").Replace("\n", "\\n").Replace("\t", "\\t").Replace("\"", "\\\""); + } + + /// + /// Getsa SHA1 hash of this text where all characters are alpha numeric. + /// + public static string ToSimplifiedSHA1Hash(this string clearText) => + new string(clearText.CreateSHA1Hash().ToCharArray().Where(c => c.IsLetterOrDigit()).ToArray()); + + /// + /// Attempts to Parse this String as the given Enum type. + /// + public static T? TryParseEnum(this string text, T? @default = null) where T : struct + { + if (Enum.TryParse(text, ignoreCase: true, result: out T value)) return value; + + return @default; + } + + /// + /// If it's null, it return empty string. Otherwise it returns this. + /// + public static string OrEmpty(this string text) + { + if (text == null) return string.Empty; + return text; + } + + /// + /// Returns the only matched string in the given text using this Regex pattern. + /// Returns null if more than one match found. + /// + public static string GetSingleMatchedValueOrDefault(this Regex pattern, string text) + { + var matches = pattern.Matches(text).Cast() + .Except(m => !m.Success || string.IsNullOrWhiteSpace(m.Value)) + .ToList(); + return matches.Count == 1 ? matches[0].Value : null; + } + + /// + /// Returns true if this collection has more than one item. + /// + public static bool HasMany(this IEnumerable collection) + { + using (var en = collection.GetEnumerator()) + return en.MoveNext() && en.MoveNext(); + } + + /// + /// Returns a string value that can be saved in xml. + /// + public static string XmlEscape(this string unescaped) + { + if (unescaped.IsEmpty()) return string.Empty; + + foreach (var set in XMLEscapingChars.Take(4)) + unescaped = unescaped.Replace(set[0], set[1]); + + return unescaped; + } + + /// + /// Returns a string value without any xml-escaped characters. + /// + public static string XmlUnescape(this string escaped) + { + if (escaped.IsEmpty()) return string.Empty; + + foreach (var set in XMLEscapingChars.Take(4)) + escaped = escaped.Replace(set[1], set[0]); + + return escaped; + } + + /// + /// Returns valid JavaScript string content with reserved characters replaced by encoded literals. + /// + public static string JavascriptEncode(this string text) + { + foreach (var ch in JsUnsafeCharacters) + { + var replace = new string(ch, 1); + var encoded = string.Format("\\x{0:X}", Convert.ToInt32(ch)); + text = text.Replace(replace, encoded); + } + + text = text.Replace(Environment.NewLine, "\\n"); + + return text; + } + + /// + /// Returns valid PascalCase JavaScript or C# string content. + /// + public static string ToPascalCaseId(this string text) + { + if (text.IsEmpty()) return text; + + return new PascalCaseIdGenerator(text).Build(); + } + + /// + /// Returns valid camelCase javaScript or C# string content. + /// + public static string ToCamelCaseId(this string text) + { + var result = ToPascalCaseId(text); + + if (result.IsEmpty()) return string.Empty; + + if (result.Length == 1) return result.ToLower(); + else return char.ToLower(result[0]) + result.Substring(1); + } + + /// + /// Converts [hello world] to [Hello World]. + /// + public static string CapitaliseFirstLetters(this string name) + { + if (name.IsEmpty()) return name; + + return name.Split(' ').Trim().Select(x => x.First().ToUpper() + x.Substring(1)).ToString(" "); + } + + /// + /// Trims all text before the specified search phrase. + /// + public static string TrimBefore(this string text, string search, bool caseSensitive = false, bool trimPhrase = false) + { + if (text.IsEmpty()) return text; + + int index; + + if (caseSensitive) index = text.IndexOf(search); + else + index = text.IndexOf(search, StringComparison.OrdinalIgnoreCase); + + if (index == -1) return text; + + text = text.Substring(index); + + if (trimPhrase) text = text.TrimStart(search, caseSensitive); + + return text; + } + + public static string TrimStart(this string text, string search, bool caseSensitive) + { + if (caseSensitive) return text.TrimStart(search); + + if (text.StartsWith(search, caseSensitive: false)) + return text.Substring(search.Length); + + return text; + } + + public static string TrimAfter(this string text, string phrase, bool trimPhrase = true, bool caseSensitive = false) + { + if (text.IsEmpty()) return text; + + int index; + + if (caseSensitive) index = text.IndexOf(phrase); + else + index = text.IndexOf(phrase, StringComparison.OrdinalIgnoreCase); + + if (index == -1) return text; + + if (!trimPhrase) index += phrase.Length; + + return text.Substring(0, index); + } + + /// + /// Returns this string. But if it's String.Empty, it returns NULL. + /// + public static string OrNullIfEmpty(this string text) + { + if (string.Equals(text, string.Empty)) return null; + + return text; + } + + /// + /// Capitalises the first letter and lower-cases the rest. + /// + public static string ToProperCase(this string name) + { + if (name.IsEmpty()) return name; + + return name.First().ToUpper() + name.Substring(1).ToLower(); + } + + /// + /// It will replace all occurances of a specified WHOLE WORD and skip occurances of the word with characters or digits attached to it. + /// + public static string ReplaceWholeWord(this string text, string word, string replacement, bool caseSensitive = true) + { + var pattern = "\\b" + Regex.Escape(word) + "\\b"; + if (caseSensitive) return Regex.Replace(text, pattern, replacement); + else return Regex.Replace(text, pattern, replacement, RegexOptions.IgnoreCase); + } + + /// + /// Returns if a specified WHOLE WORD is found in this text. It skips occurances of the word with characters or digits attached to it. + /// + public static bool ContainsWholeWord(this string text, string word, bool caseSensitive = true) + { + if (text.IsEmpty()) return false; + + var pattern = "\\b" + Regex.Escape(word) + "\\b"; + + if (caseSensitive) return Regex.IsMatch(text, pattern); + else return Regex.IsMatch(text, pattern, RegexOptions.IgnoreCase); + } + + [EscapeGCop("It is an extension for boolean type")] + public static string ToString(this bool value, string trueText, string falseText) => + ToString(value, trueText, falseText, nullText: null); + + public static string ToString(this bool? value, string trueText, string falseText, string nullText = null) + { + if (value == true) return trueText; + else if (value == false) return falseText; + else return nullText; + } + + /// + /// Ensure that this string object starts with a specified other one. + /// If it does not, then it prepends that and return the combined text. + /// + public static string EnsureStartsWith(this string text, string expression, bool caseSensitive = true) + { + if (expression.IsEmpty()) return text; + + if (text.IsEmpty()) return expression; + + if (text.StartsWith(expression, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) return text; + + return expression + text; + } + + /// + /// Ensure that this string object ends with a specified other one. + /// If it does not, then it appends that and return the combined text. + /// + public static string EnsureEndsWith(this string text, string expression, bool caseSensitive = true) + { + if (expression.IsEmpty()) return text; + + if (text.IsEmpty()) return expression; + + if (text.EndsWith(expression, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) return text; + + return text + expression; + } + } +} \ No newline at end of file diff --git a/Olive/-Extensions/StringBuilder.cs b/Olive/-Extensions/StringBuilder.cs new file mode 100644 index 000000000..7bc3a544d --- /dev/null +++ b/Olive/-Extensions/StringBuilder.cs @@ -0,0 +1,58 @@ +using System; +using System.Text; + +namespace Olive +{ + partial class OliveExtensions + { + [System.Diagnostics.DebuggerStepThrough] + public static void AddFormattedLine(this StringBuilder builder, string format, params object[] args) + { + try + { + builder.AppendFormat(format, args); + builder.AppendLine(); + } + catch (Exception ex) + { + throw new FormatException("Could not add formatted line of \"" + + format + "\" with the following parameters: {" + + args.ToString(", ") + "}.", ex); + } + } + + /// + /// Wraps the content of this string builder with the provided text blocks. + /// + public static void WrapIn(this StringBuilder builder, string left, string right) + { + builder.Insert(0, left); + builder.Append(right); + } + + /// + /// Wraps the content of this string builder with the provided lines of text. + /// A line-break will be added to the left element, and another line break will be added before the right element. + /// + public static void WrapInLines(this StringBuilder builder, string left, string right) + { + builder.Insert(0, left + Environment.NewLine); + builder.Append(Environment.NewLine + right); + } + + public static void AppendIf(this StringBuilder builder, string text, bool condition) + { + if (condition) builder.Append(text); + } + + public static void AppendLineIf(this StringBuilder builder, string text, bool condition) + { + if (condition) builder.AppendLine(text); + } + + public static void AppendLineIf(this StringBuilder builder, string text) + { + if (text.HasValue()) builder.AppendLine(text); + } + } +} diff --git a/Olive/-Extensions/TimeSpan.cs b/Olive/-Extensions/TimeSpan.cs new file mode 100644 index 000000000..5e531760c --- /dev/null +++ b/Olive/-Extensions/TimeSpan.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Olive +{ + partial class OliveExtensions + { + const double ACTUAL_DAYS_PER_YEAR = 365.2425; + const int NINETEEN_HUNDRED = 1900; + + public static TimeSpan Days(this int number) => TimeSpan.FromDays(number); + + public static TimeSpan Hours(this int number) => TimeSpan.FromHours(number); + + public static TimeSpan Minutes(this int number) => TimeSpan.FromMinutes(number); + + public static TimeSpan Seconds(this int number) => TimeSpan.FromSeconds(number); + + public static TimeSpan Milliseconds(this int number) => TimeSpan.FromMilliseconds(number); + + public static TimeSpan Ticks(this int number) => TimeSpan.FromTicks(number); + + public static TimeSpan Multiply(this TimeSpan @this, double by) => TimeSpan.FromMilliseconds(@this.TotalMilliseconds * by); + + /// + /// Gets the approximate number of the total years equivalent to this timespan. + /// This is not accurate due to unknown leap years in the actual period to which this TimeSpan relates. + /// + public static double ApproxTotalYears(this TimeSpan @this) => @this.TotalDays / ACTUAL_DAYS_PER_YEAR; + + /// + /// Converts this time to the date time on date of 1900-01-01. + /// + public static DateTime ToDate(this TimeSpan time) => new DateTime(NINETEEN_HUNDRED, 1, 1).Add(time); + + /// + /// Converts this time to the date time on date of 1900-01-01. + /// + public static DateTime? ToDate(this TimeSpan? time) => time?.ToDate(); + + /// + /// Gets the natural text for this timespan. For example "2 days, 4 hours and 3 minutes". + /// + public static string ToNaturalTime(this TimeSpan period) => ToNaturalTime(period, longForm: true); + + public static string ToNaturalTime(this TimeSpan period, bool longForm) => ToNaturalTime(period, 2, longForm); + + public static string ToNaturalTime(this TimeSpan period, int precisionParts) => + ToNaturalTime(period, precisionParts, longForm: true); + + [EscapeGCop("It is ok for trying methods to have out param.")] + static bool TryReduceDays(ref TimeSpan period, int len, out double result) + { + if (period.TotalDays >= len) + { + result = (int)Math.Floor(period.TotalDays / len); + period -= TimeSpan.FromDays(len * result); + + return true; + } + + result = 0; + return false; + } + + /// + /// Gets the natural text for this timespan. For example "2 days, 4 hours and 3 minutes". + /// + public static string ToNaturalTime(this TimeSpan period, int precisionParts, bool longForm) + { + // TODO: Support months and years. + // Hint: Assume the timespan shows a time in the past of NOW. Count years and months from there. + // i.e. count years and go back. Then count months and go back... + + var names = new Dictionary { { "year", "y" }, { "month", "M" }, { "week", "w" }, { "day", "d" }, { "hour", "h" }, { "minute", "m" }, { "second", "s" }, { " and ", " " }, { ", ", " " } }; + + Func name = (k) => longForm ? k : names[k]; + + var parts = new Dictionary(); + + if (TryReduceDays(ref period, 365, out double years)) + parts.Add(name("year"), years); + + if (TryReduceDays(ref period, 30, out double months)) + parts.Add(name("month"), months); + + if (TryReduceDays(ref period, 7, out double weeks)) + parts.Add(name("week"), weeks); + + if (period.TotalDays >= 1) + { + parts.Add(name("day"), period.Days); + period -= TimeSpan.FromDays(period.Days); + } + + if (period.TotalHours >= 1 && period.Hours > 0) + { + parts.Add(name("hour"), period.Hours); + period = period.Subtract(TimeSpan.FromHours(period.Hours)); + } + + if (period.TotalMinutes >= 1 && period.Minutes > 0) + { + parts.Add(name("minute"), period.Minutes); + period = period.Subtract(TimeSpan.FromMinutes(period.Minutes)); + } + + if (period.TotalSeconds >= 1 && period.Seconds > 0) + { + parts.Add(name("second"), period.Seconds); + period = period.Subtract(TimeSpan.FromSeconds(period.Seconds)); + } + + else if (period.TotalSeconds > 0) + { + parts.Add(name("second"), period.TotalSeconds.Round(3)); + period = TimeSpan.Zero; + } + + var outputParts = parts.Take(precisionParts).ToList(); + var r = new StringBuilder(); + + foreach (var part in outputParts) + { + r.Append(part.Value); + + if (longForm) r.Append(" "); + + r.Append(part.Key); + + if (part.Value > 1 && longForm) r.Append("s"); + + if (outputParts.IndexOf(part) == outputParts.Count - 2) + r.Append(name(" and ")); + else if (outputParts.IndexOf(part) < outputParts.Count - 2) + r.Append(name(", ")); + } + + return r.ToString(); + } + + public static string ToString(this TimeSpan? value, string format) => ("{0:" + format + "}").FormatWith(value); + + public static int CompareTo(this TimeSpan? @this, TimeSpan? another) + { + if (@this == another) return 0; + if (another == null) return @this == TimeSpan.Zero ? 1 : @this.Value.CompareTo(TimeSpan.Zero); + if (@this == null) return another == TimeSpan.Zero ? -1 : another.Value.CompareTo(TimeSpan.Zero); + return @this.Value.CompareTo(another.Value); + } + } +} \ No newline at end of file diff --git a/Olive/-Extensions/Xml.cs b/Olive/-Extensions/Xml.cs new file mode 100644 index 000000000..9230c9cfc --- /dev/null +++ b/Olive/-Extensions/Xml.cs @@ -0,0 +1,121 @@ +using System; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Linq; + +namespace Olive +{ + partial class OliveExtensions + { + /// + /// Gets an Element with the specified path. For example "Tree/Branch1/Branch2". + /// + public static XElement GetElement(this XContainer parent, string path) => FindNode(parent, path) as XElement; + + /// + /// Gets a node with the specified path. For example "Tree/Branch1/Branch2". + /// + [Obsolete("Use FindNode instead")] + public static XObject GetNode(this XContainer parent, string path) => parent.FindNode(path); + + /// + /// Finds a node with the specified path. For example "Tree/Branch1/Branch2". + /// + public static XObject FindNode(this XContainer parent, string path) + { + if (path.IsEmpty()) + throw new ArgumentNullException(nameof(path)); + + var node = parent; + + foreach (var part in path.Split('/')) + { + if (part.StartsWith("@")) + { + // Attribute: + var element = node as XElement; + if (element == null) return null; + else + { + var attributeName = part.Substring(1); + var withXName = element.Attribute(attributeName); + if (withXName != null) return withXName; + else + return element.Attributes().FirstOrDefault(a => a.Name != null && a.Name.LocalName == attributeName); + } + } + else + { + var withXName = node.Element(part); + if (withXName != null) node = withXName; + else + { + node = node.Elements().FirstOrDefault(e => e.Name != null && e.Name.LocalName == part); + if (node == null) return null; + } + } + } + + return node; + } + + /// + /// Gets the value of an attribute or inner text of an element with the specified path. For example "Tree/Branch1/Branch2". + /// + public static T GetValue(this XContainer parent, string path) + { + string value = null; + + var node = parent.FindNode(path); + + if (node is XElement) value = (node as XElement).Value; + else if (node is XAttribute) value = (node as XAttribute).Value; + else if (node != null) + throw new Exception("The provided path (" + path + ") points to an invalid Xml node (" + node.GetType() + ")."); + + if (value.IsEmpty()) return default(T); + + if (typeof(T) == typeof(string)) return (T)(object)value; + + return value.To(); + } + + /// + /// Adds this node to a specified container and returns it back to be used as fluent API. + /// + public static T AddTo(this T node, XContainer container) where T : XNode + { + container.Add(node); + return node; + } + + /// + /// Removes all namespaces from this element. + /// + public static XElement RemoveNamespaces(this XElement node) + { + var result = new XElement(node.Name.LocalName); + + foreach (var attribute in node.Attributes()) + result.Add(new XAttribute(attribute.Name.LocalName, attribute.Value)); + + if (node.HasElements) + foreach (var child in node.Elements()) + result.Add(child.RemoveNamespaces()); + else + result.Value = node.Value; + + return result; + } + + public static XmlReader ToXmlReader(this string xmlString) => XmlReader.Create(new StringReader(xmlString)); + + // public static XmlElement ToXmlElement(this XElement element) + // { + // var doc = new XmlDocument(); + // doc.Load(element.CreateReader()); + // return doc.DocumentElement; + // } + } +} \ No newline at end of file diff --git a/Olive/Logging/DefaultLogger.cs b/Olive/Logging/DefaultLogger.cs new file mode 100644 index 000000000..26eb032e4 --- /dev/null +++ b/Olive/Logging/DefaultLogger.cs @@ -0,0 +1,49 @@ +using System; + +namespace Olive +{ + public class DefaultLogger : ILogger + { + static DefaultLogger InstanceField; + + public bool Enable { get; set; } + + public static DefaultLogger Instance + { + get + { + if (InstanceField == null) + { + InstanceField = new DefaultLogger + { + Enable = Config.Get(key: "DebugMode", defaultValue: false) + }; + } + + return InstanceField; + } + } + + public DefaultLogger() + { + // single tone + } + + public void Log(string eventTitle, string description, object relatedObject, string userId, string userIp) + { + if (!Enable) return; + + Console.Write($"Event Start\r\nTitle: '{eventTitle}', UserId: '{userId}', UserIP: '{userIp}'"); + Console.Write($"Description: {description}\r\nEvent End"); + } + + public void RecordException(Exception ex) => RecordException(string.Empty, ex); + + public void RecordException(string description, Exception ex) + { + if (!Enable) return; + + Console.Write(ex.ToLogString(description)); + } + } +} diff --git a/Olive/Logging/ILogger.cs b/Olive/Logging/ILogger.cs new file mode 100644 index 000000000..cf2ea0af2 --- /dev/null +++ b/Olive/Logging/ILogger.cs @@ -0,0 +1,11 @@ +namespace Olive +{ + using System; + + public interface ILogger + { + void RecordException(Exception ex); + void RecordException(string description, Exception ex); + void Log(string eventTitle, string description, object relatedObject, string userId, string userIp); + } +} \ No newline at end of file diff --git a/Olive/Logging/Log.cs b/Olive/Logging/Log.cs new file mode 100644 index 000000000..bed548206 --- /dev/null +++ b/Olive/Logging/Log.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace Olive +{ + public static class Log + { + static List Loggers = new List(); + + static Log() => Loggers.Add(DefaultLogger.Instance); + + public static void RegisterLogger(ILogger logger) => Loggers.Add(logger); + + public static void ClearLogger() => Loggers.Clear(); + + public static void Error(Exception ex) => Loggers.ForEach(logger => logger.RecordException(ex)); + + public static void Error(string description, Exception ex = null) + { + if (ex == null) Record("Exception", description); + else + Loggers.ForEach(logger => logger.RecordException(description, ex)); + } + + public static void Warning(string description, object relatedObject = null, string userId = null, string userIp = null) => + Record("Warning", description, relatedObject, userId, userIp); + + public static void Debug(string description, object relatedObject = null, string userId = null, string userIp = null) => + Record("Debug", description, relatedObject, userId, userIp); + + public static void Info(string description, object relatedObject = null, string userId = null, string userIp = null) => + Record("Info", description, relatedObject, userId, userIp); + + public static void Audit(string description, object relatedObject = null, string userId = null, string userIp = null) => + Record("Audit", description, relatedObject, userId, userIp); + + public static void Record(string eventTitle, string description, object relatedObject = null, string userId = null, string userIp = null) => + Loggers.ForEach(logger => logger.Log(eventTitle, description, relatedObject, userId, userIp)); + } +} \ No newline at end of file diff --git a/Olive/Logo.png b/Olive/Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9d24750443c5ee6ef534166f958c830abce5d202 GIT binary patch literal 9558 zcmW++bzGBO6yAW*sdR^QOHUfevPW0F?dzF5r-V#UtF0OfXFwn7O|PEW{ONGn2zA`azj#GAlr_l9mA!P9<*|I`;l#@< zq4k)=t3Fv%hqIA5gN6tAN|N4}p65yL52?79b%Q!CWQ0Qxoe5mk5eKTzZ@K;wkMf+; zgvRAvfAr687*nzx>HNGnpGD)OYCTge8Xdf~_Vu-`@gv^Qi{19U!5i#e^xnmg%1wa5 z&Aj++r|1bMKTYg(m*2)|`sy*drPX{`&#F%tE==?*_+sz&YqWxZo8_(|R2eJZz3{<< zS9nR&?prvCaUsD*pdI{FI8S`c{-&F1?qz#v)uZ)fes-nsY0(w9COP>D_Udea!6|~P z2GYuHRG{eXy|>iqioHC^mi76;DeJc&aCvztV)*vHra?Z~(jZsX&}XUR=VZ31{g>Hl z`++9kc@Z{+z=wGXL4%VUh0$VNms`RIJI%`tLm}prmr)R>U9-mfZ}cdAY__8V;UtE_H;t z-dta}_Qrx-SNh^->#11fImZf=g#DH}Hd&$|{55j;UElR<>M>-0?VJ6Qar4<*F`_?z z{+xVz`gCGq!sSC_qj?&u!hISPgQ(p-dT~dQY!O@TY%vER$A|;X01S#NU~`f;U}KE! zG#n;~Zq)K&qO+}kRthO$r{YkiWe|6yeem>eovN>&U(@vDB)6Ttz3A{1`DKIqNLBNQ z)VLxK&UyR@4i&K)$4;S})8_8_xk{2VcjH|v_p(eyGoo8pe$>!xow)@i3 z(l(EMd}s{~4YT!_jy($_t*RicX2-|JNe=B#lSgvIQ@gL%#s5aa17|x`p44Tl_6plr z{R*~A)~ox$AO@lC?CdOVYO zcM|*f@uP^PwYB{>RVO!n+mPLxb%GyKa(uh7xs)7cRy}TJT-js!4?KaErlwp%!os5N z$XvC={r5pJ*nmHcLpEy^+UfV^kpv3>?=ML-*?=&@wrEtR^Z4Rf7sT=r?_a!IlO<fAFK8&$D2ORw#h{2dBG47(dF-CMsYS!N z_-9`N`kh5hOiB02kB?&zWA`*14gPdtG?y|u97l|mFTTWXf8{Uylo?ath+3(PzF5ye zT#jsp*Q5~n$J`ET8b94#@b0e`##ib@YA}vI+`e=2zzYi#Q#?oa;#aE6_hj8iO;Las z7~ZYR3-qT!@b9`YnH~gw=B0#|ksrh25KCr!m-j=NOIgu{k*9A#8IT=nqJK8GsU;SU z9cN~%U=Fz@X-5NAuvlyubo)2J`kSh;943S7woEZ`VWn?4J{+&+3$O;KU`#@&@yhI9>Fn!-n7}ZsUq3~;T76wEUmuh%V0QnqbZH`Qyq=Qnn2&>mnZ*kNCoGuK>n&+-Fu#reUqMHPz& zh>y3)cIQ#?_M(GpU&BQiY51nBmO@Wln*d(`Tv!tr?ym19B+`-tR#Cpk7d)<~gh|^r z3r7Yiy^Ugd8#REll>qZ%4w0M?(rkazT2`%8eDl+9f^()Na2Kw~TvDy?ncBo5a%blW zgZ<-Y&vMIVZ{@}8b_?9n^=A&dIg%%|o8Pm;eld!+7EDwCt#OV@Aw5peC$eop*Oo{! zyHC5XWs(#J9SKcD{gE-oJcxc~U{LU&eN8pzulDDfk~;h&>=!eY#-M| z@Kx37xto92cwhDN1OX5{n5Z13mNSE=T8wZ9NMmD)$ zkCU2M=eg~oj^2BDEYNf_Hy82WL2dZ&(7jfUGKXECX`Z&|u`aY#L>JgxL)aP3n}71h zQptWD_;42&ESX52po!5)wI=SWa88tND2-{(>{L5G{&K*2HR@H{AEjnJgRN!$UwgN) zO7xcV{pq%KA22WmwBf90gl1E?lfsRDUJweZ9AQS3jili~4BtmvI{3WdB#DVX8xa8(K^Q z13v!Sfif7&>OU|5F_+F-D=iuLHzq%*JhjPb*E7AO@~A`(FHSk=BvA5{fk<;ZF8xQO zS+DQg{XPHG*2HE8+TdOT2e79Z)guQjr?r^NWBk?m?7#rsB%BW)ql$h7lZ{$AkcyEz zi_^nkj9lYoJCVv*RR&0oq{=Te>E+t!lYt7Si)Z>hzHiT0TqOPV{RLJuJ$47YHng>& zj+a`e1n1r<@6YLQ{$oX2H_Noz1ryitJgfIYZH(64XKZyd;p+Gid>Sn2vJ=R1e9#`Q zIh3;`I9T`BEjrTT`}xFs)G9{$7v2r`&b{InhIv&GNZ`L6iw`3&skBud`CTH6bn zv%~{aJXFqdvL$0;X?D0Z35cPb;rQt#qpFYu1D?Mx{7Y*%Z=L$y@<=ntNC)U;+i3D> z*Gb$?Z1?V{(}UGX3oEJ*`hSW!dV}Ad_I)&YkS5;}{L*K@tL=In!6x{r4E5vq4$ka6 zbUMzHOv8|*N2;$~1w`1MQd3%;Hmy5M9MSn1jpt6sd@ z=*Z8MZMtoH4~(d&g(nPhxM~<+SEKjA?w^qXg_;lJ;N`6(>syl8^UNKjhEx-gzPxVlivQ z$_(~*op8>b-ruw@e!SZ7TlFlrGpSFi`);#}hp>{5%uTD4#hEA-8|*tC>NvdCt82f_tBa;8*Wm*;KWCuA`?a2j z9(eE_1G{%oz(HXBZ#8s6aBcggn<@5`&-C+c*oexUr6_2bl{%=qyzgn2{(B-kwz!gQ ztM5$>xRr1<)ZCzPJL9$4^ZZkn-tA(7_i)lkEeXn8@Q zr^iV}=52?}CE0s^No|S_)}lhPA~c_|wV`dwpZS~Y>gGB=sZX=piEjm}`&Y=Y<$IT! z+Ps2?i0nOOlX~ZY3VwocG_mdOO}(yndu5b|gg_dYn*y^5_VBy<%Ia#&?6$brUYz>l zK-mB(Y+1G2B%&BnB~9E}98RkGA!aO%ghDy2;`(L2_nT7-JGtKQxu{R+)m}QiFLr(` zOTmSI!so1Piw56?$nxVgt+%}?#sx)4jeC%T9tG@G3X6!w%LIzl{8A~w-m|BTOMd_I z7j1GjF~d9)w?pn0NsPqD<;JmhvdmnA37z8?yQ+^bMqhS7a?C*aM(oT2c>M&9w&2Dnj=3<3x1}S;eqedo?O`VR96W{gAg5;E+1s%7Z>i@oTTjZ zO7S0PL4(2!)K~9jv(vo!MxlSiE%6!KPPWTbZQnfX)43kF%>#g4oXXVTW+Dz(qc8#A zr8iO+6C){_@M>tVVV@2!Icq!^_xynkczoX9UVOPTnjL*}JnW&Z0rRrfUpmdI+@R0I zGl@Q2?}!wXx!KC7VELK7R}vj53snvmgd0skBVYWTbD)zy4K&py2IOA*Kc~Q+FX=_C zB$Oq@51s0e!1YVCAyr!5l!w2hYS*w<>#gUY&d1j}bGCqty;w>f0~W(Mxz3JXA&{aGB4?#~{3%{kmzw22E!H7|b zBn}C|Ys1c^S1ht0XF}eIF&7`7=|LTi2L+tV=cRWem-4pF2$6g9&Ep>Qfx)(G=FTCj zJRi9k%Iw|~lV1CG?~;>L=9@RS_QX*NU3x>B4qjkD;AEt`c(y#oy4G=qYr}J)m?_bF7jPel zSYJ5x`+W+U;~kE}3&?1^Qbs+&_rry;xIwG<`L}q%ExdK#!Y66j4=_XN9L+!De|&$~ z+9z44XZHZ!N5DGC@(yZ9eRrejh=&bYQw^8D$oM5R@xoRVgySAcjx>O z`g}bvDd9w_iI5lstwS2!dK6K2gpGzMOK=g=Ji23L;mOl!cL{hDj#slCMs!1}pQG%u zQq6eGS0#Mmt1o);`PoetvjSAb78%U(?`E^XD+enWFvlYk!L>IiL>oCFii!>VNQ(R` z_@YZ^>)COZd|0bI-F@3ZX&|NCP<1sDzb20IY(To0SPFo7VXA_hitN#z>rY*$1m9_b zA$mVo6~=Ji)~&93Z+(*kI;K&vH=#^q$^dTwi4t4Gv}(N=?vWQ?)pm)Fzr9^OI(7T* zDB_0d#-7@<9q*|O;3T!z%H1#JdNhP7@Z=Xx1IWHQ`w0Rr!_0aTM+?8;A>8{Ji~^*! zu(60D7%W=4L!ME!JM8E^<;5A##fg?Da~GqFUXva^no(;bYq_uNNT|oqpI_KTm?3h4 z>x>K>zmPJ#RxB1ra*V5X{K|O1T=@vtik1O3@W8l<$Rj{eV!h^m{sDdW?9}b%VpQYwYquKC3fE zQ2Yg9!#OI9jDH8_hd3wBIdPz4!(3Sf&gO(K6T_s|Y5^p(cC`WmLOmaXawhJ(&lUn1Ze6D;3TxR3kRE!s!+>u`!7 zJ>_{_jR{cw9Hkc}(wNio$^Nwgd^KivWpUByLQgX=h;X8u5gC2sfrXl#O+X_8d3#uHl!7#5z0%Xo7S)ymHaKtCt0qOGu-sbMq z!Y};&E3(gB9Qx^zglPcFLg;X92jCy)`mH*I^z!Q-__(r|Q++Wp0z4j9xm`$Ljld6l zcBklGU!ocoT?cxzGT5V6Qct4N`9l`&)RTofr+j*zRF-c4xnbjM5tAWusxu1~W-w+! zz1l3^B073l?ej+VW&+D&t%LcvQ($q3EK~^6O1xJ4ogw@o0lCB|eK8ZIi46kFyuTL#ukY zu<7uDZ1L6elfebCaJS4@W!Fo_t0OT!RMAJ+&b)Gxyb7ccMGS41(FtD3)pS&DW5LWy zwA~>UpA1_6ll;U!LCP1xAX)I@NMNvZ@DW!BdoNaZj*RKBdd3KpM1KvvO6jqYK}M zH4!!mN>yU?_K=X)v1rT@YHpQldQKyZVlqRm)^!lk_pV%lqqDYloIFfHM$x4jDC_$L zL>HBgg%+ucY#=LY~ zR4)DA=WxyF9U>a$@b}n63UFk_K5}z=Q^Q~` z2ri5hd6CQW<;|ci0Kz3+77+;F_UN1tASY8FvxLK**i~V=PM~}n=|NOtIIh8noO{_p z-y-6jx9f_lTZ7lEl*5Fy2th9e9Vu2mQwp>!3j#UesYGfyxx#MY75fkTt~h^l&dvoD zX+(C4;cRkM8m_kolE@1bO|YXl3thh%;;+u|!h+nmNa zuXxe{=jJy07iRkd4hvF%k)dw+tBT02&quiOE&-S*thnSPZkRw`C?B7~iV5D*D&zN? zomr%0o^HFzWLWSEZJ^4~D!^KZ5C#^&U)7}1H@=Ipu7ON&b?2vqSziwmwBQtHOx+>A z-^vZCc-43vr5PDTDjB~38@0gYIPtNL&mvM+A=5CJHy$=sysM7O`lzPVl~7a;1Z16^ zPq)W=Lh=SceHwQ=U0*w7)E$eA(=8aT+92NYIpQ) z7lp#P(F`Oh4^6Z!XPF%LP53t+4q)Ff=X~qz)rgI5B~G{lqPo8`rw?;-QOA)qVGsyM zCr*N5ERrNek=u%KdlsXQ^#^PXlJFNE?Qp4@r#RMwse<5q_gxxIns z@?H!SHi?}2kh>mCMe99jeo{t~v<;7GS98F-i+-P^|E73D=g9CnMavo!)gcl|jZ^Ug zlppn-ETfXLq*QHaLxFAtZRdET`jX7-WR$Z}4=ZAXKzA1j+fm*s7GPuTN9EVxFc{jkyeSKz4vV za7JMSj!|j#n`Ia{To9o|&OM~VP?TAS2kdHwGM2=!c4* z(vK%>BDgX%E|pubnAr}-5KRW$n^`b?!>8QS(IJVjV+L~iKUm{eC%$vJiAdPZ~YU- zQlrM4(-+H1^`Wa!okt)ZS0YKq&wskJ+97LC2+-oOTcSm=3VJC@cvXw+Wf~%TTxD9WDh|QP6ue|~%XHg%OyvjNuS;}dA=Qx!? z{1?3OrbQ^Qj7>ax9}RqlUrass;cl?MyG`+i1Nj=HCfw{S2A{R~NP>H^SfhXzgX`l= zbKNQ{8;z7`r?<@{m_WYTdDlCu4O~nNw3&E^mjq;#__1z;Am;+zUJ(^E@^U((rMdSoXi4 zVM^Y?iGgw3ezO2efef$bf%71m5S0|~?+IL>umQw%sRAhU6>_;f#5hq=1*St5Y+JmG zEuF3Vu~s6l$&&BVtHUF!Yac|MVQvj#v$83;hrzXbWGgeuD~nTh8y>Q`n@aj)2LtBI z3{3|=w-zjymKZLa^r;F87t&&I&7nHr+4{$d;&zdw24~N}fP17;W31(vex?MKn$#r* zfvUs`>h)&2C~th6TXd>V*er|UDU(wh+yuVM2jvxSP!PB5kkWZ7yiNfZy?8htKbBfR zfH@ZSKQ)o-*1Q=mfC+qq(1#!C0RzPf*apI;nn9V*=leP>sl8V?6My4j|BU8>*QnN7 z355@}pM7FWs^G&flS`lH^DWL|w{&BAVR$(RH&9SAO?rl$u}}TK$d@DTRD(~s^Kyf> zMyy@Uc>uT?9dcR6yZmipc+xAO#9Eu32sYKs&P=q20`9+~?GYF)B z7Wt+e_3tL#*RP28iX>||%-Q7hPyvS0kB9@Pq;Ysfi*yV3$RAOqFtYRVaHXBiTYBi1 z`zfjMV}NE{%Y+F)DaFDKFm|zGGw4f&3yOG(%hsR`)(jRYN-TZr-DmeyXq#lp|**Vlx8 zru7pnI2k0;xKMc!*kbWd=#%q0f}=9-xlBIm1}W{feFE<;9pYOe-CC2ex;%rNbqVzf z7v(lHE?tj>NA3KQVcQ=I~g!~U|^t=w1E9s>_eQ3i6$dc%5*n@fv%lCB~-u*UxZ|(s}}@2`W)OF*?Vf* zt@J-F7-Af2582y--%8yVu1QhMaEr>8NEluroXX`02{lS4qfrV>Kxz&7*9tMn7hyuL;P$I-;`QJQ7BJe&CxPYuvCt

    F ze{c)TT}xG@xw^VW;d-_|Q%ShtK8IOcHc!vLhjMz+5=O6{5x)(Kp;(bGW9AlN079W9 zLHB~~YxK3%ppFF@J58!CX(XVk_Z8YUeQ~dB_trEwq%u=#w!2X3o=bfZqAq1?ecbJ~ zC&<38;;p_hS_qn;19N?EH!(LwQ6VuJaUN!K_ipbmda?&gr2C^%Bl+wxRM6reo-9h0 z1&cKjuvuWq`MKydw1sKEc@a4j?voRf?-)as&+uX2+^a-DI*R=yBIT`~Ia+<=-b839 zDwa@#Fq}^=0A5~Gv}x(3O+%~YC278yNR3Tpwc$4#-Z1VKY?hJ(j(oDN3Gu zn-5PkQwYgOa)RqGT*ll>*=08(lT8eh29HRW8`{uv0277eOIcO zNa)3*%9fN4E`+oLu^aaO8SkPxHbe5Q-lkEG;YTQinLyg6*!_6%X|ihJ5503em2T4$ddoH5ZbM<{LCbgs9)qz%T=#Uu@b@l`Fnh_RZF+QIPWpz33u~XJsZu- z@Hda9C39=;9P%_QO+w?ZyoVpeup^b%p4uiC53?-rizZ>Y01ixl2SzA%7b4R=qxO3p zQL%EZ5+;PtpkX$CBQjsP`AKLOV79=p&N97bxpc->hE#@X@j^g_|>p#>D z7&K}dP^)k#2ULZ;-jBB@|6+_8FghJ2X zh=XOnDG`u_5X^ZGi`PHD%Dh&awXi9Q-xm literal 0 HcmV?d00001 diff --git a/Olive/Nuget.png b/Olive/Nuget.png new file mode 100644 index 0000000000000000000000000000000000000000..aad9cc9a3d1066d0db30a9e9f5687fa140f5e054 GIT binary patch literal 4624 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 + + + netcoreapp2.0 + Olive + Olive + + + + ..\@Assemblies\ + ..\@Assemblies\netcoreapp2.0\Olive.xml + 1701;1702;1705;1591;1573 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Olive/Package.nuspec b/Olive/Package.nuspec new file mode 100644 index 000000000..736f9dad8 --- /dev/null +++ b/Olive/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive + 1.0.3 + Olive + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Olive/Security/Encryption.cs b/Olive/Security/Encryption.cs new file mode 100644 index 000000000..7226eb65b --- /dev/null +++ b/Olive/Security/Encryption.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace Olive +{ + public class Encryption + { + const int MAX_ENCRYPTION_PART_LENGTH = 117, SIXTEEN = 16, THIRTY_TWO = 32; + + ///

    + /// Generates a public/private key for asymmetric encryption. + /// + public static KeyValuePair GenerateAsymmetricKeys() + { + var rsa = CreateRSACryptoServiceProvider(); + + return new KeyValuePair(rsa.ToXmlString(includePrivateParameters: false), rsa.ToXmlString(includePrivateParameters: true)); + } + + static RSA CreateRSACryptoServiceProvider() => RSA.Create(); + + /// + /// Encrypts the specified text with the specified public key. + /// + /// The default padding value is OaepSHA512. + public static string EncryptAsymmetric(string text, string publicKeyXml, RSAEncryptionPadding padding = null) + { + if (text.IsEmpty()) + throw new ArgumentNullException(nameof(text)); + + if (publicKeyXml.IsEmpty()) + throw new ArgumentNullException(nameof(publicKeyXml)); + + if (text.Length > MAX_ENCRYPTION_PART_LENGTH) + { + return text.Split(MAX_ENCRYPTION_PART_LENGTH).Select(p => EncryptAsymmetric(p, publicKeyXml)).ToString("|"); + } + else + { + var rsa = CreateRSACryptoServiceProvider(); + + rsa.FromXmlString(publicKeyXml); + + return rsa.Encrypt(text.ToBytes(Encoding.UTF8), padding ?? RSAEncryptionPadding.OaepSHA512).ToBase64String(); + } + } + + /// + /// Decrypts the specified text with the specified public/private key pair. + /// + /// The default padding value is OaepSHA512. + public static string DecryptAsymmetric(string encodedText, string publicPrivateKeyXml, RSAEncryptionPadding padding = null) + { + if (encodedText.IsEmpty()) + throw new ArgumentNullException(nameof(encodedText)); + + if (publicPrivateKeyXml.IsEmpty()) + throw new ArgumentNullException(nameof(publicPrivateKeyXml)); + + if (encodedText.Contains("|")) + { + return encodedText.Split('|').Select(p => DecryptAsymmetric(p, publicPrivateKeyXml)).ToString(string.Empty); + } + else + { + var rsa = CreateRSACryptoServiceProvider(); + + rsa.FromXmlString(publicPrivateKeyXml); + + return rsa.Decrypt(encodedText.ToBytes(), padding ?? RSAEncryptionPadding.OaepSHA512).ToString(Encoding.UTF8); + } + } + + /// + /// Encrypts the specified text with the specified password. + /// + public static string Encrypt(string text, string password) + { + if (text.IsEmpty()) + throw new ArgumentNullException(nameof(text)); + + if (password.IsEmpty()) + throw new ArgumentNullException(nameof(password)); + + using (var key = new Rfc2898DeriveBytes(password, Encoding.ASCII.GetBytes(password.Length.ToString()))) + { + var aes = Aes.Create(); + aes.Padding = PaddingMode.PKCS7; + var encryptor = aes.CreateEncryptor(key.GetBytes(THIRTY_TWO), key.GetBytes(SIXTEEN)); + + var textData = Encoding.Unicode.GetBytes(text); + using (var encrypted = new MemoryStream()) + { + using (var cryptoStream = new CryptoStream(encrypted, encryptor, CryptoStreamMode.Write)) + { + cryptoStream.Write(textData, 0, textData.Length); + cryptoStream.FlushFinalBlock(); + + return Convert.ToBase64String(encrypted.ToArray()); + } + } + } + } + + /// + /// Decrypts the specified encrypted text with the specified password. + /// + public static string Decrypt(string encryptedText, string password) + { + using (var key = new Rfc2898DeriveBytes(password, Encoding.ASCII.GetBytes(password.Length.ToString()))) + { + var encryptedData = encryptedText.ToBytes(); + + using (var aes = Aes.Create()) + { + aes.Padding = PaddingMode.PKCS7; + + using (var decryptor = aes.CreateDecryptor(key.GetBytes(THIRTY_TWO), key.GetBytes(SIXTEEN))) + { + using (var memoryStream = new MemoryStream(encryptedData)) + { + using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) + { + // The size of decrypted data is unknown, so we allocate a buffer big enough to store encrypted data. + // decrypted data is always the same or smaller than encrypted data. + var plainText = new byte[encryptedData.Length]; + int decryptedSize = cryptoStream.Read(plainText, 0, plainText.Length); + + return Encoding.Unicode.GetString(plainText, 0, decryptedSize); + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Olive/Security/JsonExposedAttribute.cs b/Olive/Security/JsonExposedAttribute.cs new file mode 100644 index 000000000..0b7ac63b8 --- /dev/null +++ b/Olive/Security/JsonExposedAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace Olive +{ + /// Marks a property as Serializable (mainly for Json). + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class JsonExposedAttribute : Attribute { } +} \ No newline at end of file diff --git a/Olive/Security/PessimisticJsonConverter.cs b/Olive/Security/PessimisticJsonConverter.cs new file mode 100644 index 000000000..e1290e470 --- /dev/null +++ b/Olive/Security/PessimisticJsonConverter.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Olive +{ + /// + /// When serializing objects it ignores all properties unless they have [Exposed] attribute. + /// + public class PessimisticJsonConverter : JsonConverter + { + static BindingFlags Flags = BindingFlags.Instance | BindingFlags.Public; + + public override bool CanConvert(Type objectType) => true; + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteStartObject(); + + Action add = (member, val) => + { + writer.WritePropertyName(GetJsonName(member)); + serializer.Serialize(writer, val); + }; + + foreach (var property in value.GetType().GetProperties(Flags)) + if (property.Defines()) add(property, property.GetValue(value)); + + foreach (var property in value.GetType().GetFields(Flags)) + if (property.Defines()) add(property, property.GetValue(value)); + + writer.WriteEndObject(); + } + + static string GetJsonName(MemberInfo member) + { + var customName = member.GetCustomAttribute()?.PropertyName; + return customName.Or(member.Name); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var result = objectType.CreateInstance(); + + foreach (var jsonProperty in JObject.Load(reader).Properties().ToList()) + { + var objectProperty = objectType.GetProperties(Flags).FirstOrDefault(x => GetJsonName(x) == jsonProperty.Name); + if (objectProperty != null) + { + var castedValue = jsonProperty.Value.ToString().To(objectProperty.PropertyType); + objectProperty.SetValue(result, castedValue); + } + else + { + var objectField = objectType.GetFields(Flags).FirstOrDefault(x => GetJsonName(x) == jsonProperty.Name); + if (objectField != null) + { + var castedValue = jsonProperty.Value.ToString().To(objectField.FieldType); + objectField?.SetValue(result, castedValue); + } + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/Olive/Security/SecurePassword.cs b/Olive/Security/SecurePassword.cs new file mode 100644 index 000000000..17d1ebe1f --- /dev/null +++ b/Olive/Security/SecurePassword.cs @@ -0,0 +1,71 @@ +namespace Olive +{ + using System; + using System.Security.Cryptography; + + /// + /// Provides secure password hashing service based on PBKDF2. + /// + public class SecurePassword + { + // The following constants may be changed without breaking existing hashes. + const int SALT_BYTE_SIZE = 64, HASH_BYTE_SIZE = 64; + static int PBKDF2_ITERATIONS = Config.Get("Secure.Password.Pbkdf2.Iterations", defaultValue: 10000); + + public string Password { get; set; } + public string Salt { get; set; } + + /// + /// Creates a salted PBKDF2 hash of the password. + /// + public static SecurePassword Create(string password) + { + // Generate a random salt + using (var csprng = RandomNumberGenerator.Create()) + { + var salt = new byte[SALT_BYTE_SIZE]; + csprng.GetBytes(salt); + // Hash the password and encode the parameters + var hashBytes = GetBytes(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE); + + return new SecurePassword + { + Password = Convert.ToBase64String(hashBytes), + Salt = Convert.ToBase64String(salt) + }; + } + } + + /// + /// Validates a password given a hash of the correct one. + /// + public static bool Verify(string clearTextPassword, string hashedPassword, string salt) + { + if (clearTextPassword.IsEmpty()) return false; + if (hashedPassword.IsEmpty()) return false; + if (salt.IsEmpty()) return false; + + var pass = new SecurePassword { Password = hashedPassword, Salt = salt }; + + var hashedBytes = pass.GetHashBytes(); + + var testHash = GetBytes(clearTextPassword, pass.GetSaltBytes(), PBKDF2_ITERATIONS, hashedBytes.Length); + return SlowEquals(hashedBytes, testHash); + } + + static bool SlowEquals(byte[] leftBytes, byte[] rightBytes) + { + var diff = (uint)leftBytes.Length ^ (uint)rightBytes.Length; + for (var i = 0; i < leftBytes.Length && i < rightBytes.Length; i++) + diff |= (uint)(leftBytes[i] ^ rightBytes[i]); + return diff == 0; + } + + static byte[] GetBytes(string password, byte[] salt, int iterations, int outputBytes) => + new Rfc2898DeriveBytes(password, salt, iterations).GetBytes(outputBytes); + + byte[] GetHashBytes() => Convert.FromBase64String(Password); + + byte[] GetSaltBytes() => Convert.FromBase64String(Salt); + } +} \ No newline at end of file diff --git a/Olive/TPL/AsyncEvent/AsyncEvent.Handler.cs b/Olive/TPL/AsyncEvent/AsyncEvent.Handler.cs new file mode 100644 index 000000000..a204cc2f9 --- /dev/null +++ b/Olive/TPL/AsyncEvent/AsyncEvent.Handler.cs @@ -0,0 +1,132 @@ +namespace Olive +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Threading.Tasks; + + public interface IAsyncEventHandler : IDisposable { object Action { get; } } + + /// + /// Provides a mechanism to prevent event handler dependency memory leaks. + /// + /// + public class EventHandlerDisposer + { + object SyncLock = new object(); + List> Dependencies = new List>(); + + /// + /// Will dispose all registered event handlers and clear them from the list. + /// + public void DisposeAll() + { + lock (SyncLock) + { + foreach (var c in Dependencies.ToArray()) + { + c.GetTargetOrDefault()?.Dispose(); + c.SetTarget(null); + Dependencies.Remove(c); + } + } + } + + public void Register(IAsyncEventHandler handler) + { + if (handler == null) return; + + lock (SyncLock) + Dependencies.Add(handler.GetWeakReference()); + } + } + + public abstract class AsyncEventHandler : IDisposable, IEquatable + { + internal string Caller; + protected bool IsDisposed; + + [DebuggerStepThrough] + protected Task Raise(Func raiser) + { + if (IsDisposed) return Task.CompletedTask; + + if (raiser == null) return Task.CompletedTask; + return raiser.Invoke(); + } + + [DebuggerStepThrough] + protected Task Raise(Action action) + { + if (IsDisposed) return Task.CompletedTask; + + action?.Invoke(); + + return Task.CompletedTask; + } + + public abstract bool Equals(AsyncEventHandler other); + + public abstract void Dispose(); + } + + public abstract class AsyncEventHandler : AsyncEventHandler, IAsyncEventHandler + where TActionFunction : class + { + internal TActionFunction Action; + // WeakReference EventRef; + + internal AbstractAsyncEvent Event { get; set; } + + // internal AbstractAsyncEvent Event + // { + // get => EventRef?.GetTargetOrDefault(); + // set => value?.GetWeakReference(); + // } + + object IAsyncEventHandler.Action => Action; + + public override bool Equals(AsyncEventHandler other) + { + return Action == (other as AsyncEventHandler)?.Action; + } + + public override void Dispose() + { + IsDisposed = true; + + Event?.RemoveHandler(this); + // EventRef = null; + Event = null; + Action = null; + Caller = null; + } + } + + public class AsyncEventActionHandler : AsyncEventHandler + { + [DebuggerStepThrough] + internal Task Raise() => Raise(Action); + } + + public class AsyncEventTaskHandler : AsyncEventHandler> + { + [DebuggerStepThrough] + internal Task Raise() => Raise(RaiseIt); + + [DebuggerStepThrough] + Task RaiseIt() => Action?.Invoke() ?? Task.CompletedTask; + } + + public class AsyncEventActionHandler : AsyncEventHandler> + { + [DebuggerStepThrough] + internal Task Raise(T arg) => Raise(() => Action?.Invoke(arg)); + } + + public class AsyncEventTaskHandler : AsyncEventHandler> + { + [DebuggerStepThrough] + internal Task Raise(T arg) => Raise(() => Action?.Invoke(arg) ?? Task.CompletedTask); + } +} \ No newline at end of file diff --git a/Olive/TPL/AsyncEvent/AsyncEvent.Raise.cs b/Olive/TPL/AsyncEvent/AsyncEvent.Raise.cs new file mode 100644 index 000000000..32f052e31 --- /dev/null +++ b/Olive/TPL/AsyncEvent/AsyncEvent.Raise.cs @@ -0,0 +1,82 @@ +namespace Olive +{ + using System; + using System.Diagnostics; + using System.Linq; + using System.Runtime.CompilerServices; + using System.Threading.Tasks; + + partial class AbstractAsyncEvent + { + AsyncLock RaisingLock; + bool IsRaising; + + /// + /// Determines how concurrent attempts to raise an event should be handled. + /// + protected ConcurrentEventRaisePolicy ConcurrentRaisePolicy = ConcurrentEventRaisePolicy.Parallel; + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected async Task Raise(Func raiser, bool inParallel) + { + if (Handlers.None()) return; + + var handlers = Handlers.ToArray(); + + await Raise(handlers, raiser, inParallel); + } + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + Task Raise(AsyncEventHandler[] handlers, Func raiser, bool inParallel) + { + switch (ConcurrentRaisePolicy) + { + case ConcurrentEventRaisePolicy.Parallel: return RaiseOnce(handlers, raiser, inParallel); + case ConcurrentEventRaisePolicy.Ignore: return RaiseWithIgnorePolicy(handlers, raiser, inParallel); + case ConcurrentEventRaisePolicy.Queue: return RaiseWithQueuePolicy(handlers, raiser, inParallel); + default: throw new NotImplementedException(); + } + } + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + async Task RaiseWithIgnorePolicy(AsyncEventHandler[] handlers, Func raiser, bool inParallel) + { + if (IsRaising) return; + + IsRaising = true; + try { await RaiseOnce(handlers, raiser, inParallel); } + finally { IsRaising = false; } + } + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + async Task RaiseWithQueuePolicy(AsyncEventHandler[] handlers, Func raiser, bool inParallel) + { + if (RaisingLock == null) RaisingLock = new AsyncLock(); + + using (await RaisingLock.Lock()) await RaiseOnce(handlers, raiser, inParallel); + } + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + async Task RaiseOnce(AsyncEventHandler[] handlers, Func raiser, bool inParallel) + { + try + { + if (inParallel) + { + await Task.WhenAll(handlers.Select(x => + { + if (!IsDisposing) return raiser(x); + else return Task.CompletedTask; + })); + } + else foreach (var h in handlers) + if (!IsDisposing) await raiser(h); + } + catch (Exception ex) + { + Debug.WriteLine($"Raising the event {DeclaringType}.{EventName} failed: " + ex.ToLogString()); + throw; + } + } + } +} diff --git a/Olive/TPL/AsyncEvent/AsyncEvent.TArg.cs b/Olive/TPL/AsyncEvent/AsyncEvent.TArg.cs new file mode 100644 index 000000000..b9eecb11e --- /dev/null +++ b/Olive/TPL/AsyncEvent/AsyncEvent.TArg.cs @@ -0,0 +1,99 @@ +namespace Olive +{ + using System; + using System.Diagnostics; + using System.Runtime.CompilerServices; + using System.Threading.Tasks; + + public class AsyncEvent : AbstractAsyncEvent + { + public AsyncEvent([CallerMemberName] string eventName = "", [CallerFilePath] string declaringFile = "") + : base(eventName, declaringFile) { } + + public AsyncEvent(ConcurrentEventRaisePolicy raisePolicy, [CallerMemberName] string eventName = "", [CallerFilePath] string declaringFile = "") : base(eventName, declaringFile) + { + ConcurrentRaisePolicy = raisePolicy; + } + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + public AsyncEvent Handle(Func handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int line = 0) + { + return DoHandleOn>(handler, null, callerFile, line); + } + + /// + /// The same as Handle. It's added to get past the strange bug in C# for selecting the correct overload of Handle(). + /// + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + public AsyncEvent HandleWith(Action handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int line = 0) + { + return DoHandleOn(null, handler, callerFile, line); + } + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + public AsyncEvent Handle(Action handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int line = 0) + { + return DoHandleOn(null, handler, callerFile, line); + } + + protected AsyncEvent DoHandleOn(Func handlerTask, Action handlerAction, + string callerFile, int line) + { + return DoHandleOn>(handlerTask, handlerAction, callerFile, line); + } + + [DebuggerStepThrough] + public IAsyncEventHandler CreateHandler(Func handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int line = 0) + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + lock (Handlers) + { + RemoveHandler(handler); + + var result = new AsyncEventTaskHandler + { + Action = handler, + Event = this, + Caller = Debugger.IsAttached ? $"{callerFile}:{line}" : string.Empty + }; + + Handlers.Add(result); + return result; + } + } + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + Task RaiseHandler(AsyncEventHandler handler, TArg arg) + { + if (handler is AsyncEventActionHandler h) return h.Raise(); + else if (handler is AsyncEventTaskHandler t) return t.Raise(); + else if (handler is AsyncEventActionHandler ha) return ha.Raise(arg); + else if (handler is AsyncEventTaskHandler aa) return aa.Raise(arg); + else return Task.CompletedTask; + } + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task Raise(TArg arg) => Raise(arg, inParallel: false); + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task Raise(TArg arg, bool inParallel) => Raise(h => RaiseHandler(h, arg), inParallel); + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + public AsyncEvent RemoveHandler(Action handler) => this.DoRemoveHandler(handler); + + /// + /// The same as RemoveHandler. + /// It's added to get past the strange bug in C# for selecting the correct overload of RemoveHandler(). + /// + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + public AsyncEvent RemoveActionHandler(Action handler) => this.DoRemoveHandler(handler); + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + public AsyncEvent RemoveHandler(Func handler) => this.DoRemoveHandler(handler); + } +} \ No newline at end of file diff --git a/Olive/TPL/AsyncEvent/AsyncEvent.cs b/Olive/TPL/AsyncEvent/AsyncEvent.cs new file mode 100644 index 000000000..c12843c2c --- /dev/null +++ b/Olive/TPL/AsyncEvent/AsyncEvent.cs @@ -0,0 +1,30 @@ +namespace Olive +{ + using System.Diagnostics; + using System.Runtime.CompilerServices; + using System.Threading.Tasks; + + public class AsyncEvent : AbstractAsyncEvent + { + public AsyncEvent([CallerMemberName] string eventName = "", [CallerFilePath] string declaringFile = "") : base(eventName, declaringFile) { } + + public AsyncEvent(ConcurrentEventRaisePolicy raisePolicy, [CallerMemberName] string eventName = "", [CallerFilePath] string declaringFile = "") : base(eventName, declaringFile) + { + ConcurrentRaisePolicy = raisePolicy; + } + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task RaiseHandler(AsyncEventHandler handler) + { + if (handler is AsyncEventActionHandler h) return h.Raise(); + else if (handler is AsyncEventTaskHandler t) return t.Raise(); + else return Task.CompletedTask; + } + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task Raise() => Raise(inParallel: false); + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + public Task Raise(bool inParallel) => Raise(RaiseHandler, inParallel); + } +} \ No newline at end of file diff --git a/Olive/TPL/AsyncEvent/AsyncEventExtensions.cs b/Olive/TPL/AsyncEvent/AsyncEventExtensions.cs new file mode 100644 index 000000000..e79da7654 --- /dev/null +++ b/Olive/TPL/AsyncEvent/AsyncEventExtensions.cs @@ -0,0 +1,190 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace Olive +{ + public static class AsyncEventExtensions + { + /// + /// The same as RemoveHandler. + /// It's added to get past the strange bug in C# for selecting the correct overload of RemoveHandler(). + /// + public static TEvent RemoveActionHandler(this TEvent @event, Action handler) + where TEvent : AbstractAsyncEvent + { + return @event.DoRemoveHandler(handler); + } + + [DebuggerStepThrough] + public static TEvent RemoveActionHandler(this TEvent @event, Action handler) + where TEvent : AbstractAsyncEvent + { + return @event.DoRemoveHandler(handler); + } + + [DebuggerStepThrough] + public static TEvent RemoveActionHandler(this TEvent @event, Action handler) + where TEvent : AbstractAsyncEvent + { + return @event.DoRemoveHandler(handler); + } + + [DebuggerStepThrough] + public static TEvent RemoveHandler(this TEvent @event, Func handler) + where TEvent : AbstractAsyncEvent + { + return @event.DoRemoveHandler(handler); + } + + [DebuggerStepThrough] + public static TEvent RemoveHandler(this TEvent @event, Func handler) + where TEvent : AbstractAsyncEvent + { + return @event.DoRemoveHandler(handler); + } + + [DebuggerStepThrough] + public static TEvent RemoveHandler(this TEvent @event, Func handler) + where TEvent : AbstractAsyncEvent + { + return @event.DoRemoveHandler(handler); + } + + [DebuggerStepThrough] + internal static TEvent DoRemoveHandler(this TEvent @event, object handlerFunction) + where TEvent : AbstractAsyncEvent + { + lock (@event.Handlers) + { + var itemsToRemove = @event.Handlers.Where(x => ((IAsyncEventHandler)x).Action == handlerFunction).ToArray(); + itemsToRemove.Do(x => x.Dispose()); + @event.Handlers.Remove(itemsToRemove); + } + + return @event; + } + + /// + /// The same as Handle. It's added to get past the strange bug in C# for selecting the correct overload of Handle(). + /// + public static TEvent HandleWith(this TEvent @event, Action handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int callerLine = 0) + where TEvent : AbstractAsyncEvent + { + return Handle(@event, handler, callerFile, callerLine); + } + + public static TEvent Handle(this TEvent @event, Action handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int callerLine = 0) + where TEvent : AbstractAsyncEvent + { + return HandleOn(@event, handler, callerFile, callerLine); + } + + public static TEvent Handle(this TEvent @event, Func handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int callerLine = 0) + where TEvent : AbstractAsyncEvent + { + return HandleOn(@event, handler, callerFile, callerLine); + } + + /// + /// The same as HandleOn. It's added to get past the strange bug in C# for selecting the correct overload of HandleOn(). + /// + public static TEvent HandleActionOn(this TEvent @event, Action handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int line = 0) + where TEvent : AbstractAsyncEvent + { + return HandleOn(@event, handler, callerFile, line); + } + + public static TEvent HandleOn(this TEvent @event, Action handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int line = 0) + where TEvent : AbstractAsyncEvent + { + if (handler == null) return @event; + + lock (@event.Handlers) + @event.Handlers.AddUnique(new AsyncEventActionHandler + { + Action = handler, + Event = @event, + Caller = Debugger.IsAttached ? $"{callerFile}:{line}" : string.Empty + }); + + return @event; + } + + /// + /// Creates an event handler which you can dispose of explicitly if required. + /// + public static IAsyncEventHandler CreateActionHandler(this TEvent @event, Action handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int line = 0) + where TEvent : AbstractAsyncEvent + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + lock (@event.Handlers) + { + @event.RemoveActionHandler(handler); + + var result = new AsyncEventActionHandler + { + Action = handler, + Event = @event, + Caller = Debugger.IsAttached ? $"{callerFile}:{line}" : string.Empty + }; + + @event.Handlers.Add(result); + return result; + } + } + + /// + /// Creates an event handler which you can dispose of explicitly if required. + /// + public static IAsyncEventHandler CreateHandler(this TEvent @event, Func handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int line = 0) + where TEvent : AbstractAsyncEvent + { + if (handler == null) throw new ArgumentNullException(nameof(handler)); + + lock (@event.Handlers) + { + @event.RemoveHandler(handler); + + var result = new AsyncEventTaskHandler + { + Action = handler, + Event = @event, + Caller = Debugger.IsAttached ? $"{callerFile}:{line}" : string.Empty + }; + + @event.Handlers.Add(result); + return result; + } + } + + public static TEvent HandleOn(this TEvent @event, Func handler, + [CallerFilePath] string callerFile = null, [CallerLineNumber] int line = 0) + where TEvent : AbstractAsyncEvent + { + if (handler == null) return @event; + + lock (@event.Handlers) + { + @event.Handlers.AddUnique(new AsyncEventTaskHandler + { + Action = handler, + Event = @event, + Caller = Debugger.IsAttached ? $"{callerFile}:{line}" : string.Empty + }); + } + + return @event; + } + } +} \ No newline at end of file diff --git a/Olive/TPL/AsyncEvent/AsyncEventHandlingException.cs b/Olive/TPL/AsyncEvent/AsyncEventHandlingException.cs new file mode 100644 index 000000000..576e61b13 --- /dev/null +++ b/Olive/TPL/AsyncEvent/AsyncEventHandlingException.cs @@ -0,0 +1,9 @@ +namespace Olive +{ + using System; + + public class AsyncEventHandlingException : Exception + { + public AsyncEventHandlingException(string message, Exception innerException) : base(message, innerException) { } + } +} \ No newline at end of file diff --git a/Olive/TPL/AsyncEvent/BaseAsyncEvent.cs b/Olive/TPL/AsyncEvent/BaseAsyncEvent.cs new file mode 100644 index 000000000..35226a1d9 --- /dev/null +++ b/Olive/TPL/AsyncEvent/BaseAsyncEvent.cs @@ -0,0 +1,107 @@ +namespace Olive +{ + using System; + using System.Diagnostics; + using System.Linq; + using System.Runtime.CompilerServices; + using System.Threading.Tasks; + + public interface IAsyncEvent { bool IsHandled(); } + + public abstract partial class AbstractAsyncEvent : IAsyncEvent, IDisposable + { + public TimeSpan? Timeout { get; set; } + + protected WeakReference OwnerReference; + protected string DeclaringFile, EventName; + protected bool IsDisposing; + + internal protected ConcurrentList Handlers = new ConcurrentList(); + + protected AbstractAsyncEvent(string eventName, string declaringFile) + { + if (Debugger.IsAttached) + { + EventName = eventName; + DeclaringFile = declaringFile; + } + } + + public int HandlersCount => Handlers.Count; + + public void SetOwner(object owner) => OwnerReference = owner.GetWeakReference(); + + public object Owner => OwnerReference.GetTargetOrDefault(); + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + internal bool HasHandler(object handlerFunction) + { + return Handlers.Any(x => ReferenceEquals(((IAsyncEventHandler)x).Action, handlerFunction)); + } + + public bool IsHandled() => Handlers.Any(); + + protected string DeclaringType => DeclaringFile.OrEmpty().Split('\\').LastOrDefault().TrimEnd(".cs", caseSensitive: false).Split('.').FirstOrDefault(); + + public override string ToString() => DeclaringType + "." + EventName + " ◀ " + Owner; + + internal void RemoveHandler(AsyncEventHandler handler) + { + lock (Handlers) Handlers.Remove(handler); + } + + /// Removes all current handlers from this event. + public void ClearHandlers() => Handlers = new ConcurrentList(); + + /// + /// Returns a tasks that completes once as soon as this event is fired. + /// + public Task AwaitRaiseCompletion() + { + var completionTask = new TaskCompletionSource(); + + void waiter() + { + completionTask.TrySetResult(result: true); + this.RemoveActionHandler(waiter); + } + + this.HandleWith(waiter); + return completionTask.Task; + } + + [DebuggerStepThrough, MethodImpl(MethodImplOptions.AggressiveInlining)] + protected TReturn DoHandleOn(Func handlerTask, + Action handlerAction, string callerFile, int line) + where TReturn : AbstractAsyncEvent + { + if (handlerTask == null && handlerAction == null) return (TReturn)this; + + var caller = Debugger.IsAttached ? $"{callerFile}:{line}" : string.Empty; + + if (handlerTask != null && !HasHandler(handlerTask)) + Handlers.Add(new AsyncEventTaskHandler + { + Action = handlerTask, + Event = this, + Caller = caller + }); + + if (handlerAction != null && !HasHandler(handlerTask)) + Handlers.Add(new AsyncEventActionHandler + { + Action = handlerAction, + Event = this, + Caller = caller + }); + + return (TReturn)this; + } + + public void Dispose() + { + IsDisposing = true; + ClearHandlers(); + } + } +} \ No newline at end of file diff --git a/Olive/TPL/AsyncEvent/ConcurrentEventRaisePolicy.cs b/Olive/TPL/AsyncEvent/ConcurrentEventRaisePolicy.cs new file mode 100644 index 000000000..2c273c783 --- /dev/null +++ b/Olive/TPL/AsyncEvent/ConcurrentEventRaisePolicy.cs @@ -0,0 +1,23 @@ +namespace Olive +{ + /// + /// Determines how concurrent attempts to raise an event should be handled. + /// + public enum ConcurrentEventRaisePolicy + { + /// + /// A new concurrent attempt to raise this event should be ignored while the previous raise is still running. + /// + Ignore, + + /// + /// A new concurrent attempt to raise this event should be queued to run after the previous raise is still running. + /// + Queue, + + /// + /// A new concurrent attempt to raise this event should run immediately irrespective of any unfinished previous raise. + /// + Parallel + } +} diff --git a/Olive/TPL/AsyncLock/AsyncLock.cs b/Olive/TPL/AsyncLock/AsyncLock.cs new file mode 100644 index 000000000..3ed20854e --- /dev/null +++ b/Olive/TPL/AsyncLock/AsyncLock.cs @@ -0,0 +1,54 @@ +namespace Olive +{ + using System; + using System.Threading.Tasks; + + /// A recursive mutual exclusion lock that to use with async code. + public sealed class AsyncLock + { + bool IsTaken; + readonly AsyncWaitQueue Queue = new AsyncWaitQueue(); + readonly Task WaitTask; + readonly object Mutex = new object(); + + public AsyncLock() + { + WaitTask = Task.FromResult(new Key(this)); + } + + public DisposableAwaitable Lock() + { + lock (Mutex) + { + if (IsTaken) return new DisposableAwaitable(Queue.Enqueue()); + else + { + IsTaken = true; + return new DisposableAwaitable(WaitTask); + } + } + } + + void Release() + { + IDisposable toRelease = null; + + lock (Mutex) + { + if (Queue.Count == 0) IsTaken = false; + else toRelease = Queue.Dequeue(WaitTask.Result); + } + + toRelease?.Dispose(); + } + + sealed class Key : IDisposable + { + readonly AsyncLock Lock; + + public Key(AsyncLock asyncLock) { Lock = asyncLock; } + + public void Dispose() => Lock.Release(); + } + } +} diff --git a/Olive/TPL/AsyncLock/AsyncLockDeque.cs b/Olive/TPL/AsyncLock/AsyncLockDeque.cs new file mode 100644 index 000000000..e90910449 --- /dev/null +++ b/Olive/TPL/AsyncLock/AsyncLockDeque.cs @@ -0,0 +1,64 @@ +namespace Olive +{ + using System; + using System.Collections; + using System.Collections.Generic; + + internal sealed class AsyncLockDeque : IEnumerable + { + T[] Buffer; + int Offset; + + public AsyncLockDeque(int capacity = 5) + { + Buffer = new T[capacity]; + } + + int Capacity => Buffer.Length; + + public int Count { get; private set; } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerator GetEnumerator() + { + int count = Count; + for (int i = 0; i != count; ++i) + yield return Buffer[(i + Offset) % Capacity]; + } + + void GrowBy(int items) + { + var newBuffer = new T[Capacity + items]; + + if (Offset > (Capacity - Count)) + { + int length = Capacity - Offset; + Array.Copy(Buffer, Offset, newBuffer, 0, length); + Array.Copy(Buffer, 0, newBuffer, length, Count - length); + } + else Array.Copy(Buffer, Offset, newBuffer, 0, Count); + + Buffer = newBuffer; + Offset = 0; + } + + public void Enqueue(T value) + { + if (Count == Capacity) GrowBy(5); + + Buffer[(Count + Offset) % Capacity] = value; + ++Count; + } + + public T Deque() + { + --Count; + + var index = Offset; + Offset = (Offset + 1) % Capacity; + + return Buffer[index]; + } + } +} \ No newline at end of file diff --git a/Olive/TPL/AsyncLock/AsyncWaitQueue.cs b/Olive/TPL/AsyncLock/AsyncWaitQueue.cs new file mode 100644 index 000000000..c93b33690 --- /dev/null +++ b/Olive/TPL/AsyncLock/AsyncWaitQueue.cs @@ -0,0 +1,45 @@ +namespace Olive +{ + using System; + using System.Threading.Tasks; + + internal sealed class AsyncWaitQueue + { + readonly AsyncLockDeque> Queue = new AsyncLockDeque>(); + + public int Count { get { lock (Queue) return Queue.Count; } } + + public Task Enqueue() + { + var source = new TaskCompletionSource(); + lock (Queue) Queue.Enqueue(source); + return source.Task; + } + + public IDisposable Dequeue(T result) + { + lock (Queue) return new CompleteDisposable(result, Queue.Deque()); + } + + sealed class CompleteDisposable : IDisposable + { + readonly TaskCompletionSource[] Sources; + readonly T Result; + + public CompleteDisposable(T result, params TaskCompletionSource[] taskCompletionSources) + { + Result = result; + Sources = taskCompletionSources; + } + + public void Dispose() + { + foreach (var s in Sources) + { + Task.Run(() => s.TrySetResult(Result)); + s.Task.Wait(); + } + } + } + } +} diff --git a/Olive/TPL/AsyncLock/DisposableAwaitable.cs b/Olive/TPL/AsyncLock/DisposableAwaitable.cs new file mode 100644 index 000000000..b0c7a8b21 --- /dev/null +++ b/Olive/TPL/AsyncLock/DisposableAwaitable.cs @@ -0,0 +1,15 @@ +namespace Olive +{ + using System; + using System.Runtime.CompilerServices; + using System.Threading.Tasks; + + public struct DisposableAwaitable where T : IDisposable + { + public readonly Task Task; + + internal DisposableAwaitable(Task task) { Task = task; } + + public TaskAwaiter GetAwaiter() => Task.GetAwaiter(); + } +} \ No newline at end of file diff --git a/Olive/TPL/CallContext.cs b/Olive/TPL/CallContext.cs new file mode 100644 index 000000000..873f68d48 --- /dev/null +++ b/Olive/TPL/CallContext.cs @@ -0,0 +1,27 @@ +namespace Olive +{ + using System.Collections.Concurrent; + using System.Threading; + + public static class CallContext + { + static ConcurrentDictionary> state = new ConcurrentDictionary>(); + + /// + /// Stores a given object and associates it with the specified name. + /// + /// The name with which to associate the new item in the call context. + /// The object to store in the call context. + public static void SetData(string name, T data) => + state.GetOrAdd(name, _ => new AsyncLocal()).Value = data; + + /// + /// Retrieves an object with the specified name from the current call context/>. + /// + /// The type of the data being retrieved. Must match the type used when the was set via . + /// The name of the item in the call context. + /// The object in the call context associated with the specified name, or a default value for if none is found. + public static T GetData(string name) => + state.TryGetValue(name, out AsyncLocal data) ? data.Value : default(T); + } +} \ No newline at end of file diff --git a/Olive/TPL/ConcurrentList.cs b/Olive/TPL/ConcurrentList.cs new file mode 100644 index 000000000..e63778057 --- /dev/null +++ b/Olive/TPL/ConcurrentList.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Olive +{ + public class ConcurrentList : IList + { + readonly List List; + readonly object Lock = new object(); + bool IsVirgin = true; // For performance + T FirstItem; + + public ConcurrentList() { List = new List(); } + + public ConcurrentList(int capacity) + { + Lock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); + List = new List(capacity); + } + + public ConcurrentList(IEnumerable items) + { + List = new List(items); + IsVirgin = false; + } + + public override string ToString() => "ConcurrentList<" + typeof(T).GetProgrammingName() + "> [" + Count + "]"; + + public void Add(T item) + { + lock (Lock) + { + IsVirgin = false; + if (IsVirgin) FirstItem = item; + List.Add(item); + } + } + + /// + /// Adds an object only if it doesn't already exist in the list. + /// + public void AddUnique(T item) + { + lock (Lock) + { + if (List.Contains(item)) return; + IsVirgin = false; + if (IsVirgin) FirstItem = item; + List.Add(item); + } + } + + public void Insert(int index, T item) + { + lock (Lock) + { + IsVirgin = false; + if (index == 0) FirstItem = item; + List.Insert(index, item); + } + } + + public bool Remove(T item) + { + lock (Lock) + { + var result = List.Remove(item); + FirstItem = List.FirstOrDefault(); + return result; + } + } + + public void RemoveAt(int index) + { + lock (Lock) + { + List.RemoveAt(index); + if (index == 0) FirstItem = List.FirstOrDefault(); + } + } + + public int IndexOf(T item) + { + lock (Lock) + { + if (IsVirgin) return -1; + return List.IndexOf(item); + } + } + + public void Clear() + { + lock (Lock) + { + if (IsVirgin) return; + List.Clear(); + FirstItem = default(T); + } + } + + public bool Contains(T item) + { + lock (Lock) + { + if (IsVirgin) return false; + return List.Contains(item); + } + } + + public void CopyTo(T[] array, int arrayIndex) + { + lock (Lock) + { + if (IsVirgin) return; + List.CopyTo(array, arrayIndex); + } + } + + public IEnumerator GetEnumerator() + { + T[] copy; + + lock (Lock) + { + if (IsVirgin) return Enumerable.Empty().GetEnumerator(); + copy = List.ToArray(); + } + + return ((IEnumerable)copy).GetEnumerator(); + } + + public bool Any() + { + lock (Lock) + { + if (IsVirgin) return false; + return List.Any(); + } + } + + public IEnumerable OfType() where TType : T + { + lock (Lock) + { + if (IsVirgin) return Enumerable.Empty(); + return List.OfType(); + } + } + + public bool AnyOfType() where TType : T + { + lock (Lock) + { + if (IsVirgin) return false; + return List.Any(x => x is TType); + } + } + + public bool AnyOfType(Func criteria) where TType : T + { + lock (Lock) + { + if (IsVirgin) return false; + return List.Any(x => x is TType t && criteria(t)); + } + } + + public bool Any(Func criteria) + { + lock (Lock) + { + if (IsVirgin) return false; + return List.Any(criteria); + } + } + + public bool None() + { + lock (Lock) + { + if (IsVirgin) return true; + return List.None(); + } + } + + public T FirstOrDefault() + { + lock (Lock) return FirstItem; + } + + public T FirstOrDefault(Func criteria) + { + lock (Lock) + { + if (IsVirgin) return default(T); + if (FirstItem is T && criteria(FirstItem)) return FirstItem; + return List.FirstOrDefault(criteria); + } + } + + public TType FirstOrDefaultOfType() where TType : T + { + lock (Lock) + { + if (IsVirgin) return default(TType); + if (FirstItem is TType t) return t; + return List.OfType().FirstOrDefault(); + } + } + + public TType FirstOrDefaultOfType(Func criteria) where TType : T + { + lock (Lock) + { + if (IsVirgin) return default(TType); + if (FirstItem is TType t && criteria(t)) return t; + return List.OfType().FirstOrDefault(criteria); + } + } + + public T LastOrDefault() + { + lock (Lock) + { + if (IsVirgin) return default(T); + var index = List.Count - 1; + if (index == -1) return default(T); + return List[index]; + } + } + + public T LastOrDefault(Func criteria = null) + { + lock (Lock) + { + if (IsVirgin) return default(T); + + for (var i = List.Count - 1; i >= 0; i--) + { + var item = List[i]; + if (criteria(item)) return item; + } + + return default(T); + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public T this[int index] + { + get + { + lock (Lock) return List[index]; + } + set + { + lock (Lock) List[index] = value; + } + } + + public int Count + { + get + { + lock (Lock) + { + if (IsVirgin) return 0; + return List.Count; + } + } + } + + public bool IsReadOnly => false; + } +} diff --git a/Olive/Utilities/Base32Integer.cs b/Olive/Utilities/Base32Integer.cs new file mode 100644 index 000000000..92b715976 --- /dev/null +++ b/Olive/Utilities/Base32Integer.cs @@ -0,0 +1,65 @@ +namespace Olive +{ + using System; + using System.Text; + + public sealed class Base32Integer + { + // the valid chars for the encoding + static string ValidChars = "1AZ2WSX3" + "EDC4RFV5" + "TGB6YHN7" + "UJM8K9LP"; + + public int Value { get; } + + /// + /// Creates a new Base32Integer instance. + /// + public Base32Integer(int value) => Value = value; + + /// + /// Creates a new Base32Integer instance. + /// + public Base32Integer(string base32Integer) => Value = FromBase32String(base32Integer); + + public override string ToString() => ToBase32String(Value); + + /// + /// Converts an array of bytes to a Base32-k string. + /// + public static string ToBase32String(int value) + { + var r = new StringBuilder(); + + do + { + var mod = value % 32; + r.Insert(0, ValidChars[mod]); + value = (value - mod) / 32; + } + while (value > 0); + + return r.ToString(); + } + + /// + /// Converts a Base32-k string into an array of bytes. + /// + public static int FromBase32String(string valueString) + { + var result = 0; + + for (var figureIndex = valueString.Length - 1; figureIndex >= 0; figureIndex--) + { + var figureChar = valueString[figureIndex]; + var figureValue = ValidChars.IndexOf(figureChar); + + result += (int)Math.Pow(32, valueString.Length - figureIndex - 1) * figureValue; + } + + return result; + } + + public static implicit operator int(Base32Integer value) => value.Value; + + public static implicit operator Base32Integer(int value) => new Base32Integer(value); + } +} \ No newline at end of file diff --git a/Olive/Utilities/CachedValue.cs b/Olive/Utilities/CachedValue.cs new file mode 100644 index 000000000..5d52c5f95 --- /dev/null +++ b/Olive/Utilities/CachedValue.cs @@ -0,0 +1,54 @@ +using System; + +namespace Olive +{ + public static class CachedValue + { + /// + /// Creates a CachedValue the specified value builder. + /// + public static CachedValue Create(Func valueBuilder) => new CachedValue(valueBuilder); + } + + public class CachedValue + { + /// + /// Stores the underlying value. + /// + T _Value; + + Func ValueBuilder; + + /// + /// Gets the underlying value. + /// + public T Value + { + get + { + if (ValueBuilder != null) + { + _Value = ValueBuilder(); + ValueBuilder = null; + } + + return _Value; + } + } + + /// + /// Creates a new CachedValue instance. + /// + public CachedValue(T value) => _Value = value; + + /// + /// Initializes a new CachedValue instance with lazy loading support. + /// + /// The value builder. + public CachedValue(Func valueBuilder) => ValueBuilder = valueBuilder; + + public static implicit operator T(CachedValue value) => value.Value; + + public static implicit operator CachedValue(T value) => new CachedValue(value); + } +} diff --git a/Olive/Utilities/Config.cs b/Olive/Utilities/Config.cs new file mode 100644 index 000000000..63da09025 --- /dev/null +++ b/Olive/Utilities/Config.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.Extensions.Configuration; + +namespace Olive +{ + /// + /// Provides shortcut access to the value specified in web.config (or App.config) under AppSettings or ConnectionStrings. + /// + public static class Config + { + static IConfiguration Configuration; + + const string CONNECTION_STRINGS_CONFIG_ROOT = "ConnectionStrings"; + + static Config() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); + + Configuration = builder.Build(); + } + + /// + /// Gets the connection string with the specified key. + /// The connection strings should store directly under the ConnectionStrings section. + /// + public static string GetConnectionString(string key) + { + return Configuration[$"{CONNECTION_STRINGS_CONFIG_ROOT}:{key}"] ?? + throw new ArgumentException($"Thre is no connectionString defined in the appsettings.json or web.config with the key '{key}'."); + } + + /// + /// Gets all the connection strings. + /// The connection strings should store directly under the ConnectionStrings section. + /// + public static IEnumerable> GetConnectionStrings() + { + return Configuration.GetSection(CONNECTION_STRINGS_CONFIG_ROOT).GetChildren() + .Select(section => new KeyValuePair(section.Key, section.Value)); + } + + /// + /// Attempts to bind the given object instance to configuration values by matching + /// property names against configuration keys recursively. + /// + /// The key of the configuration section. + /// The object to bind. + public static void Bind(string key, object instance) => Configuration.GetSection(key).Bind(instance); + + /// + /// Attempts to bind a new instance of given type to configuration values by matching + /// property names against configuration keys recursively. + /// + /// The key of the configuration section. + /// A new instance of the given generic type. + public static T Bind(string key) where T : new() + { + var result = new T(); + + Configuration.GetSection(key).Bind(result); + + return result; + } + + /// + /// Gets the value configured in Web.Config (or App.config) under AppSettings. + /// + public static string Get(string key) => Get(key, string.Empty); + + /// + /// Gets the value configured in Web.Config (or App.config) under AppSettings. + /// If no value is found there, it will return the specified default value. + /// + public static string Get(string key, string defaultValue) => Configuration[key].Or(defaultValue); + + /// + /// Reads the value configured in Web.Config (or App.config) under AppSettings. + /// It will then convert it into the specified type. + /// + public static T Get(string key) => Get(key, default(T)); + + /// + /// Reads the value configured in Web.Config (or App.config) under AppSettings. + /// It will then convert it into the specified type. + /// If no value is found there, it will return the specified default value. + /// + public static T Get(string key, T defaultValue) + { + var value = "[???]"; + try + { + value = Get(key, defaultValue.ToStringOrEmpty()); + + if (value.IsEmpty()) return defaultValue; + + var type = typeof(T); + + return value.To(); + } + catch (Exception ex) + { + throw new Exception("Could not retrieve '{0}' config value for key '{1}' and value '{2}'.".FormatWith(typeof(T).FullName, key, value), ex); + } + } + + /// + /// Reads the value configured in Web.Config (or App.config) under AppSettings. + /// It will then try to convert it into the specified type. + /// If no vale is found in AppSettings or the conversion fails, then it will return null, or the default value of the specified type T. + /// + public static T TryGet(string key) + { + var value = Get(key); + + if (value.IsEmpty()) return default(T); + + var type = typeof(T); + + try + { + return (T)value.To(type); + } + catch + { + // No logging is needed + return default(T); + } + } + + /// + /// Determines whether the specified key is defined in configuration file. + /// + public static bool IsDefined(string key) => Get(key).HasValue(); + + /// + /// Reads the app settings from a specified configuration file. + /// + [Obsolete("The XML settings are outdated.")] + public static async Task> ReadAppSettings(FileInfo configFile) + { + if (configFile == null) throw new ArgumentNullException(nameof(configFile)); + + if (!configFile.Exists()) throw new ArgumentException("File does not exist: " + configFile.FullName); + + var result = new Dictionary(); + + var config = XDocument.Parse(await configFile.ReadAllText()); + + var appSettings = config.Root.Elements().SingleOrDefault(a => a.Name.LocalName == "appSettings"); + + foreach (var setting in appSettings?.Elements()) + { + var key = setting.GetValue("@key"); + + if (result.ContainsKey(key)) + throw new Exception($"The key '{key}' is defined more than once in the application config file '{configFile.FullName}'."); + + result.Add(key, setting.GetValue("@value")); + } + + return result; + } + } +} \ No newline at end of file diff --git a/Olive/Utilities/EscapeGCopAttribute.cs b/Olive/Utilities/EscapeGCopAttribute.cs new file mode 100644 index 000000000..e46230e41 --- /dev/null +++ b/Olive/Utilities/EscapeGCopAttribute.cs @@ -0,0 +1,15 @@ +namespace Olive +{ + using System; + + /// + /// When applied to a method it will skip all GCop warnings for that method (not to be abused). + /// It is bad to escape any cop. Always try to avoid using this attribute by fixing your code. + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = true)] + public class EscapeGCopAttribute : Attribute + { + public EscapeGCopAttribute(string reason) { Reason = reason; } + public string Reason { get; private set; } + } +} \ No newline at end of file diff --git a/Olive/Utilities/LocalTime.cs b/Olive/Utilities/LocalTime.cs new file mode 100644 index 000000000..a5b25bc5b --- /dev/null +++ b/Olive/Utilities/LocalTime.cs @@ -0,0 +1,155 @@ +using System; + +namespace Olive +{ + public class LocalTime + { + /// + /// By default provides the current server's timezone. + /// You can override this to provide user-specific time-zones or based on any other system setting. + /// + public static Func CurrentTimeZone = () => TimeZoneInfo.Local; + + /// + /// If set, it will provide the "Now" value. + /// Note: This has lower priority than thread-level overrides. + /// + static Func GlobalNowGetter; + + /// + /// Gets the local current date/time of the application. + /// By default it equals to System.DateTime.Now. + /// To override its value, you should wrap the calling code inside "using (LocalTime.SetNow(some date)) { ... }" + ///   + /// Examples: + /// ————————————————————————————————— + /// var now = LocalTime.Now // which is identical to DateTime.Now + /// ————————————————————————————————— + /// using (LocalTime.Set(DateTime.Parse("15/01/2000 06:13"))) + /// { + ///  var date = LocalTime.Now; // that sets date to 15th Jan 200 at 6:13. + /// } + /// + public static DateTime Now + { + get + { + var setting = ProcessContext.Current; + + var nowGetter = setting?.NowGetter ?? GlobalNowGetter; + + if (nowGetter != null) return nowGetter(); + + return DateTime.UtcNow.ToLocal(); + } + } + + /// + /// Gets the current Universal Time. + /// + public static DateTime UtcNow + { + get + { + var setting = ProcessContext.Current; + + var nowGetter = setting?.NowGetter ?? GlobalNowGetter; + + if (nowGetter != null) return nowGetter().ToUniversal(); + + return DateTime.UtcNow; + } + } + + public static bool IsRedefined => (ProcessContext.Current?.NowGetter ?? GlobalNowGetter) != null; + + /// + /// Gets the local current date of the application (no time). + /// By default it equals to System.DateTime.Today. + /// To override its value, you should wrap the calling code inside "using (LocalTime.SetNow(some date)) { ... }" + ///   + /// Examples: + /// ————————————————————————————————— + /// var now = LocalTime.Today // which is identical to DateTime.Today + /// ————————————————————————————————— + /// using (LocalTime.Set(DateTime.Parse("15/01/2000 06:13"))) + /// { + ///  var date = LocalTime.Today; // that sets date to 15th Jan 200. + /// } + /// + public static DateTime Today => Now.Date; + + /// + /// Gets the current Universal Time's date part (without time). + /// + public static DateTime UtcToday => UtcNow.Date; + + /// + /// Sets the current time of the application. + ///   + /// Examples: + /// ————————————————————————————————— + /// using (LocalTime.Set(DateTime.Parse("15/01/2000 06:13"))) + /// { + /// //Here any call for LocalTime.Now/Today will return 15th of Jan 2000 (at 6:30). + /// } + /// + public static IDisposable Set(DateTime overriddenNow) => Set(() => overriddenNow); + + /// + /// Sets the current time function of the application. + /// + public static IDisposable Set(Func overriddenNow) => + new ProcessContext(new OverriddenApplicationDate(overriddenNow)); + + /// + /// Sets the current time function of the application. + /// Note: This has lower priority than thread-level time setting. + /// + public static void RedefineNow(Func overriddenNow) => GlobalNowGetter = overriddenNow; + + /// + /// Freezes the time to the current system time. + ///   + /// Examples: + /// ————————————————————————————————— + /// using (LocalTime.Stop()) + /// { + ///  // Freezes the time to Datetime.Now. + /// } + /// + public static IDisposable Stop() => Set(DateTime.Now); + + /// + /// Adds the specified time to the current LocalTime. + /// + public static void Add(TimeSpan time) + { + var setting = ProcessContext.Current; + + if (setting == null) + throw new InvalidOperationException("The current thread is not running inside a LocalTime."); + + var currentGetter = setting.NowGetter; + setting.NowGetter = () => currentGetter.Invoke().Add(time); + } + + public static void AddSeconds(double seconds) => Add(TimeSpan.FromSeconds(seconds)); + + public static void AddMinutes(double minutes) => Add(TimeSpan.FromMinutes(minutes)); + + public static void AddHours(double hours) => Add(TimeSpan.FromHours(hours)); + + public static void AddDays(double days) => Add(TimeSpan.FromDays(days)); + } + + class OverriddenApplicationDate + { + public Func NowGetter { get; internal set; } + + /// + /// Creates a new OverriddenApplicationDate instance. + /// + public OverriddenApplicationDate(Func time) => NowGetter = time; + } +} \ No newline at end of file diff --git a/Olive/Utilities/PascalCaseIdGenerator.cs b/Olive/Utilities/PascalCaseIdGenerator.cs new file mode 100644 index 000000000..24cb379a7 --- /dev/null +++ b/Olive/Utilities/PascalCaseIdGenerator.cs @@ -0,0 +1,85 @@ +using System; +using System.Linq; + +namespace Olive +{ + /// + /// Generates an identifier for a given string value. + /// + internal class PascalCaseIdGenerator + { + public string Value { get; private set; } + + public PascalCaseIdGenerator(string value) + { + if (value.IsEmpty()) + throw new ArgumentNullException(nameof(value)); + + Value = value.Trim(); + } + + void Replace(string from, string to) + { + while (Value.Contains(from)) + Value = Value.Replace(from, to); + } + + void Remove(params char[] characters) + { + foreach (var c in characters) + while (Value.Contains(c)) + Value = Value.Remove(c.ToString()); + } + + void ConvertToPascalCase() + { + for (var index = 0; index < Value.Length; index++) + { + var isFirstLetter = index == 0 || Value[index - 1] == ' '; + var character = Value[index]; + + if (isFirstLetter) + { + if (char.IsLower(character)) + { + Value = Value.Remove(index, 1); + Value = Value.Insert(index, char.ToUpper(character).ToString()); + } + + if (index > 0 && Value[index - 1] == ' ') + Value = Value.Remove(index - 1, 1); + } + } + } + + public string Build() + { + Remove('\''); + Replace(" ", " "); + Replace("&", "And"); + + foreach (var c in Value) + { + if (c == '_' || c == ' ') continue; + + if (!char.IsLetterOrDigit(c)) + Replace(c.ToString(), "_"); + } + + Value = Value.Trim('_', ' ', '\r', '\n', '\t'); + + ConvertToPascalCase(); + + while (Value.ContainsAny(new[] { " _", "_ ", "__" })) + { + Replace(" _", "_"); + Replace("_ ", "_"); + Replace("__", "_"); + } + + if (Value.FirstOrDefault().IsDigit()) Value = "_" + Value; + + return Value; + } + } +} \ No newline at end of file diff --git a/Olive/Utilities/ProcessContext.cs b/Olive/Utilities/ProcessContext.cs new file mode 100644 index 000000000..f332ea3b4 --- /dev/null +++ b/Olive/Utilities/ProcessContext.cs @@ -0,0 +1,73 @@ +using System; + +namespace Olive +{ + /// + /// Provides process context data sharing mechanism to pass arguments and data around execution in a shared pipeline. + /// It supports context nesting. + /// + public class ProcessContext : IDisposable + { + string Key; + + /// Gets or sets the Data of this ProcessContext. + public T Data { get; } + + /// + /// Creates a new Process Context. + /// + public ProcessContext(T data) : this(null, data) { } + + /// + /// Gets the data of the current context with default key (null). + /// + public static T Current => CallContext.GetData(GetKey()); + + /// + /// Creates a new Process Context with the specified key and data. + /// + public ProcessContext(string key, T data) + { + Data = data; + Key = GetKey(key); + CallContext.SetData(Key, data); + } + + static string GetKey(string key = null) => $"ProcessContext:{typeof(T).FullName}|K:{key}"; + + /// + /// Gets the data of the current context with the specified key. + /// + public static T GetCurrent(string key) => CallContext.GetData(GetKey(key)); + + /// + /// Disposes the current process context and switches the actual context to the containing process context. + /// + public void Dispose() + { + try { CallContext.SetData(GetKey(Key), default(T)); } + catch + { + // No logging is needed + } + } + } + + /// + /// Provides a facade for easiper creation of a Process Context. + /// + public static class ProcessContext + { + /// + /// Create a process context for the specified object. + /// To access the context object, you can use ProcessContext<Your Type>.Current. + /// + public static ProcessContext Create(T contextObject) => new ProcessContext(contextObject); + + /// + /// Create a process context for the specified object with the specified key. + /// To access the context object, you can use ProcessContext<Your Type>.GetCurrent(key). + /// + public static ProcessContext Create(string key, T contextObject) => new ProcessContext(key, contextObject); + } +} \ No newline at end of file diff --git a/Olive/Utilities/PropertyComparer.cs b/Olive/Utilities/PropertyComparer.cs new file mode 100644 index 000000000..bfa5b1ec1 --- /dev/null +++ b/Olive/Utilities/PropertyComparer.cs @@ -0,0 +1,30 @@ +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Olive +{ + internal class PropertyComparer : IComparer + { + PropertyInfo Property; + + public PropertyComparer(PropertyInfo property) => Property = property; + + public TValue ExtractValue(TItem item) => (TValue)Property.GetValue(item, null); + + public int Compare(object first, object second) => + Comparer.Default.Compare(Property.GetValue(first, null), Property.GetValue(second, null)); + } + + internal class PropertyComparer : IComparer + { + PropertyInfo Property; + + public PropertyComparer(PropertyInfo property) => Property = property; + + public TValue ExtractValue(TItem item) => (TValue)Property.GetValue(item, null); + + public int Compare(T first, T second) => + Comparer.Default.Compare((T)Property.GetValue(first, null), (T)Property.GetValue(second, null)); + } +} diff --git a/Olive/Utilities/Range.cs b/Olive/Utilities/Range.cs new file mode 100644 index 000000000..64d4d7bfe --- /dev/null +++ b/Olive/Utilities/Range.cs @@ -0,0 +1,130 @@ +using System; + +namespace Olive +{ + /// + /// Provides a range of values. + /// + public class Range where T : IComparable, IComparable + { + const int NINETEEN_HUNDRED = 1900; + + /// + /// Gets or sets the From of this Range. + /// + public T From { get; set; } + + /// + /// Gets or sets the To of this Range. + /// + public T To { get; set; } + + /// + /// Creates a new Range instance. + /// + public Range() { } + + /// + /// Creates a new Range instance with the specified boundaries. + /// + public Range(T from, T to) + { + if (from.CompareTo(to) > 0) + throw new ArgumentException($"nameof{from} should be smaller than or equal to {nameof(To)} value in a range."); + + From = from; + To = to; + } + + /// + /// Gets the length of this range. For a date range, use the TimeOfDay property of the returned date time. + /// + public T GetLength() + { + if (From.CompareTo(To) > 0) + throw new InvalidOperationException("'from' should be smaller than or equal to 'To' value in a range."); + + var fromValue = (object)From; + var toValue = (object)To; + object result; + + if (typeof(T) == typeof(int)) + result = (int)toValue - (int)fromValue; + + else if (typeof(T) == typeof(double)) + result = (double)toValue - (double)fromValue; + + else if (typeof(T) == typeof(long)) + result = (long)toValue - (long)fromValue; + + else if (typeof(T) == typeof(decimal)) + result = (decimal)toValue - (decimal)fromValue; + + else if (typeof(T) == typeof(DateTime)) + result = new DateTime(NINETEEN_HUNDRED, 1, 1).Add((DateTime)toValue - (DateTime)fromValue); + + else + throw new NotSupportedException("GetLength() is not supported on type: " + typeof(T).FullName); + + return (T)result; + } + + /// + /// Determines whether or not this range lacks the given value. + /// + public bool Lacks(T value, bool includingEdges = true) => !Contains(value, includingEdges); + + /// + /// Determines whether or not this range cotnains the given value + /// + public bool Contains(T value, bool includeEdges = true) + { + if (includeEdges) + { + if (value.CompareTo(From) < 0) return false; + if (value.CompareTo(To) > 0) return false; + } + else + { + if (value.CompareTo(From) <= 0) return false; + if (value.CompareTo(To) >= 0) return false; + } + + return true; + } + + /// + /// Determines whether or not this range contains with the given range + /// + public bool Contains(Range range) => Contains(range.From, includeEdges: true) && Contains(range.To, includeEdges: true); + + /// + /// Determines whether or not this range intersects with the given range + /// + public bool Intersects(Range range, bool includeEdges = true) + { + return Contains(range.From, includeEdges) || + Contains(range.To, includeEdges) || + range.Contains(From, includeEdges) || + range.Contains(To, includeEdges) || + (From.Equals(range.From) && To.Equals(range.To)); + } + + /// + /// Returns: {From} - {To}. + /// + public override string ToString() => ToString(" - "); + + /// + /// Returns {From}{rangeSeparator}{To}. + /// + public string ToString(string rangeSeparator) => From + rangeSeparator + To; + + /// + /// Returns the From and To values formatted by the specified format and then joined together with the specified rangeSeparator. + /// + /// E.g. {0:dd MM yy} + public string ToString(string rangeSeparator, string perItemFormat) => + string.Format(perItemFormat, From) + rangeSeparator + string.Format(perItemFormat, To); + } +} \ No newline at end of file diff --git a/Olive/Utilities/ShortGuid.cs b/Olive/Utilities/ShortGuid.cs new file mode 100644 index 000000000..7c74fa861 --- /dev/null +++ b/Olive/Utilities/ShortGuid.cs @@ -0,0 +1,190 @@ +using System; + +namespace Olive +{ + /// + /// Represents a globally unique identifier (GUID) with a shorter string value. + /// + public struct ShortGuid + { + const int Thirty = 30; + const int TwentyTwo = 22; + + Guid _guid; + string _value; + + /// + /// Gets/sets the underlying Guid + /// + public Guid Guid + { + get => _guid; + set + { + if (value != _guid) + { + _guid = value; + _value = Encode(value); + } + } + } + + /// + /// Gets/sets the underlying base64 encoded string + /// + public string Value + { + get => _value; + set + { + if (value != _value) + { + _value = value; + _guid = Decode(value); + } + } + } + + /// + /// Equivalent to Guid.Empty. + /// + public static readonly ShortGuid Empty = new ShortGuid(Guid.Empty); + + /// + /// Parses a specified text (that is either a normal Guid or a short guid). + /// + public static ShortGuid Parse(string text) + { + if (text.IsEmpty()) return Empty; + if (text.Length >= Thirty) + return new ShortGuid(new Guid(text)); + return new ShortGuid(text); + } + + /// + /// Creates a ShortGuid from a base64 encoded string + /// + /// The encoded guid as a + /// base64 string + public ShortGuid(string value) + { + _value = value; + _guid = Decode(value); + } + + /// + /// Creates a ShortGuid from a Guid + /// + /// The Guid to encode + public ShortGuid(Guid guid) + { + _value = Encode(guid); + _guid = guid; + } + + /// + /// Returns the base64 encoded guid as a string + /// + public override string ToString() => _value; + + /// + /// Returns a value indicating whether this instance and a + /// specified Object represent the same type and value. + /// + /// The object to compare + public override bool Equals(object obj) + { + if (obj is ShortGuid) + return _guid.Equals(((ShortGuid)obj)._guid); + if (obj is Guid) + return _guid.Equals((Guid)obj); + if (obj is string) + return _guid.Equals(((ShortGuid)obj)._guid); + return false; + } + + /// + /// Returns the HashCode for underlying Guid. + /// + public override int GetHashCode() => _guid.GetHashCode(); + + /// + /// Initialises a new instance of the ShortGuid class + /// + public static ShortGuid NewGuid() => new ShortGuid(Guid.NewGuid()); + + /// + /// Creates a new instance of a Guid using the string value, + /// then returns the base64 encoded version of the Guid. + /// + /// An actual Guid string (i.e. not a ShortGuid) + public static string Encode(string value) + { + var guid = new Guid(value); + return Encode(guid); + } + + /// + /// Encodes the given Guid as a base64 string that is 22 + /// characters long. + /// + /// The Guid to encode + public static string Encode(Guid guid) + { + string encoded = Convert.ToBase64String(guid.ToByteArray()); + encoded = encoded + .Replace("/", "_") + .Replace("+", "-"); + return encoded.Substring(0, TwentyTwo); + } + + /// + /// Decodes the given base64 string + /// + /// The base64 encoded string of a Guid + /// A new Guid + public static Guid Decode(string value) + { + value = value + .Replace("_", "/") + .Replace("-", "+"); + var buffer = Convert.FromBase64String(value + "=="); + return new Guid(buffer); + } + + /// + /// Determines if both ShortGuids have the same underlying Guid value. + /// + public static bool operator ==(ShortGuid me, ShortGuid other) + { + if ((object)me == null) return (object)other == null; + return me._guid == other._guid; + } + + /// + /// Determines if both ShortGuids do not have the + /// same underlying Guid value. + /// + public static bool operator !=(ShortGuid me, ShortGuid other) => !(me == other); + + /// + /// Implicitly converts the ShortGuid to it's string equivilent + /// + public static implicit operator string(ShortGuid shortGuid) => shortGuid._value; + + /// + /// Implicitly converts the ShortGuid to it's Guid equivilent + /// + public static implicit operator Guid(ShortGuid shortGuid) => shortGuid._guid; + + /// + /// Implicitly converts the string to a ShortGuid + /// + public static implicit operator ShortGuid(string shortGuid) => new ShortGuid(shortGuid); + + /// + /// Implicitly converts the Guid to a ShortGuid + /// + public static implicit operator ShortGuid(Guid guid) => new ShortGuid(guid); + } +} \ No newline at end of file diff --git a/Olive/Utilities/TemporaryFile.cs b/Olive/Utilities/TemporaryFile.cs new file mode 100644 index 000000000..2d62c8292 --- /dev/null +++ b/Olive/Utilities/TemporaryFile.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; + +namespace Olive +{ + /// + /// This class provides a unique file path in a temporary folder (i.e. in the application temp folder + /// in the system by default and can be provided in Config of the application through a setting with key "Application.TemporaryFilesPath") + /// After this instance is disposed any possibly created file in the path will be deleted physically. + /// + /// If this class fails to dispose an application event will be added to the projects database. + /// + public class TemporaryFile : IDisposable + { + static string TemporaryFileFolder = GetTemporaryFileFolder(); + + Guid ID; + + public string Extension { get; set; } + + /// + /// Creates a new instance of temporary file. The file will have "dat" extension by default. + /// + public TemporaryFile() : this("dat") { } + + /// + /// Gets or sets the FilePath of this TemporaryFile. + /// + public string FilePath + { + get + { + var folder = TemporaryFileFolder; + if (Directory.Exists(folder) == false) + Directory.CreateDirectory(folder); + + var filename = ID + "." + Extension.Trim('.'); + return folder + "\\" + filename; + } + } + + /// + /// Creates a new instance of temporary file. + /// with the given extension. Extension can either have "." or not + /// + public TemporaryFile(string extension) + { + ID = Guid.NewGuid(); + Extension = extension; + } + + /// + /// Finds a proper folder path for temporary files + /// + static string GetTemporaryFileFolder() + { + var relativePath = Config.Get("Application.TemporaryFilesPath", ""); + + if (relativePath.HasValue()) + return AppDomain.CurrentDomain.BaseDirectory + relativePath.Replace("/", "\\").Replace("\\\\", "\\").Trim('\\'); + + return Path.GetTempPath(); + } + + /// + /// Disposes this instance of temporary file and deletes the file if provided + /// + public void Dispose() + { + try + { + if (FilePath.AsFile().Exists()) File.Delete(FilePath); + } + catch (Exception ex) + { + Log.Error("Can not dispose temporary file.", ex); + } + } + } +} \ No newline at end of file diff --git a/OliveVSIX/Key.snk b/OliveVSIX/Key.snk new file mode 100644 index 0000000000000000000000000000000000000000..00963ed6c5c949017b427f283bde65f8158bedc7 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096+ygU2xbIBsc!{{*CqM;K(y!y~QK#ut> zGfw8p-{Js`1GB-)*Ukq>9m?kBuJ-WmSHCV7s*7cF9XZ`S|KY-|<$|w`=mx`eU3fL=_1k;6@r|SwlL!DMJ^07| z9Wrmvb28zAE%*4tC-d0>GFeP#)?MDgtDyc)m?1Xa%{_f}M=Nw8X_2Hf9CJbwiFcJt zg#46BzeaeLpBBhxt!H=M5+P^NpOqevF<2rrBHlD`8E`11Hom8S3iB>J@CPV&>&d2I zZhayF1^=ekHR+k8-NY#sas}2-ivESwxnAShq4uioI)CMS&>Xf;o}xr-WYSlHVq^ni z*{i&vI|rs+1p3LWk!FM{t8r>`Y@RGikL@1qY4EPflXK1BPcos1^<_-+|BO|}o|I%P zweXLP8T%07*8o2wTN+a%ihV+3U{dU}GN;&&&WNdBPU6)a3)Y`4-J`?!ICY-o4?{Tn iscH&|Cu^xjVQ76vO@K6;rrC|5&Wk%L^k+}H$@^*xk}D+u literal 0 HcmV?d00001 diff --git a/OliveVSIX/NugetPacker/NugetPacker.cs b/OliveVSIX/NugetPacker/NugetPacker.cs new file mode 100644 index 000000000..f97d0acbe --- /dev/null +++ b/OliveVSIX/NugetPacker/NugetPacker.cs @@ -0,0 +1,112 @@ +using System; +using System.ComponentModel.Design; +using System.Globalization; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; + +namespace OliveVSIX.NugetPacker +{ + /// + /// Command handler + /// + internal sealed class NugetPacker + { + bool ExceptionOccurred; + + /// + /// Command ID. + /// + public const int CommandId = 0x0100; + + /// + /// Command menu group (command set GUID). + /// + public static readonly Guid CommandSet = new Guid("090581b5-cfbb-40d7-9ff4-bdc7f81edef5"); + + /// + /// VS Package that provides this command, not null. + /// + readonly Package package; + + /// + /// Initializes a new instance of the class. + /// Adds our command handlers for menu (commands must exist in the command table file) + /// + /// Owner package, not null. + NugetPacker(Package package) + { + this.package = package ?? throw new ArgumentNullException("package"); + + if (ServiceProvider.GetService(typeof(IMenuCommandService)) is OleMenuCommandService commandService) + { + var menuCommandID = new CommandID(CommandSet, CommandId); + var menuItem = new MenuCommand(MenuItemCallback, menuCommandID); + commandService.AddCommand(menuItem); + + NugetPackerLogic.OnCompleted += NugetPackerLogic_OnCompleted; + NugetPackerLogic.OnException += NugetPackerLogic_OnException; + } + } + + void NugetPackerLogic_OnException(object sender, Exception arg) + { + ExceptionOccurred = true; + + VsShellUtilities.ShowMessageBox( + ServiceProvider, + arg.Message, + arg.GetType().ToString(), + OLEMSGICON.OLEMSGICON_CRITICAL, + OLEMSGBUTTON.OLEMSGBUTTON_OK, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); + } + + void NugetPackerLogic_OnCompleted(object sender, EventArgs e) + { + VsShellUtilities.ShowMessageBox( + ServiceProvider, + ExceptionOccurred ? "The process completed with error(s)." : "The selected projects are updated.", + "Nuget updater", + OLEMSGICON.OLEMSGICON_INFO, + OLEMSGBUTTON.OLEMSGBUTTON_OK, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); + } + + /// + /// Gets the instance of the command. + /// + public static NugetPacker Instance + { + get; + private set; + } + + /// + /// Gets the service provider from the owner package. + /// + IServiceProvider ServiceProvider => package; + + /// + /// Initializes the singleton instance of the command. + /// + /// Owner package, not null. + public static void Initialize(Package package) + { + Instance = new NugetPacker(package); + } + + /// + /// This function is the callback used to execute the command when the menu item is clicked. + /// See the constructor to see how the menu item is associated with this function using + /// OleMenuCommandService service and MenuCommand class. + /// + /// Event sender. + /// Event args. + void MenuItemCallback(object sender, EventArgs e) + { + ExceptionOccurred = false; + var dte2 = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE80.DTE2; + NugetPackerLogic.Pack(dte2); + } + } +} diff --git a/OliveVSIX/NugetPacker/NugetPackerLogic.cs b/OliveVSIX/NugetPacker/NugetPackerLogic.cs new file mode 100644 index 000000000..250da7dfc --- /dev/null +++ b/OliveVSIX/NugetPacker/NugetPackerLogic.cs @@ -0,0 +1,152 @@ +using EnvDTE; +using EnvDTE80; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml; + +namespace OliveVSIX.NugetPacker +{ + public delegate void ExceptionHandler(object sender, Exception arg); + + static class NugetPackerLogic + { + const string NuspecFileName = "Package.nuspec"; + const string NugetFileName = "nuget.exe"; + const string OutputFolder = "NugetPackages"; +#pragma warning disable GCop412 // Never hardcode a path or drive name in code. Get the application path programmatically and use relative path. Use “AppDomain.CurrentDomain.GetPath” to get the physical path. + const string ApiKeyContainingFile = @"C:\Projects\NUGET-Publish-Key.txt"; +#pragma warning restore GCop412 // Never hardcode a path or drive name in code. Get the application path programmatically and use relative path. Use “AppDomain.CurrentDomain.GetPath” to get the physical path. + + static DTE2 Dte2; + static string NugetExe; + static string SolutionPath; + static string ApiKey; + static string NugetPackagesFolder; + + static System.Threading.Thread Thread; + + public static event EventHandler OnCompleted; + public static event ExceptionHandler OnException; + + public static void Pack(DTE2 dte2) + { + Dte2 = dte2; + + SolutionPath = Path.GetDirectoryName(Dte2.Solution.FullName); + NugetExe = Path.Combine(SolutionPath, NugetFileName); + ApiKey = File.ReadAllText(ApiKeyContainingFile); + NugetPackagesFolder = Path.Combine(SolutionPath, OutputFolder); + + var start = new System.Threading.ThreadStart(() => + { + try + { + foreach (var item in GetSelectedProjectPath()) + PackSingleProject(item); + } + catch (Exception exception) + { + InvokeException(exception); + } + + OnCompleted?.Invoke(null, EventArgs.Empty); + }); + + Thread = new System.Threading.Thread(start) { IsBackground = true }; + Thread.Start(); + } + + static void InvokeException(Exception exception) => OnException?.Invoke(null, exception); + + static void PackSingleProject(Project proj) + { + var projectPath = Path.GetDirectoryName(proj.FullName); + var nuspecAddress = Path.Combine(projectPath, NuspecFileName); + + var packageFilename = UpdateVersionThenReturnPackageName(nuspecAddress); + + if (TryPack(nuspecAddress, out string packingMessage)) + { + if (!TryPush(packageFilename, out string pushingMessage)) + InvokeException(new Exception(pushingMessage)); + } + else + InvokeException(new Exception(packingMessage)); + } + + static bool TryPush(string packageFilename, out string message) + { + return ExecuteNuget($"push \"{NugetPackagesFolder}\\{packageFilename}\" {ApiKey} -NonInteractive -Source https://www.nuget.org/api/v2/package", out message); + } + + static bool TryPack(string nuspecAddress, out string message) => + ExecuteNuget($"pack \"{nuspecAddress}\" -OutputDirectory \"{NugetPackagesFolder}\"", out message); + + static bool ExecuteNuget(string arguments, out string message) + { + var startInfo = new ProcessStartInfo(NugetExe) + { + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden + }; + + var process = System.Diagnostics.Process.Start(startInfo); + + process.WaitForExit(); + + if (process.ExitCode == 0) + { + message = null; + return true; + } + else + { + message = $"{arguments}:\r\n{process.StandardError.ReadToEnd()}"; + return false; + } + } + + static string UpdateVersionThenReturnPackageName(string nuspecAddress) + { + var doc = new XmlDocument(); + doc.Load(nuspecAddress); + + // TODO: This is not a proper way to access to xml nodes. It is just a in rush code. + var idNode = doc.ChildNodes[1].ChildNodes[0].ChildNodes[0]; + var versionNode = doc.ChildNodes[1].ChildNodes[0].ChildNodes[1]; + + var versionParts = versionNode.InnerText.Split('.'); + var minorPart = int.Parse(versionParts.LastOrDefault()) + 1; + + var newVersion = string.Empty; + for (var index = 0; index < versionParts.Length - 1; index++) + newVersion += $"{versionParts[index]}."; + + versionNode.InnerText = $"{newVersion}{minorPart}"; + + doc.Save(nuspecAddress); + + return $"{idNode.InnerText}.{versionNode.InnerText}.nupkg"; + } + + static IEnumerable GetSelectedProjectPath() + { + var uih = Dte2.ToolWindows.SolutionExplorer; + var selectedItems = (Array)uih.SelectedItems; + + if (selectedItems != null) + foreach (UIHierarchyItem selItem in selectedItems) + if (selItem.Object is Project proj) + yield return proj; + } + } +} diff --git a/OliveVSIX/NugetPacker/NugetPackerPackage.cs b/OliveVSIX/NugetPacker/NugetPackerPackage.cs new file mode 100644 index 000000000..250cb1b0d --- /dev/null +++ b/OliveVSIX/NugetPacker/NugetPackerPackage.cs @@ -0,0 +1,69 @@ +using System; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.Win32; + +namespace OliveVSIX.NugetPacker +{ + /// + /// This is the class that implements the package exposed by this assembly. + /// + /// + /// + /// The minimum requirement for a class to be considered a valid package for Visual Studio + /// is to implement the IVsPackage interface and register itself with the shell. + /// This package uses the helper classes defined inside the Managed Package Framework (MPF) + /// to do it: it derives from the Package class that provides the implementation of the + /// IVsPackage interface and uses the registration attributes defined in the framework to + /// register itself and its components with the shell. These attributes tell the pkgdef creation + /// utility what data to put into .pkgdef file. + /// + /// + /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. + /// + /// + [PackageRegistration(UseManagedResourcesOnly = true)] + [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] // Info on this package for Help/About + [ProvideMenuResource("Menus.ctmenu", 1)] + [Guid(NugetPackerPackage.PackageGuidString)] + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")] + public sealed class NugetPackerPackage : Package + { + /// + /// NugetPackerPackage GUID string. + /// + public const string PackageGuidString = "aebcdf85-651a-4513-8f1c-a706edd15c5c"; + + /// + /// Initializes a new instance of the class. + /// + public NugetPackerPackage() + { + // Inside this method you can place any initialization code that does not require + // any Visual Studio service because at this point the package object is created but + // not sited yet inside Visual Studio environment. The place to do all the other + // initialization is the Initialize method. + } + + #region Package Members + + /// + /// Initialization of the package; this method is called right after the package is sited, so this is the place + /// where you can put all the initialization code that rely on services provided by VisualStudio. + /// + protected override void Initialize() + { + NugetPacker.Initialize(this); + base.Initialize(); + } + + #endregion + } +} diff --git a/OliveVSIX/NugetPacker/NugetPackerPackage.vsct b/OliveVSIX/NugetPacker/NugetPackerPackage.vsct new file mode 100644 index 000000000..adf226da5 --- /dev/null +++ b/OliveVSIX/NugetPacker/NugetPackerPackage.vsct @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OliveVSIX/NugetPacker/Resources/NugetPacker.png b/OliveVSIX/NugetPacker/Resources/NugetPacker.png new file mode 100644 index 0000000000000000000000000000000000000000..b22d975cbf00eda60e37587614e0677d0bfb525d GIT binary patch literal 1172 zcmV;F1Z(?=P)AHIP00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGqB>(^xB>_oNB=7(L1Sv^GK~z{r&6rO} zR8bVhMO+m@n`n^>S1nuxLD3=*TeOfsmlen?+6F={#2~abZOoueAZTJ(h!8>v{t3;> za?E)|5DA$%%#4KTQ&4A9oX&T-XSj3Ub>17D7(e)!JLjHr)!%vdzW1y+tlHh(J<9H6 zC)uRE&VI~3rPB^9A}Xs7r5JEE`&%NBp!)jyJP!>G(f0N>8gX8yD`m6Unw_1U4t9(0 z`^R-SPl)q;AA1VsnwSfU>5DfL{NbJ}g%6z=Y!y2`Ha4b!&wZSu_228{Z}{|MX*t&j zZo(SpokR_TtlRpvN}qckDl^<=y9pd?++5B0pB~($m6=KGCJ#9-DB`AjX=;A-hyG_1 zN-BV`t*0W3!CXzHQVMc9m8MO9D`43PoZE#OT-5;IfBKm6Z09Y^y}%qG^SC?X#c8;% zmPW2zAT)v-cn0$>D6qi4bZ1++_WElMyK_xfRaTMM6*J>-K>W+fd8@3$k!07x#Wsa& zh6e@)sHdmL2_OEsyh5>94C^nU#%=(NtDNIFl9sXrroHe%Qf zR#RHk57Kf$F@4Ym6oL;2B#=yiGL}!B8j$3hx#l>m9^&~A$eWpY>x2()toyQ7R+pg( zF;^30j9tL+`*eCvE-0oC(tt4duu~>1a~uXx^37ax999qU{L_hv32JL=bHaz`K$0hxe2L3grw7Dy*V%vN z@pF89A^<;=&QMcR6D=$(V6A3ds#K@}eDzQ1v^=?>zyKfhChP(p#i(@(kW64QmQS4) zkXbK z3DUMSq1eYz6R-(fVy2zqKPCsu9LuLp3rKRrQR_M8gpZvtIXRj8-rL(t$z&3146={t za`hC1ryCPE8iCz_=YDAha^&$-WCO9ipLYNow%GtP4m$-%g{;#8a^T^2W)0ic`s=u3>eS(!53jy;Ap}TwK(!j{X4>h0jPj$#%z}oK71%7O)%j{Z2*9CjSurx-l4;Lit}qY m*JGpEo(&`nZYkjtQN=$$-bTw6(Xv|r0000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + NugetPacker Extension + + + NugetPacker Visual Studio Extension Detailed Info + + \ No newline at end of file diff --git a/OliveVSIX/OliveVSIX.csproj b/OliveVSIX/OliveVSIX.csproj new file mode 100644 index 000000000..bf0e762ed --- /dev/null +++ b/OliveVSIX/OliveVSIX.csproj @@ -0,0 +1,204 @@ + + + + + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + true + + + true + + + Key.snk + + + + Debug + AnyCPU + 2.0 + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {83FFCB26-B836-4E43-8F16-3FC0B29A7572} + Library + Properties + OliveVSIX + OliveVSIX + v4.6.1 + true + true + true + true + true + false + Program + $(DevEnvDir)devenv.exe + /rootsuffix Exp + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + Designer + + + + + Menus.ctmenu + Designer + + + Always + true + + + + + False + + + False + + + False + + + False + + + + False + + + ..\packages\Microsoft.VisualStudio.CoreUtility.15.0.26606\lib\net45\Microsoft.VisualStudio.CoreUtility.dll + True + + + ..\packages\Microsoft.VisualStudio.Imaging.15.0.26606\lib\net45\Microsoft.VisualStudio.Imaging.dll + True + + + ..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll + True + + + ..\packages\Microsoft.VisualStudio.Shell.15.0.15.0.26606\lib\Microsoft.VisualStudio.Shell.15.0.dll + True + + + ..\packages\Microsoft.VisualStudio.Shell.Framework.15.0.26606\lib\net45\Microsoft.VisualStudio.Shell.Framework.dll + True + + + ..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll + True + + + True + ..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll + True + + + True + ..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll + True + + + True + ..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll + True + + + True + ..\packages\Microsoft.VisualStudio.Shell.Interop.15.3.DesignTime.15.0.26606\lib\net20\Microsoft.VisualStudio.Shell.Interop.15.3.DesignTime.dll + True + + + ..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll + True + + + ..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll + True + + + ..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll + True + + + ..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll + True + + + ..\packages\Microsoft.VisualStudio.Threading.15.3.23\lib\net45\Microsoft.VisualStudio.Threading.dll + True + + + ..\packages\Microsoft.VisualStudio.Utilities.15.0.26606\lib\net45\Microsoft.VisualStudio.Utilities.dll + True + + + ..\packages\Microsoft.VisualStudio.Validation.15.3.15\lib\net45\Microsoft.VisualStudio.Validation.dll + True + + + False + + + + + + + + + + + true + VSPackage + Designer + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + \ No newline at end of file diff --git a/OliveVSIX/Properties/AssemblyInfo.cs b/OliveVSIX/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..b2ea00f8c --- /dev/null +++ b/OliveVSIX/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("OliveVSIX")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("OliveVSIX")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/OliveVSIX/Resources/Olive.png b/OliveVSIX/Resources/Olive.png new file mode 100644 index 0000000000000000000000000000000000000000..c29070869e43cbb63b9c03937cb70f442083e2f7 GIT binary patch literal 1614 zcmV-U2C?~xP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D02y>eSaefwW^{L9 za%BK;VQFr3E^cLXAT%y8E-^XO*0N*(00q8DL_t(|UhP_IXdG1(PJL9Rid1S)@fly( z`altbf{LJ2)IU{MrNnM#?rhwhotY$7F-7b6tUr8FjGJV4?r4i32JwMEs-jpG!HQPf z{tzsdsI7`GQ1M4YO#JTVM3eOF?CdkU>wNIBBzy09+)y*W+DF=&RO;?dx~J@gE%Dn3^H}{>&USkE9{33rFf3W;;HcmL~>j zSZe~I{siave-z`rL-A8-c%4a7huEVbT*xA20XF1x^ z*hL~19e!U4{o zfP>?EPFPb?8lRGD#o@Z{K{)N-Wd&7{D!QQO11$8ACB<6D({Hmzw5!cwtrI=M`L>_9 z4Bx%Z%`KIxpCI>&1FO};38|E-L!JmYTL(=SvDO)3ilP0XQKq^~)7rO?ia`$=Rl zpzvs{`p=w@a*3T*jnKWRXS+`#H6@`MQ4qQr!od4D0p$<}&knS?w^mYs*it(da(?CH zhoy=<)q<1A!ic%C#p<-=gp@+;*k0sxSC|l2PtX`@PJnYN}^7{)oxu26)EyAt`NOdrIdKh%;;=~F_Mk0&4TaS>C!oK z-T#El2!`qDr*Hy_5ei<6KgZYD&ZeG|5$11j^Xf$$>fK~cF!JHa2nZtb{i(L8wnZdj z4p4i?arpes&8r)w_|6@TEhdwK0cUhpNC6fqEc&0k`3PI;&vSF?O+13{AhU`C`QOJ0 z;b`Tw0h%@keZz_4RnBp9>P?(WJJsWrOe+qAOFt)QJ_|ik(plpyEaDh9r|y)-1PCGOsk`?*M|Xy61&edbs-{ z{J&xD3E}*{l+-%Nyz+p9a6Knn5sR}QMTMzeXsY4@V;A-rKbv^ot8<9lC?95a%?egQ z0}~tbWz~KF0b!4FN*a|2`?ph>)KW6PGN7_s$0=z2+e5%MQdmTxI?y;d2DhCZq)^?rbc&w*M9M23b4+QH5(lUVStkmC>8!->;LRqX&} zfp1#UIkB3C27+4PJ}l^~U`Y)T#_xq4uUI+JZx3n9No0M1QZ~UErD#d<7KX6bT*Ywb zNY66Whod361rDavD=;`Xpzik_&qz96;-+4yb%uILQA?=C2VwltfeRq^Bs0l&8rIrS z*H`H9wjkdpmGjuK&sAMOU;lrge$}aRd8)weZiEOCB1DMLDrjr_2L~XZoXDfi)&Kwi M07*qoM6N<$f?)XrH2?qr literal 0 HcmV?d00001 diff --git a/OliveVSIX/packages.config b/OliveVSIX/packages.config new file mode 100644 index 000000000..76e7edd94 --- /dev/null +++ b/OliveVSIX/packages.config @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OliveVSIX/source.extension.vsixmanifest b/OliveVSIX/source.extension.vsixmanifest new file mode 100644 index 000000000..9692c8b3d --- /dev/null +++ b/OliveVSIX/source.extension.vsixmanifest @@ -0,0 +1,26 @@ + + + + + OliveVSIX + Empty VSIX Project. + https://github.com/Geeksltd/Olive + Resources\Olive.png + Resources\Olive.png + + + + + + + + + + + + + + + + + diff --git a/Services/Olive.Services.CSV/CSVReader.cs b/Services/Olive.Services.CSV/CSVReader.cs new file mode 100644 index 000000000..8c4450a83 --- /dev/null +++ b/Services/Olive.Services.CSV/CSVReader.cs @@ -0,0 +1,73 @@ +using System; +using System.Data; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Olive.Entities; + +namespace Olive.Services.CSV +{ + /// + /// A data-reader style interface for reading Csv files. + /// + public static class CsvReader + { + /// + /// Reads a CSV blob into a data table. Note use the CastTo() method on the returned DataTable to gain fully-typed objects. + /// + public static async Task Read(Blob csv, bool isFirstRowHeaders, int minimumFieldCount = 0) => + Read(await csv.GetContentText(), isFirstRowHeaders, minimumFieldCount); + + /// + /// Reads a CSV file into a data table. Note use the CastTo() method on the returned DataTable to gain fully-typed objects. + /// + public static async Task Read(FileInfo csv, bool isFirstRowHeaders, int minimumFieldCount = 0) => + Read(await File.ReadAllTextAsync(csv.FullName), isFirstRowHeaders, minimumFieldCount); + + /// + /// Reads a CSV piece of string into a data table. Note use the CastTo() method on the returned DataTable to gain fully-typed objects. + /// + public static DataTable Read(string csvContent, bool isFirstRowHeaders, int minimumFieldCount = 0) + { + var output = new DataTable(); + + using (var csv = new LumenWorks.Framework.IO.Csv.CsvDataReader(new StringReader(csvContent), isFirstRowHeaders)) + { + csv.MissingFieldAction = LumenWorks.Framework.IO.Csv.MissingFieldAction.ReplaceByNull; + var fieldCount = Math.Max(csv.FieldCount, minimumFieldCount); + var headers = csv.GetFieldHeaders(); + + if (!isFirstRowHeaders) + headers = Enumerable.Range(0, fieldCount).Select(i => "Column" + i).ToArray(); + + for (int i = 0; i < fieldCount; i++) + output.Columns.Add(new DataColumn(headers[i], typeof(string))); + + while (csv.ReadNextRecord()) + { + var row = output.NewRow(); + + for (int i = 0; i < fieldCount; i++) row[i] = csv[i]; + + output.Rows.Add(row); + } + } + + return output; + } + + /// + /// Gets the column names on the specified CSV blob. + /// + public static async Task GetColumns(Blob blob) => GetColumns(await blob.GetContentText()); + + /// + /// Gets the column names on the specified CSV content. + /// + public static string[] GetColumns(string csvContent) + { + using (var csv = new LumenWorks.Framework.IO.Csv.CsvDataReader(new StringReader(csvContent), true)) + return csv.GetFieldHeaders(); + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.CSV/LumenWorks.Framework/CsvDataReader.Enums.cs b/Services/Olive.Services.CSV/LumenWorks.Framework/CsvDataReader.Enums.cs new file mode 100644 index 000000000..8f43cd3e5 --- /dev/null +++ b/Services/Olive.Services.CSV/LumenWorks.Framework/CsvDataReader.Enums.cs @@ -0,0 +1,101 @@ +// LumenWorks.Framework.IO.CSV.ParseErrorEventArgs +// Copyright (c) 2006 Sébastien Lorion +// +// MIT license (http://en.wikipedia.org/wiki/MIT_License) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; + +namespace LumenWorks.Framework.IO.Csv +{ + [Flags] + internal enum ValueTrimmingOptions + { + None = 0, + UnquotedOnly = 1, + QuotedOnly = 2, + All = UnquotedOnly | QuotedOnly + } + + /// + /// Specifies the action to take when a parsing error has occured. + /// + internal enum ParseErrorAction + { + /// + /// Raises the event. + /// + RaiseEvent = 0, + + /// + /// Tries to advance to next line. + /// + AdvanceToNextLine = 1, + + /// + /// Throws an exception. + /// + ThrowException = 2, + } + + /// + /// Specifies the action to take when a field is missing. + /// + internal enum MissingFieldAction + { + /// + /// Treat as a parsing error. + /// + ParseError = 0, + + /// + /// Replaces by an empty value. + /// + ReplaceByEmpty = 1, + + /// + /// Replaces by a null value (). + /// + ReplaceByNull = 2, + } + + partial class CsvDataReader + { + /// + /// Defines the data reader validations. + /// + [Flags] + private enum DataReaderValidations + { + /// + /// No validation. + /// + None = 0, + + /// + /// Validate that the data reader is initialized. + /// + IsInitialized = 1, + + /// + /// Validate that the data reader is not closed. + /// + IsNotClosed = 2 + } + } +} diff --git a/Services/Olive.Services.CSV/LumenWorks.Framework/CsvDataReader.RecordEnumerator.cs b/Services/Olive.Services.CSV/LumenWorks.Framework/CsvDataReader.RecordEnumerator.cs new file mode 100644 index 000000000..6dc643c02 --- /dev/null +++ b/Services/Olive.Services.CSV/LumenWorks.Framework/CsvDataReader.RecordEnumerator.cs @@ -0,0 +1,160 @@ +// LumenWorks.Framework.IO.CSV.CsvReader.RecordEnumerator +// Copyright (c) 2005 S�bastien Lorion +// +// MIT license (http://en.wikipedia.org/wiki/MIT_License) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections; +using System.Collections.Generic; +using LumenWorks.Framework.IO.Csv.Resources; + +namespace LumenWorks.Framework.IO.Csv +{ + partial class CsvDataReader + { + /// + /// Supports a simple iteration over the records of a . + /// + public struct RecordEnumerator + : IEnumerator, IEnumerator + { + #region Fields + + /// + /// Contains the enumerated . + /// + CsvDataReader _reader; + + /// + /// Contains the current record. + /// + string[] _current; + + /// + /// Contains the current record index. + /// + long _currentRecordIndex; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// The to iterate over. + /// + /// is a . + /// + public RecordEnumerator(CsvDataReader reader) + { + if (reader == null) + throw new ArgumentNullException("reader"); + + _reader = reader; + _current = null; + + _currentRecordIndex = reader.currentRecordIndex; + } + + #endregion + + #region IEnumerator Members + + /// + /// Gets the current record. + /// + public string[] Current => _current; + + /// + /// Advances the enumerator to the next record of the CSV. + /// + /// if the enumerator was successfully advanced to the next record, if the enumerator has passed the end of the CSV. + public bool MoveNext() + { + if (_reader.currentRecordIndex != _currentRecordIndex) + throw new InvalidOperationException(ExceptionMessage.EnumerationVersionCheckFailed); + + if (_reader.ReadNextRecord()) + { + _current = new string[_reader.fieldCount]; + + _reader.CopyCurrentRecordTo(_current); + _currentRecordIndex = _reader.currentRecordIndex; + + return true; + } + else + { + _current = null; + _currentRecordIndex = _reader.currentRecordIndex; + + return false; + } + } + + #endregion + + #region IEnumerator Members + + /// + /// Sets the enumerator to its initial position, which is before the first record in the CSV. + /// + public void Reset() + { + if (_reader.currentRecordIndex != _currentRecordIndex) + throw new InvalidOperationException(ExceptionMessage.EnumerationVersionCheckFailed); + + _reader.MoveTo(-1); + + _current = null; + _currentRecordIndex = _reader.currentRecordIndex; + } + + /// + /// Gets the current record. + /// + object IEnumerator.Current + { + get + { + if (_reader.currentRecordIndex != _currentRecordIndex) + throw new InvalidOperationException(ExceptionMessage.EnumerationVersionCheckFailed); + + return Current; + } + } + + #endregion + + #region IDisposable Members + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + _reader = null; + _current = null; + } + + #endregion + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.CSV/LumenWorks.Framework/CsvDataReader.cs b/Services/Olive.Services.CSV/LumenWorks.Framework/CsvDataReader.cs new file mode 100644 index 000000000..4428b8b6a --- /dev/null +++ b/Services/Olive.Services.CSV/LumenWorks.Framework/CsvDataReader.cs @@ -0,0 +1,2285 @@ +// LumenWorks.Framework.IO.CSV.CsvReader +// Copyright (c) 2005 S�bastien Lorion +// +// MIT license (http://en.wikipedia.org/wiki/MIT_License) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.IO; +using LumenWorks.Framework.IO.Csv.Resources; +using Debug = System.Diagnostics.Debug; + +namespace LumenWorks.Framework.IO.Csv +{ + /// + /// Represents a reader that provides fast, non-cached, forward-only access to CSV data. + /// + internal partial class CsvDataReader + : IDataReader, IEnumerable, IDisposable + { + #region Constants + + /// + /// Defines the default buffer size. + /// + public const int DefaultBufferSize = 0x1000; + + /// + /// Defines the default delimiter character separating each field. + /// + public const char DefaultDelimiter = ','; + + /// + /// Defines the default quote character wrapping every field. + /// + public const char DefaultQuote = '"'; + + /// + /// Defines the default escape character letting insert quotation characters inside a quoted field. + /// + public const char DefaultEscape = '"'; + + /// + /// Defines the default comment character indicating that a line is commented out. + /// + public const char DefaultComment = '#'; + + #endregion + + #region Fields + + /// + /// Contains the field header comparer. + /// + static readonly StringComparer fieldHeaderComparer = StringComparer.CurrentCultureIgnoreCase; + + #region Settings + + /// + /// Contains the pointing to the CSV file. + /// + TextReader reader; + + /// + /// Contains the buffer size. + /// + int bufferSize; + + /// + /// Contains the comment character indicating that a line is commented out. + /// + char comment; + + /// + /// Contains the escape character letting insert quotation characters inside a quoted field. + /// + char escape; + + /// + /// Contains the delimiter character separating each field. + /// + char delimiter; + + /// + /// Contains the quotation character wrapping every field. + /// + char quote; + + /// + /// Determines which values should be trimmed. + /// + ValueTrimmingOptions trimmingOptions; + + /// + /// Indicates if field names are located on the first non commented line. + /// + bool hasHeaders; + + #endregion + + #region State + + /// + /// Indicates if the class is initialized. + /// + bool initialized; + + /// + /// Contains the field headers. + /// + string[] fieldHeaders; + + /// + /// Contains the dictionary of field indexes by header. The key is the field name and the value is its index. + /// + Dictionary fieldHeaderIndexes; + + /// + /// Contains the current record index in the CSV file. + /// A value of means that the reader has not been initialized yet. + /// Otherwise, a negative value means that no record has been read yet. + /// + long currentRecordIndex; + + /// + /// Contains the starting position of the next unread field. + /// + int nextFieldStart; + + /// + /// Contains the index of the next unread field. + /// + int nextFieldIndex; + + /// + /// Contains the array of the field values for the current record. + /// A null value indicates that the field have not been parsed. + /// + string[] fields; + + /// + /// Contains the maximum number of fields to retrieve for each record. + /// + int fieldCount; + + /// + /// Contains the read buffer. + /// + char[] buffer; + + /// + /// Contains the current read buffer length. + /// + int bufferLength; + + /// + /// Indicates if the end of the reader has been reached. + /// + bool eof; + + /// + /// Indicates if the last read operation reached an EOL character. + /// + bool eol; + + /// + /// Indicates if the first record is in cache. + /// This can happen when initializing a reader with no headers + /// because one record must be read to get the field count automatically + /// + bool firstRecordInCache; + + /// + /// Indicates if one or more field are missing for the current record. + /// Resets after each successful record read. + /// + bool missingFieldFlag; + + /// + /// Indicates if a parse error occured for the current record. + /// Resets after each successful record read. + /// + bool parseErrorFlag; + + #endregion + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the CsvReader class. + /// + /// A pointing to the CSV file. + /// if field names are located on the first non commented line, otherwise, . + /// + /// is a . + /// + /// + /// Cannot read from . + /// + public CsvDataReader(TextReader reader, bool hasHeaders) + : this(reader, hasHeaders, DefaultDelimiter, DefaultQuote, DefaultEscape, DefaultComment, ValueTrimmingOptions.UnquotedOnly, DefaultBufferSize) + { + } + + /// + /// Initializes a new instance of the CsvReader class. + /// + /// A pointing to the CSV file. + /// if field names are located on the first non commented line, otherwise, . + /// The buffer size in bytes. + /// + /// is a . + /// + /// + /// Cannot read from . + /// + public CsvDataReader(TextReader reader, bool hasHeaders, int bufferSize) + : this(reader, hasHeaders, DefaultDelimiter, DefaultQuote, DefaultEscape, DefaultComment, ValueTrimmingOptions.UnquotedOnly, bufferSize) + { + } + + /// + /// Initializes a new instance of the CsvReader class. + /// + /// A pointing to the CSV file. + /// if field names are located on the first non commented line, otherwise, . + /// The delimiter character separating each field (default is ','). + /// + /// is a . + /// + /// + /// Cannot read from . + /// + public CsvDataReader(TextReader reader, bool hasHeaders, char delimiter) + : this(reader, hasHeaders, delimiter, DefaultQuote, DefaultEscape, DefaultComment, ValueTrimmingOptions.UnquotedOnly, DefaultBufferSize) + { + } + + /// + /// Initializes a new instance of the CsvReader class. + /// + /// A pointing to the CSV file. + /// if field names are located on the first non commented line, otherwise, . + /// The delimiter character separating each field (default is ','). + /// The buffer size in bytes. + /// + /// is a . + /// + /// + /// Cannot read from . + /// + public CsvDataReader(TextReader reader, bool hasHeaders, char delimiter, int bufferSize) + : this(reader, hasHeaders, delimiter, DefaultQuote, DefaultEscape, DefaultComment, ValueTrimmingOptions.UnquotedOnly, bufferSize) + { + } + + /// + /// Initializes a new instance of the CsvReader class. + /// + /// A pointing to the CSV file. + /// if field names are located on the first non commented line, otherwise, . + /// The delimiter character separating each field (default is ','). + /// The quotation character wrapping every field (default is '''). + /// + /// The escape character letting insert quotation characters inside a quoted field (default is '\'). + /// If no escape character, set to '\0' to gain some performance. + /// + /// The comment character indicating that a line is commented out (default is '#'). + /// Determines which values should be trimmed. + /// + /// is a . + /// + /// + /// Cannot read from . + /// + public CsvDataReader(TextReader reader, bool hasHeaders, char delimiter, char quote, char escape, char comment, ValueTrimmingOptions trimmingOptions) + : this(reader, hasHeaders, delimiter, quote, escape, comment, trimmingOptions, DefaultBufferSize) + { + } + + /// + /// Initializes a new instance of the CsvReader class. + /// + /// A pointing to the CSV file. + /// if field names are located on the first non commented line, otherwise, . + /// The delimiter character separating each field (default is ','). + /// The quotation character wrapping every field (default is '''). + /// + /// The escape character letting insert quotation characters inside a quoted field (default is '\'). + /// If no escape character, set to '\0' to gain some performance. + /// + /// The comment character indicating that a line is commented out (default is '#'). + /// Determines which values should be trimmed. + /// The buffer size in bytes. + /// + /// is a . + /// + /// + /// must be 1 or more. + /// + public CsvDataReader(TextReader reader, bool hasHeaders, char delimiter, char quote, char escape, char comment, ValueTrimmingOptions trimmingOptions, int bufferSize) + { +#if DEBUG + allocStack = new System.Diagnostics.StackTrace(); +#endif + + if (reader == null) + throw new ArgumentNullException(nameof(reader)); + + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException("bufferSize", bufferSize, ExceptionMessage.BufferSizeTooSmall); + + this.bufferSize = bufferSize; + + if (reader is StreamReader) + { + var stream = ((StreamReader)reader).BaseStream; + + if (stream.CanSeek) + { + // Handle bad implementations returning 0 or less + if (stream.Length > 0) + this.bufferSize = (int)Math.Min(bufferSize, stream.Length); + } + } + + this.reader = reader; + this.delimiter = delimiter; + this.quote = quote; + this.escape = escape; + this.comment = comment; + + this.hasHeaders = hasHeaders; + this.trimmingOptions = trimmingOptions; + SupportsMultiline = true; + SkipEmptyLines = true; + DefaultHeaderName = "Column"; + + currentRecordIndex = -1; + DefaultParseErrorAction = ParseErrorAction.RaiseEvent; + } + + #endregion + + #region Events + + /// + /// Occurs when there is an error while parsing the CSV stream. + /// + public event EventHandler ParseError; + + /// + /// Raises the event. + /// + /// The that contains the event data. + protected virtual void OnParseError(ParseErrorEventArgs e) => ParseError?.Invoke(this, e); + + #endregion + + #region Properties + + #region Settings + + /// + /// Gets the comment character indicating that a line is commented out. + /// + /// The comment character indicating that a line is commented out. + public char Comment => comment; + + /// + /// Gets the escape character letting insert quotation characters inside a quoted field. + /// + /// The escape character letting insert quotation characters inside a quoted field. + public char Escape => escape; + + /// + /// Gets the delimiter character separating each field. + /// + /// The delimiter character separating each field. + public char Delimiter => delimiter; + + /// + /// Gets the quotation character wrapping every field. + /// + /// The quotation character wrapping every field. + public char Quote => quote; + + /// + /// Indicates if field names are located on the first non commented line. + /// + /// if field names are located on the first non commented line, otherwise, . + public bool HasHeaders => hasHeaders; + + /// + /// Indicates if spaces at the start and end of a field are trimmed. + /// + /// if spaces at the start and end of a field are trimmed, otherwise, . + public ValueTrimmingOptions TrimmingOption => trimmingOptions; + + /// + /// Gets the buffer size. + /// + public int BufferSize => bufferSize; + + /// + /// Gets or sets the default action to take when a parsing error has occured. + /// + /// The default action to take when a parsing error has occured. + public ParseErrorAction DefaultParseErrorAction { get; set; } + + /// + /// Gets or sets the action to take when a field is missing. + /// + /// The action to take when a field is missing. + public MissingFieldAction MissingFieldAction { get; set; } + + /// + /// Gets or sets a value indicating if the reader supports multiline fields. + /// + /// A value indicating if the reader supports multiline field. + public bool SupportsMultiline { get; set; } + + /// + /// Gets or sets a value indicating if the reader will skip empty lines. + /// + /// A value indicating if the reader will skip empty lines. + public bool SkipEmptyLines { get; set; } + + /// + /// Gets or sets the default header name when it is an empty string or only whitespaces. + /// The header index will be appended to the specified name. + /// + /// The default header name when it is an empty string or only whitespaces. + public string DefaultHeaderName { get; set; } + + #endregion + + #region State + + /// + /// Gets the maximum number of fields to retrieve for each record. + /// + /// The maximum number of fields to retrieve for each record. + /// + /// The instance has been disposed of. + /// + public int FieldCount + { + get + { + EnsureInitialize(); + return fieldCount; + } + } + + /// + /// Gets a value that indicates whether the current stream position is at the end of the stream. + /// + /// if the current stream position is at the end of the stream; otherwise . + public virtual bool EndOfStream => eof; + + /// + /// Gets the field headers. + /// + /// The field headers or an empty array if headers are not supported. + /// + /// The instance has been disposed of. + /// + public string[] GetFieldHeaders() + { + EnsureInitialize(); + Debug.Assert(this.fieldHeaders != null, "Field headers must be non null."); + + var fieldHeaders = new string[this.fieldHeaders.Length]; + + for (int i = 0; i < fieldHeaders.Length; i++) + fieldHeaders[i] = this.fieldHeaders[i]; + + return fieldHeaders; + } + + /// + /// Gets the current record index in the CSV file. + /// + /// The current record index in the CSV file. + public virtual long CurrentRecordIndex => currentRecordIndex; + + /// + /// Indicates if one or more field are missing for the current record. + /// Resets after each successful record read. + /// + public bool MissingFieldFlag => missingFieldFlag; + + /// + /// Indicates if a parse error occured for the current record. + /// Resets after each successful record read. + /// + public bool ParseErrorFlag => parseErrorFlag; + + #endregion + + #endregion + + #region Indexers + + /// + /// Gets the field with the specified name and record position. must be . + /// + /// + /// The field with the specified name and record position. + /// + /// + /// is or an empty string. + /// + /// + /// The CSV does not have headers ( property is ). + /// + /// + /// not found. + /// + /// + /// Record index must be > 0. + /// + /// + /// Cannot move to a previous record in forward-only mode. + /// + /// + /// Cannot read record at . + /// + /// + /// The CSV appears to be corrupt at the current position. + /// + /// + /// The instance has been disposed of. + /// + public string this[int record, string field] + { + get + { + if (!MoveTo(record)) + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, ExceptionMessage.CannotReadRecordAtIndex, record)); + + return this[field]; + } + } + + /// + /// Gets the field at the specified index and record position. + /// + /// + /// The field at the specified index and record position. + /// A is returned if the field cannot be found for the record. + /// + /// + /// must be included in [0, [. + /// + /// + /// Record index must be > 0. + /// + /// + /// Cannot move to a previous record in forward-only mode. + /// + /// + /// Cannot read record at . + /// + /// + /// The CSV appears to be corrupt at the current position. + /// + /// + /// The instance has been disposed of. + /// + public string this[int record, int field] + { + get + { + if (!MoveTo(record)) + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, ExceptionMessage.CannotReadRecordAtIndex, record)); + + return this[field]; + } + } + + /// + /// Gets the field with the specified name. must be . + /// + /// + /// The field with the specified name. + /// + /// + /// is or an empty string. + /// + /// + /// The CSV does not have headers ( property is ). + /// + /// + /// not found. + /// + /// + /// The CSV appears to be corrupt at the current position. + /// + /// + /// The instance has been disposed of. + /// + public string this[string field] + { + get + { + if (string.IsNullOrEmpty(field)) + throw new ArgumentNullException(nameof(field)); + + if (!hasHeaders) + throw new InvalidOperationException(ExceptionMessage.NoHeaders); + + int index = GetFieldIndex(field); + + if (index < 0) + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, ExceptionMessage.FieldHeaderNotFound, field), "field"); + + return this[index]; + } + } + + /// + /// Gets the field at the specified index. + /// + /// The field at the specified index. + /// + /// must be included in [0, [. + /// + /// + /// No record read yet. Call ReadLine() first. + /// + /// + /// The CSV appears to be corrupt at the current position. + /// + /// + /// The instance has been disposed of. + /// + public virtual string this[int field] + { + get + { + return ReadField(field, initializing: false, discardValue: false); + } + } + + #endregion + + #region Methods + + #region EnsureInitialize + + /// + /// Ensures that the reader is initialized. + /// + void EnsureInitialize() + { + if (!initialized) + ReadNextRecord(onlyReadHeaders: true, skipToNextLine: false); + + Debug.Assert(fieldHeaders != null); + Debug.Assert(fieldHeaders.Length > 0 || (fieldHeaders.Length == 0 && fieldHeaderIndexes == null)); + } + + #endregion + + #region GetFieldIndex + + /// + /// Gets the field index for the provided header. + /// + /// The header to look for. + /// The field index for the provided header. -1 if not found. + /// + /// The instance has been disposed of. + /// + public int GetFieldIndex(string header) + { + EnsureInitialize(); + + int index; + + if (fieldHeaderIndexes != null && fieldHeaderIndexes.TryGetValue(header, out index)) + return index; + else + return -1; + } + + #endregion + + #region CopyCurrentRecordTo + + /// + /// Copies the field array of the current record to a one-dimensional array, starting at the beginning of the target array. + /// + /// The one-dimensional array that is the destination of the fields of the current record. + public void CopyCurrentRecordTo(string[] array) => CopyCurrentRecordTo(array, 0); + + /// + /// Copies the field array of the current record to a one-dimensional array, starting at the beginning of the target array. + /// + /// The one-dimensional that is the destination of the fields of the current record. + /// The zero-based index in at which copying begins. + /// + /// is . + /// + /// + /// is les than zero or is equal to or greater than the length . + /// + /// + /// No current record. + /// + /// + /// The number of fields in the record is greater than the available space from to the end of . + /// + public void CopyCurrentRecordTo(string[] array, int index) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + if (index < 0 || index >= array.Length) + throw new ArgumentOutOfRangeException("index", index, string.Empty); + + if (currentRecordIndex < 0 || !initialized) + throw new InvalidOperationException(ExceptionMessage.NoCurrentRecord); + + if (array.Length - index < fieldCount) + throw new ArgumentException(ExceptionMessage.NotEnoughSpaceInArray, "array"); + + for (int i = 0; i < fieldCount; i++) + { + if (parseErrorFlag) + array[index + i] = null; + else + array[index + i] = this[i]; + } + } + + #endregion + + #region GetCurrentRawData + + /// + /// Gets the current raw CSV data. + /// + /// Used for exception handling purpose. + /// The current raw CSV data. + public string GetCurrentRawData() + { + if (buffer != null && bufferLength > 0) + return new string(buffer, 0, bufferLength); + else + return string.Empty; + } + + #endregion + + #region IsWhiteSpace + + /// + /// Indicates whether the specified Unicode character is categorized as white space. + /// + bool IsWhiteSpace(char character) + { + // Handle cases where the delimiter is a whitespace (e.g. tab) + if (character == delimiter) + return false; + else + { + // See char.IsLatin1(char c) in Reflector + if (character <= '\x00ff') + return (character == ' ' || character == '\t'); + else + return (System.Globalization.CharUnicodeInfo.GetUnicodeCategory(character) == System.Globalization.UnicodeCategory.SpaceSeparator); + } + } + + #endregion + + #region MoveTo + + /// + /// Moves to the specified record index. + /// + /// The record index. + /// true if the operation was successful; otherwise, false. + /// + /// The instance has been disposed of. + /// + public virtual bool MoveTo(long record) + { + if (record < currentRecordIndex) return false; + + // Get number of record to read + long offset = record - currentRecordIndex; + + while (offset > 0) + { + if (!ReadNextRecord()) return false; + + offset--; + } + + return true; + } + + #endregion + + #region ParseNewLine + + /// + /// Parses a new line delimiter. + /// + /// The starting position of the parsing. Will contain the resulting end position. + /// if a new line delimiter was found; otherwise, . + /// + /// The instance has been disposed of. + /// + bool ParseNewLine(ref int pos) + { + Debug.Assert(pos <= bufferLength); + + // Check if already at the end of the buffer + if (pos == bufferLength) + { + pos = 0; + + if (!ReadBuffer()) return false; + } + + var character = buffer[pos]; + + // Treat \r as new line only if it's not the delimiter + + if (character == '\r' && delimiter != '\r') + { + pos++; + + // Skip following \n (if there is one) + + if (pos < bufferLength) + { + if (buffer[pos] == '\n') pos++; + } + else + { + if (ReadBuffer()) + { + if (buffer[0] == '\n') + pos = 1; + else + pos = 0; + } + } + + if (pos >= bufferLength) + { + ReadBuffer(); + pos = 0; + } + + return true; + } + else if (character == '\n') + { + pos++; + + if (pos >= bufferLength) + { + ReadBuffer(); + pos = 0; + } + + return true; + } + + return false; + } + + /// + /// Determines whether the character at the specified position is a new line delimiter. + /// + /// The position of the character to verify. + /// + /// if the character at the specified position is a new line delimiter; otherwise, . + /// + bool IsNewLine(int pos) + { + Debug.Assert(pos < bufferLength); + + var c = buffer[pos]; + + if (c == '\n') return true; + else if (c == '\r' && delimiter != '\r') + return true; + else + return false; + } + + #endregion + + #region ReadBuffer + + /// + /// Fills the buffer with data from the reader. + /// + /// if data was successfully read; otherwise, . + /// + /// The instance has been disposed of. + /// + bool ReadBuffer() + { + if (eof) return false; + + CheckDisposed(); + + bufferLength = reader.Read(buffer, 0, bufferSize); + + if (bufferLength > 0) return true; + else + { + eof = true; + buffer = null; + + return false; + } + } + + #endregion + + #region ReadField + + /// + /// Reads the field at the specified index. + /// Any unread fields with an inferior index will also be read as part of the required parsing. + /// + /// The field index. + /// Indicates if the reader is currently initializing. + /// Indicates if the value(s) are discarded. + /// + /// The field at the specified index. + /// A indicates that an error occured or that the last field has been reached during initialization. + /// + /// + /// is out of range. + /// + /// + /// There is no current record. + /// + /// + /// The CSV data appears to be missing a field. + /// + /// + /// The CSV data appears to be malformed. + /// + /// + /// The instance has been disposed of. + /// + string ReadField(int field, bool initializing, bool discardValue) + { + if (!initializing) + { + if (field < 0 || field >= fieldCount) + throw new ArgumentOutOfRangeException("field", field, string.Format(CultureInfo.InvariantCulture, ExceptionMessage.FieldIndexOutOfRange, field)); + + if (currentRecordIndex < 0) + throw new InvalidOperationException(ExceptionMessage.NoCurrentRecord); + + // Directly return field if cached + if (fields[field] != null) + return fields[field]; + else if (missingFieldFlag) + return HandleMissingField(null, field, ref nextFieldStart); + } + + CheckDisposed(); + + int index = nextFieldIndex; + + while (index < field + 1) + { + // Handle case where stated start of field is past buffer + // This can occur because _nextFieldStart is simply 1 + last char position of previous field + if (nextFieldStart == bufferLength) + { + nextFieldStart = 0; + + // Possible EOF will be handled later (see Handle_EOF1) + ReadBuffer(); + } + + string value = null; + + if (missingFieldFlag) + { + value = HandleMissingField(value, index, ref nextFieldStart); + } + else if (nextFieldStart == bufferLength) + { + // Handle_EOF1: Handle EOF here + + // If current field is the requested field, then the value of the field is "" as in "f1,f2,f3,(\s*)" + // otherwise, the CSV is malformed + + if (index == field) + { + if (!discardValue) + { + value = string.Empty; + fields[index] = value; + } + + missingFieldFlag = true; + } + else + { + value = HandleMissingField(value, index, ref nextFieldStart); + } + } + else + { + // Trim spaces at start + if ((trimmingOptions & ValueTrimmingOptions.UnquotedOnly) != 0) + SkipWhiteSpaces(ref nextFieldStart); + + if (eof) + { + value = string.Empty; + fields[field] = value; + } + else if (buffer[nextFieldStart] != quote) + { + // Non-quoted field + + int start = nextFieldStart; + int pos = nextFieldStart; + + for (; ; ) + { + while (pos < bufferLength) + { + var c = buffer[pos]; + + if (c == delimiter) + { + nextFieldStart = pos + 1; + + break; + } + else if (c == '\r' || c == '\n') + { + nextFieldStart = pos; + eol = true; + + break; + } + else + pos++; + } + + if (pos < bufferLength) break; + else + { + if (!discardValue) + value += new string(buffer, start, pos - start); + + start = 0; + pos = 0; + nextFieldStart = 0; + + if (!ReadBuffer()) + break; + } + } + + if (!discardValue) + { + if ((trimmingOptions & ValueTrimmingOptions.UnquotedOnly) == 0) + { + if (!eof && pos > start) + value += new string(buffer, start, pos - start); + } + else + { + if (!eof && pos > start) + { + // Do the trimming + pos--; + while (pos > -1 && IsWhiteSpace(buffer[pos])) + pos--; + pos++; + + if (pos > 0) + value += new string(buffer, start, pos - start); + } + else + pos = -1; + + // If pos <= 0, that means the trimming went past buffer start, + // and the concatenated value needs to be trimmed too. + if (pos <= 0) + { + pos = (value == null ? -1 : value.Length - 1); + + // Do the trimming + while (pos > -1 && IsWhiteSpace(value[pos])) + pos--; + + pos++; + + if (pos > 0 && pos != value.Length) + value = value.Substring(0, pos); + } + } + + if (value == null) value = string.Empty; + } + + if (eol || eof) + { + eol = ParseNewLine(ref nextFieldStart); + + // Reaching a new line is ok as long as the parser is initializing or it is the last field + if (!initializing && index != fieldCount - 1) + { + if (value != null && value.Length == 0) + value = null; + + value = HandleMissingField(value, index, ref nextFieldStart); + } + } + + if (!discardValue) fields[index] = value; + } + else + { + // Quoted field + + // Skip quote + int start = nextFieldStart + 1; + int pos = start; + + bool quoted = true; + bool escaped = false; + + if ((trimmingOptions & ValueTrimmingOptions.QuotedOnly) != 0) + { + SkipWhiteSpaces(ref start); + pos = start; + } + + for (; ; ) + { + while (pos < bufferLength) + { + var c = buffer[pos]; + + if (escaped) + { + escaped = false; + start = pos; + } + // IF current char is escape AND (escape and quote are different OR next char is a quote) + else if (c == escape && (escape != quote || (pos + 1 < bufferLength && buffer[pos + 1] == quote) || (pos + 1 == bufferLength && reader.Peek() == quote))) + { + if (!discardValue) + value += new string(buffer, start, pos - start); + + escaped = true; + } + else if (c == quote) + { + quoted = false; + break; + } + + pos++; + } + + if (!quoted) break; + else + { + if (!discardValue && !escaped) + value += new string(buffer, start, pos - start); + + start = 0; + pos = 0; + nextFieldStart = 0; + + if (!ReadBuffer()) + { + HandleParseError(new MalformedCsvException(GetCurrentRawData(), nextFieldStart, Math.Max(0, currentRecordIndex), index), ref nextFieldStart); + return null; + } + } + } + + if (!eof) + { + // Append remaining parsed buffer content + if (!discardValue && pos > start) + value += new string(buffer, start, pos - start); + + if (!discardValue && value != null && (trimmingOptions & ValueTrimmingOptions.QuotedOnly) != 0) + { + int newLength = value.Length; + while (newLength > 0 && IsWhiteSpace(value[newLength - 1])) + newLength--; + + if (newLength < value.Length) + value = value.Substring(0, newLength); + } + + // Skip quote + nextFieldStart = pos + 1; + + // Skip whitespaces between the quote and the delimiter/eol + SkipWhiteSpaces(ref nextFieldStart); + + // Skip delimiter + bool delimiterSkipped; + + if (nextFieldStart < bufferLength && buffer[nextFieldStart] == delimiter) + { + nextFieldStart++; + delimiterSkipped = true; + } + else + { + delimiterSkipped = false; + } + + // Skip new line delimiter if initializing or last field + // (if the next field is missing, it will be caught when parsed) + if (!eof && !delimiterSkipped && (initializing || index == fieldCount - 1)) + eol = ParseNewLine(ref nextFieldStart); + + // If no delimiter is present after the quoted field and it is not the last field, then it is a parsing error + if (!delimiterSkipped && !eof && !(eol || IsNewLine(nextFieldStart))) + HandleParseError(new MalformedCsvException(GetCurrentRawData(), nextFieldStart, Math.Max(0, currentRecordIndex), index), ref nextFieldStart); + } + + if (!discardValue) + { + if (value == null) + value = string.Empty; + + fields[index] = value; + } + } + } + + nextFieldIndex = Math.Max(index + 1, nextFieldIndex); + + if (index == field) + { + // If initializing, return null to signify the last field has been reached + + if (initializing) + { + if (eol || eof) + return null; + else + return string.IsNullOrEmpty(value) ? string.Empty : value; + } + else + return value; + } + + index++; + } + + // Getting here is bad ... + HandleParseError(new MalformedCsvException(GetCurrentRawData(), nextFieldStart, Math.Max(0, currentRecordIndex), index), ref nextFieldStart); + return null; + } + + #endregion + + #region ReadNextRecord + + /// + /// Reads the next record. + /// + /// if a record has been successfully reads; otherwise, . + /// + /// The instance has been disposed of. + /// + public bool ReadNextRecord() => ReadNextRecord(onlyReadHeaders: false, skipToNextLine: false); + + /// + /// Reads the next record. + /// + /// + /// Indicates if the reader will proceed to the next record after having read headers. + /// if it stops after having read headers; otherwise, . + /// + /// + /// Indicates if the reader will skip directly to the next line without parsing the current one. + /// To be used when an error occurs. + /// + /// if a record has been successfully reads; otherwise, . + /// + /// The instance has been disposed of. + /// + protected virtual bool ReadNextRecord(bool onlyReadHeaders, bool skipToNextLine) + { + if (eof) + { + if (firstRecordInCache) + { + firstRecordInCache = false; + currentRecordIndex++; + + return true; + } + else + return false; + } + + CheckDisposed(); + + if (!initialized) + { + buffer = new char[bufferSize]; + + // will be replaced if and when headers are read + fieldHeaders = new string[0]; + + if (!ReadBuffer()) return false; + + if (!SkipEmptyAndCommentedLines(ref nextFieldStart)) + return false; + + // Keep growing _fields array until the last field has been found + // and then resize it to its final correct size + + fieldCount = 0; + fields = new string[16]; + + while (ReadField(fieldCount, initializing: true, discardValue: false) != null) + { + if (parseErrorFlag) + { + fieldCount = 0; + Array.Clear(fields, 0, fields.Length); + parseErrorFlag = false; + nextFieldIndex = 0; + } + else + { + fieldCount++; + + if (fieldCount == fields.Length) + Array.Resize(ref fields, (fieldCount + 1) * 2); + } + } + + // _fieldCount contains the last field index, but it must contains the field count, + // so increment by 1 + fieldCount++; + + if (fields.Length != fieldCount) + Array.Resize(ref fields, fieldCount); + + initialized = true; + + // If headers are present, call ReadNextRecord again + if (hasHeaders) + { + // Don't count first record as it was the headers + currentRecordIndex = -1; + + firstRecordInCache = false; + + fieldHeaders = new string[fieldCount]; + fieldHeaderIndexes = new Dictionary(fieldCount, fieldHeaderComparer); + + for (int i = 0; i < fields.Length; i++) + { + string headerName = fields[i]; + if (string.IsNullOrEmpty(headerName) || headerName.Trim().Length == 0) + headerName = DefaultHeaderName + i.ToString(); + + fieldHeaders[i] = headerName; + fieldHeaderIndexes.Add(headerName, i); + } + + // Proceed to first record + if (!onlyReadHeaders) + { + // Calling again ReadNextRecord() seems to be simpler, + // but in fact would probably cause many subtle bugs because a derived class does not expect a recursive behavior + // so simply do what is needed here and no more. + + if (!SkipEmptyAndCommentedLines(ref nextFieldStart)) + return false; + + Array.Clear(fields, 0, fields.Length); + nextFieldIndex = 0; + eol = false; + + currentRecordIndex++; + return true; + } + } + else + { + if (onlyReadHeaders) + { + firstRecordInCache = true; + currentRecordIndex = -1; + } + else + { + firstRecordInCache = false; + currentRecordIndex = 0; + } + } + } + else + { + if (skipToNextLine) + SkipToNextLine(ref nextFieldStart); + else if (currentRecordIndex > -1 && !missingFieldFlag) + { + // If not already at end of record, move there + if (!eol && !eof) + { + if (!SupportsMultiline) + SkipToNextLine(ref nextFieldStart); + else + { + // a dirty trick to handle the case where extra fields are present + while (ReadField(nextFieldIndex, initializing: true, discardValue: true) != null) + { + } + } + } + } + + if (!firstRecordInCache && !SkipEmptyAndCommentedLines(ref nextFieldStart)) + return false; + + if (hasHeaders || !firstRecordInCache) + eol = false; + + // Check to see if the first record is in cache. + // This can happen when initializing a reader with no headers + // because one record must be read to get the field count automatically + if (firstRecordInCache) + firstRecordInCache = false; + else + { + Array.Clear(fields, 0, fields.Length); + nextFieldIndex = 0; + } + + missingFieldFlag = false; + parseErrorFlag = false; + currentRecordIndex++; + } + + return true; + } + + #endregion + + #region SkipEmptyAndCommentedLines + + /// + /// Skips empty and commented lines. + /// If the end of the buffer is reached, its content be discarded and filled again from the reader. + /// + /// + /// The position in the buffer where to start parsing. + /// Will contains the resulting position after the operation. + /// + /// if the end of the reader has not been reached; otherwise, . + /// + /// The instance has been disposed of. + /// + bool SkipEmptyAndCommentedLines(ref int pos) + { + if (pos < bufferLength) + DoSkipEmptyAndCommentedLines(ref pos); + + while (pos >= bufferLength && !eof) + { + if (ReadBuffer()) + { + pos = 0; + DoSkipEmptyAndCommentedLines(ref pos); + } + else + return false; + } + + return !eof; + } + + /// + /// Worker method. + /// Skips empty and commented lines. + /// + /// + /// The position in the buffer where to start parsing. + /// Will contains the resulting position after the operation. + /// + /// + /// The instance has been disposed of. + /// + void DoSkipEmptyAndCommentedLines(ref int pos) + { + while (pos < bufferLength) + { + if (buffer[pos] == comment) + { + pos++; + SkipToNextLine(ref pos); + } + else if (SkipEmptyLines && ParseNewLine(ref pos)) + continue; + else + break; + } + } + + #endregion + + #region SkipWhiteSpaces + + /// + /// Skips whitespace characters. + /// + /// The starting position of the parsing. Will contain the resulting end position. + /// if the end of the reader has not been reached; otherwise, . + /// + /// The instance has been disposed of. + /// + bool SkipWhiteSpaces(ref int pos) + { + for (; ; ) + { + while (pos < bufferLength && IsWhiteSpace(buffer[pos])) + pos++; + + if (pos < bufferLength) break; + else + { + pos = 0; + + if (!ReadBuffer()) + return false; + } + } + + return true; + } + + #endregion + + #region SkipToNextLine + + /// + /// Skips ahead to the next NewLine character. + /// If the end of the buffer is reached, its content be discarded and filled again from the reader. + /// + /// + /// The position in the buffer where to start parsing. + /// Will contains the resulting position after the operation. + /// + /// if the end of the reader has not been reached; otherwise, . + /// + /// The instance has been disposed of. + /// + bool SkipToNextLine(ref int pos) + { + // ((pos = 0) == 0) is a little trick to reset position inline + while ((pos < bufferLength || (ReadBuffer() && ((pos = 0) == 0))) && !ParseNewLine(ref pos)) + pos++; + + return !eof; + } + + #endregion + + #region HandleParseError + + /// + /// Handles a parsing error. + /// + /// The parsing error that occured. + /// The current position in the buffer. + /// + /// is . + /// + void HandleParseError(MalformedCsvException error, ref int pos) + { + if (error == null) + throw new ArgumentNullException("error"); + + parseErrorFlag = true; + + switch (DefaultParseErrorAction) + { + case ParseErrorAction.ThrowException: + throw error; + + case ParseErrorAction.RaiseEvent: + var e = new ParseErrorEventArgs(error, ParseErrorAction.ThrowException); + OnParseError(e); + + switch (e.Action) + { + case ParseErrorAction.ThrowException: + throw e.Error; + + case ParseErrorAction.RaiseEvent: + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, ExceptionMessage.ParseErrorActionInvalidInsideParseErrorEvent, e.Action), e.Error); + + case ParseErrorAction.AdvanceToNextLine: + // already at EOL when fields are missing, so don't skip to next line in that case + if (!missingFieldFlag && pos >= 0) + SkipToNextLine(ref pos); + break; + + default: + throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, ExceptionMessage.ParseErrorActionNotSupported, e.Action), e.Error); + } + + break; + + case ParseErrorAction.AdvanceToNextLine: + // already at EOL when fields are missing, so don't skip to next line in that case + if (!missingFieldFlag && pos >= 0) + SkipToNextLine(ref pos); + break; + + default: + throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, ExceptionMessage.ParseErrorActionNotSupported, DefaultParseErrorAction), error); + } + } + + #endregion + + #region HandleMissingField + + /// + /// Handles a missing field error. + /// + /// The partially parsed value, if available. + /// The missing field index. + /// The current position in the raw data. + /// + /// The resulting value according to . + /// If the action is set to , + /// then the parse error will be handled according to . + /// + string HandleMissingField(string value, int fieldIndex, ref int currentPosition) + { + if (fieldIndex < 0 || fieldIndex >= fieldCount) + throw new ArgumentOutOfRangeException("fieldIndex", fieldIndex, string.Format(CultureInfo.InvariantCulture, ExceptionMessage.FieldIndexOutOfRange, fieldIndex)); + + missingFieldFlag = true; + + for (int i = fieldIndex + 1; i < fieldCount; i++) + fields[i] = null; + + if (value != null) + return value; + else + { + switch (MissingFieldAction) + { + case MissingFieldAction.ParseError: + HandleParseError(new MissingFieldCsvException(GetCurrentRawData(), currentPosition, Math.Max(0, currentRecordIndex), fieldIndex), ref currentPosition); + return value; + + case MissingFieldAction.ReplaceByEmpty: + return string.Empty; + + case MissingFieldAction.ReplaceByNull: + return null; + + default: + throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, ExceptionMessage.MissingFieldActionNotSupported, MissingFieldAction)); + } + } + } + + #endregion + + #endregion + + #region IDataReader support methods + + /// + /// Validates the state of the data reader. + /// + /// The validations to accomplish. + /// + /// No current record. + /// + /// + /// This operation is invalid when the reader is closed. + /// + void ValidateDataReader(DataReaderValidations validations) + { + if ((validations & DataReaderValidations.IsInitialized) != 0 && !initialized) + throw new InvalidOperationException(ExceptionMessage.NoCurrentRecord); + + if ((validations & DataReaderValidations.IsNotClosed) != 0 && isDisposed) + throw new InvalidOperationException(ExceptionMessage.ReaderClosed); + } + + /// + /// Copy the value of the specified field to an array. + /// + /// The index of the field. + /// The offset in the field value. + /// The destination array where the field value will be copied. + /// The destination array offset. + /// The number of characters to copy from the field value. + /// + long CopyFieldToArray(int field, long fieldOffset, Array destinationArray, int destinationOffset, int length) + { + EnsureInitialize(); + + if (field < 0 || field >= fieldCount) + throw new ArgumentOutOfRangeException("field", field, string.Format(CultureInfo.InvariantCulture, ExceptionMessage.FieldIndexOutOfRange, field)); + + if (fieldOffset < 0 || fieldOffset >= int.MaxValue) + throw new ArgumentOutOfRangeException("fieldOffset"); + + // Array.Copy(...) will do the remaining argument checks + + if (length == 0) + return 0; + + var value = this[field] ?? string.Empty; + + Debug.Assert(fieldOffset < int.MaxValue); + + Debug.Assert(destinationArray.GetType() == typeof(char[]) || destinationArray.GetType() == typeof(byte[])); + + if (destinationArray.GetType() == typeof(char[])) + Array.Copy(value.ToCharArray((int)fieldOffset, length), 0, destinationArray, destinationOffset, length); + else + { + var chars = value.ToCharArray((int)fieldOffset, length); + var source = new byte[chars.Length]; ; + + for (int i = 0; i < chars.Length; i++) + source[i] = Convert.ToByte(chars[i]); + + Array.Copy(source, 0, destinationArray, destinationOffset, length); + } + + return length; + } + + #endregion + + #region IDataReader Members + + int IDataReader.RecordsAffected => -1; + + bool IDataReader.IsClosed => eof; + + bool IDataReader.NextResult() + { + ValidateDataReader(DataReaderValidations.IsNotClosed); + + return false; + } + + void IDataReader.Close() => Dispose(); + + bool IDataReader.Read() + { + ValidateDataReader(DataReaderValidations.IsNotClosed); + + return ReadNextRecord(); + } + + int IDataReader.Depth + { + get + { + ValidateDataReader(DataReaderValidations.IsNotClosed); + + return 0; + } + } + + DataTable IDataReader.GetSchemaTable() + { + EnsureInitialize(); + ValidateDataReader(DataReaderValidations.IsNotClosed); + + var schema = new DataTable("SchemaTable") + { + Locale = CultureInfo.InvariantCulture, + MinimumCapacity = fieldCount + }; + + schema.Columns.Add(SchemaTableColumn.AllowDBNull, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.BaseColumnName, typeof(string)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.BaseSchemaName, typeof(string)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.BaseTableName, typeof(string)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.ColumnName, typeof(string)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.ColumnSize, typeof(int)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.DataType, typeof(object)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.IsAliased, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.IsExpression, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.IsKey, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.IsLong, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.IsUnique, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.NumericPrecision, typeof(short)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.NumericScale, typeof(short)).ReadOnly = true; + schema.Columns.Add(SchemaTableColumn.ProviderType, typeof(int)).ReadOnly = true; + + schema.Columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(string)).ReadOnly = true; + schema.Columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string)).ReadOnly = true; + schema.Columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(bool)).ReadOnly = true; + schema.Columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(bool)).ReadOnly = true; + + string[] columnNames; + + if (hasHeaders) columnNames = fieldHeaders; + else + { + columnNames = new string[fieldCount]; + + for (int i = 0; i < fieldCount; i++) + columnNames[i] = "Column" + i.ToString(CultureInfo.InvariantCulture); + } + + // null marks columns that will change for each row + object[] schemaRow = new object[] { + true, // 00- AllowDBNull + null, // 01- BaseColumnName + string.Empty, // 02- BaseSchemaName + string.Empty, // 03- BaseTableName + null, // 04- ColumnName + null, // 05- ColumnOrdinal + int.MaxValue, // 06- ColumnSize + typeof(string), // 07- DataType + false, // 08- IsAliased + false, // 09- IsExpression + false, // 10- IsKey + false, // 11- IsLong + false, // 12- IsUnique + DBNull.Value, // 13- NumericPrecision + DBNull.Value, // 14- NumericScale + (int) DbType.String, // 15- ProviderType + + string.Empty, // 16- BaseCatalogName + string.Empty, // 17- BaseServerName + false, // 18- IsAutoIncrement + false, // 19- IsHidden + true, // 20- IsReadOnly + false // 21- IsRowVersion + }; + + for (int i = 0; i < columnNames.Length; i++) + { + schemaRow[1] = columnNames[i]; // Base column name + schemaRow[4] = columnNames[i]; // Column name + schemaRow[5] = i; // Column ordinal + + schema.Rows.Add(schemaRow); + } + + return schema; + } + + #endregion + + #region IDataRecord Members + + int IDataRecord.GetInt32(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + + string value = this[i]; + + return int.Parse(value == null ? string.Empty : value, CultureInfo.CurrentCulture); + } + + object IDataRecord.this[string name] + { + get + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return this[name]; + } + } + + object IDataRecord.this[int i] + { + get + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return this[i]; + } + } + + object IDataRecord.GetValue(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + + if (((IDataRecord)this).IsDBNull(i)) + return DBNull.Value; + else + return this[i]; + } + + bool IDataRecord.IsDBNull(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return (string.IsNullOrEmpty(this[i])); + } + + long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + + return CopyFieldToArray(i, fieldOffset, buffer, bufferoffset, length); + } + + byte IDataRecord.GetByte(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return byte.Parse(this[i], CultureInfo.CurrentCulture); + } + + Type IDataRecord.GetFieldType(int i) + { + EnsureInitialize(); + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + + if (i < 0 || i >= fieldCount) + throw new ArgumentOutOfRangeException("i", i, string.Format(CultureInfo.InvariantCulture, ExceptionMessage.FieldIndexOutOfRange, i)); + + return typeof(string); + } + + decimal IDataRecord.GetDecimal(int number) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return decimal.Parse(this[number], CultureInfo.CurrentCulture); + } + + int IDataRecord.GetValues(object[] values) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + + var record = (IDataRecord)this; + + for (int i = 0; i < fieldCount; i++) + values[i] = record.GetValue(i); + + return fieldCount; + } + + string IDataRecord.GetName(int i) + { + EnsureInitialize(); + ValidateDataReader(DataReaderValidations.IsNotClosed); + + if (i < 0 || i >= fieldCount) + throw new ArgumentOutOfRangeException("i", i, string.Format(CultureInfo.InvariantCulture, ExceptionMessage.FieldIndexOutOfRange, i)); + + if (hasHeaders) + return fieldHeaders[i]; + else + return "Column" + i.ToString(CultureInfo.InvariantCulture); + } + + long IDataRecord.GetInt64(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return long.Parse(this[i], CultureInfo.CurrentCulture); + } + + double IDataRecord.GetDouble(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return double.Parse(this[i], CultureInfo.CurrentCulture); + } + + bool IDataRecord.GetBoolean(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + + string value = this[i]; + + int result; + + if (int.TryParse(value, out result)) + return (result != 0); + else + return bool.Parse(value); + } + + Guid IDataRecord.GetGuid(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return new Guid(this[i]); + } + + DateTime IDataRecord.GetDateTime(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return DateTime.Parse(this[i], CultureInfo.CurrentCulture); + } + + int IDataRecord.GetOrdinal(string name) + { + EnsureInitialize(); + ValidateDataReader(DataReaderValidations.IsNotClosed); + + int index; + + if (!fieldHeaderIndexes.TryGetValue(name, out index)) + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, ExceptionMessage.FieldHeaderNotFound, name), "name"); + + return index; + } + + string IDataRecord.GetDataTypeName(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return typeof(string).FullName; + } + + float IDataRecord.GetFloat(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return float.Parse(this[i], CultureInfo.CurrentCulture); + } + + IDataReader IDataRecord.GetData(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + + if (i == 0) + return this; + else + return null; + } + + long IDataRecord.GetChars(int field, long fieldoffset, char[] buffer, int bufferoffset, int length) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + + return CopyFieldToArray(field, fieldoffset, buffer, bufferoffset, length); + } + + string IDataRecord.GetString(int field) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return this[field]; + } + + char IDataRecord.GetChar(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return char.Parse(this[i]); + } + + short IDataRecord.GetInt16(int i) + { + ValidateDataReader(DataReaderValidations.IsInitialized | DataReaderValidations.IsNotClosed); + return short.Parse(this[i], CultureInfo.CurrentCulture); + } + + #endregion + + #region IEnumerable Members + + /// + /// Returns an that can iterate through CSV records. + /// + /// An that can iterate through CSV records. + /// + /// The instance has been disposed of. + /// + public CsvDataReader.RecordEnumerator GetEnumerator() + { + return new CsvDataReader.RecordEnumerator(this); + } + + /// + /// Returns an that can iterate through CSV records. + /// + /// An that can iterate through CSV records. + /// + /// The instance has been disposed of. + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + + #region IEnumerable Members + + /// + /// Returns an that can iterate through CSV records. + /// + /// An that can iterate through CSV records. + /// + /// The instance has been disposed of. + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #endregion + + #region IDisposable members + +#if DEBUG + /// + /// Contains the stack when the object was allocated. + /// + System.Diagnostics.StackTrace allocStack; +#endif + + /// + /// Contains the disposed status flag. + /// + bool isDisposed = false; + + /// + /// Contains the locking object for multi-threading purpose. + /// + readonly object _lock = new object(); + + /// + /// Occurs when the instance is disposed of. + /// + public event EventHandler Disposed; + + /// + /// Gets a value indicating whether the instance has been disposed of. + /// + /// + /// if the instance has been disposed of; otherwise, . + /// + [System.ComponentModel.Browsable(false)] + public bool IsDisposed => isDisposed; + + /// + /// Raises the event. + /// + /// A that contains the event data. + protected virtual void OnDisposed(EventArgs e) => Disposed?.Invoke(this, e); + + /// + /// Checks if the instance has been disposed of, and if it has, throws an ; otherwise, does nothing. + /// + /// + /// The instance has been disposed of. + /// + /// + /// Derived classes should call this method at the start of all methods and properties that should not be accessed after a call to . + /// + protected void CheckDisposed() + { + if (isDisposed) + throw new ObjectDisposedException(GetType().FullName); + } + + /// + /// Releases all resources used by the instance. + /// + /// + /// Calls with the disposing parameter set to to free unmanaged and managed resources. + /// + public void Dispose() + { + if (!isDisposed) + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } + + /// + /// Releases the unmanaged resources used by this instance and optionally releases the managed resources. + /// + /// + /// to release both managed and unmanaged resources; to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + // Refer to http://www.bluebytesoftware.com/blog/PermaLink,guid,88e62cdf-5919-4ac7-bc33-20c06ae539ae.aspx + // Refer to http://www.gotdotnet.com/team/libraries/whitepapers/resourcemanagement/resourcemanagement.aspx + + // No exception should ever be thrown except in critical scenarios. + // Unhandled exceptions during finalization will tear down the process. + if (!isDisposed) + { + try + { + // Dispose-time code should call Dispose() on all owned objects that implement the IDisposable interface. + // "owned" means objects whose lifetime is solely controlled by the container. + // In cases where ownership is not as straightforward, techniques such as HandleCollector can be used. + // Large managed object fields should be nulled out. + + // Dispose-time code should also set references of all owned objects to null, after disposing them. This will allow the referenced objects to be garbage collected even if not all references to the "parent" are released. It may be a significant memory consumption win if the referenced objects are large, such as big arrays, collections, etc. + if (disposing) + { + // Acquire a lock on the object while disposing. + + if (reader != null) + { + lock (_lock) + { + if (reader != null) + { + reader.Dispose(); + + reader = null; + buffer = null; + eof = true; + } + } + } + } + } + finally + { + // Ensure that the flag is set + isDisposed = true; + + // Catch any issues about firing an event on an already disposed object. + try + { + OnDisposed(EventArgs.Empty); + } + catch { } + } + } + } + + /// + /// Releases unmanaged resources and performs other cleanup operations before the instance is reclaimed by garbage collection. + /// + ~CsvDataReader() + { +#if DEBUG + Debug.WriteLine("FinalizableObject was not disposed" + allocStack.ToString()); +#endif + + Dispose(disposing: false); + } + + #endregion + } +} \ No newline at end of file diff --git a/Services/Olive.Services.CSV/LumenWorks.Framework/Exceptions/ExceptionMessage.Designer.cs b/Services/Olive.Services.CSV/LumenWorks.Framework/Exceptions/ExceptionMessage.Designer.cs new file mode 100644 index 000000000..72a79628a --- /dev/null +++ b/Services/Olive.Services.CSV/LumenWorks.Framework/Exceptions/ExceptionMessage.Designer.cs @@ -0,0 +1,204 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.42 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace LumenWorks.Framework.IO.Csv.Resources +{ + /// + /// 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", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class ExceptionMessage + { + + static global::System.Resources.ResourceManager resourceMan; + + static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ExceptionMessage() + { + } + + /// + /// 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("LumenWorks.Framework.IO.Csv.Resources.ExceptionMessage", typeof(ExceptionMessage).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 Buffer size must be 1 or more.. + /// + internal static string BufferSizeTooSmall => "Buffer size must be 1 or more."; + + /// + /// Looks up a localized string similar to Cannot move to a previous record in forward-only mode.. + /// + internal static string CannotMovePreviousRecordInForwardOnly + { + get + { + return "Cannot move to a previous record in forward-only mode."; + } + } + + /// + /// Looks up a localized string similar to Cannot read record at index '{0}'.. + /// + internal static string CannotReadRecordAtIndex => "Cannot read record at index '{0}'."; + + /// + /// Looks up a localized string similar to Enumeration has either not started or has already finished.. + /// + internal static string EnumerationFinishedOrNotStarted + { + get + { + return "Enumeration has either not started or has already finished."; + } + } + + /// + /// Looks up a localized string similar to Collection was modified; enumeration operation may not execute.. + /// + internal static string EnumerationVersionCheckFailed + { + get + { + return "Collection was modified; enumeration operation may not execute."; + } + } + + /// + /// Looks up a localized string similar to '{0}' field header not found.. + /// + internal static string FieldHeaderNotFound => "'{0}' field header not found."; + + /// + /// Looks up a localized string similar to Field index must be included in [0, FieldCount[. Specified field index was : '{0}'.. + /// + internal static string FieldIndexOutOfRange + { + get + { + return "Field index must be included in [0, FieldCount[. Specified field index was : '{0}'."; + } + } + + /// + /// Looks up a localized string similar to The CSV appears to be corrupt near record '{0}' field '{1} at position '{2}'. Current raw data : '{3}'.. + /// + internal static string MalformedCsvException + { + get + { + return "The CSV appears to be corrupt near record '{0}' field '{1} at position '{2}'. Current raw data : '{3}'."; + } + } + + /// + /// Looks up a localized string similar to '{0}' is not a supported missing field action.. + /// + internal static string MissingFieldActionNotSupported + { + get + { + return "'{0}' is not a supported missing field action."; + } + } + + /// + /// Looks up a localized string similar to No current record.. + /// + internal static string NoCurrentRecord => "No current record."; + + /// + /// Looks up a localized string similar to The CSV does not have headers (CsvReader.HasHeaders property is false).. + /// + internal static string NoHeaders + { + get + { + return "The CSV does not have headers (CsvReader.HasHeaders property is false)."; + } + } + + /// + /// Looks up a localized string similar to The number of fields in the record is greater than the available space from index to the end of the destination array.. + /// + internal static string NotEnoughSpaceInArray + { + get + { + return "The number of fields in the record is greater than the available space from index to the end of the destination array."; + } + } + + /// + /// Looks up a localized string similar to '{0}' is not a valid ParseErrorAction while inside a ParseError event.. + /// + internal static string ParseErrorActionInvalidInsideParseErrorEvent + { + get + { + return "'{0}' is not a valid ParseErrorAction while inside a ParseError event."; + } + } + + /// + /// Looks up a localized string similar to '{0}' is not a supported ParseErrorAction.. + /// + internal static string ParseErrorActionNotSupported => "'{0}' is not a supported ParseErrorAction."; + + /// + /// Looks up a localized string similar to This operation is invalid when the reader is closed.. + /// + internal static string ReaderClosed => "This operation is invalid when the reader is closed."; + + /// + /// Looks up a localized string similar to Record index must be 0 or more.. + /// + internal static string RecordIndexLessThanZero => "Record index must be 0 or more."; + } +} diff --git a/Services/Olive.Services.CSV/LumenWorks.Framework/Exceptions/MalformedCsvException.cs b/Services/Olive.Services.CSV/LumenWorks.Framework/Exceptions/MalformedCsvException.cs new file mode 100644 index 000000000..0698a0ef1 --- /dev/null +++ b/Services/Olive.Services.CSV/LumenWorks.Framework/Exceptions/MalformedCsvException.cs @@ -0,0 +1,204 @@ +// LumenWorks.Framework.IO.Csv.MalformedCsvException +// Copyright (c) 2005 S�bastien Lorion +// +// MIT license (http://en.wikipedia.org/wiki/MIT_License) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Globalization; +using System.Runtime.Serialization; +using LumenWorks.Framework.IO.Csv.Resources; + +namespace LumenWorks.Framework.IO.Csv +{ + /// + /// Represents the exception that is thrown when a CSV file is malformed. + /// + [Serializable()] + internal class MalformedCsvException + : Exception + { + #region Fields + + /// + /// Contains the message that describes the error. + /// + string message; + + /// + /// Contains the raw data when the error occured. + /// + string rawData; + + /// + /// Contains the current field index. + /// + int currentFieldIndex; + + /// + /// Contains the current record index. + /// + long currentRecordIndex; + + /// + /// Contains the current position in the raw data. + /// + int currentPosition; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the MalformedCsvException class. + /// + public MalformedCsvException() + : this(null, null) + { + } + + /// + /// Initializes a new instance of the MalformedCsvException class. + /// + /// The message that describes the error. + public MalformedCsvException(string message) + : this(message, null) + { + } + + /// + /// Initializes a new instance of the MalformedCsvException class. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. + public MalformedCsvException(string message, Exception innerException) + : base(string.Empty, innerException) + { + this.message = (message == null ? string.Empty : message); + + rawData = string.Empty; + currentPosition = -1; + currentRecordIndex = -1; + currentFieldIndex = -1; + } + + /// + /// Initializes a new instance of the MalformedCsvException class. + /// + /// The raw data when the error occured. + /// The current position in the raw data. + /// The current record index. + /// The current field index. + public MalformedCsvException(string rawData, int currentPosition, long currentRecordIndex, int currentFieldIndex) + : this(rawData, currentPosition, currentRecordIndex, currentFieldIndex, null) + { + } + + /// + /// Initializes a new instance of the MalformedCsvException class. + /// + /// The raw data when the error occured. + /// The current position in the raw data. + /// The current record index. + /// The current field index. + /// The exception that is the cause of the current exception. + public MalformedCsvException(string rawData, int currentPosition, long currentRecordIndex, int currentFieldIndex, Exception innerException) + : base(string.Empty, innerException) + { + this.rawData = (rawData == null ? string.Empty : rawData); + this.currentPosition = currentPosition; + this.currentRecordIndex = currentRecordIndex; + this.currentFieldIndex = currentFieldIndex; + + message = string.Format(CultureInfo.InvariantCulture, ExceptionMessage.MalformedCsvException, this.currentRecordIndex, this.currentFieldIndex, this.currentPosition, this.rawData); + } + + /// + /// Initializes a new instance of the MalformedCsvException class with serialized data. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected MalformedCsvException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + message = info.GetString("MyMessage"); + + rawData = info.GetString("RawData"); + currentPosition = info.GetInt32("CurrentPosition"); + currentRecordIndex = info.GetInt64("CurrentRecordIndex"); + currentFieldIndex = info.GetInt32("CurrentFieldIndex"); + } + + #endregion + + #region Properties + + /// + /// Gets the raw data when the error occured. + /// + /// The raw data when the error occured. + public string RawData => rawData; + + /// + /// Gets the current position in the raw data. + /// + /// The current position in the raw data. + public int CurrentPosition => currentPosition; + + /// + /// Gets the current record index. + /// + /// The current record index. + public long CurrentRecordIndex => currentRecordIndex; + + /// + /// Gets the current field index. + /// + /// The current record index. + public int CurrentFieldIndex => currentFieldIndex; + + #endregion + + #region Overrides + + /// + /// Gets a message that describes the current exception. + /// + /// A message that describes the current exception. + public override string Message => message; + + /// + /// When overridden in a derived class, sets the with information about the exception. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + { + base.GetObjectData(info, context); + + info.AddValue("MyMessage", message); + + info.AddValue("RawData", rawData); + info.AddValue("CurrentPosition", currentPosition); + info.AddValue("CurrentRecordIndex", currentRecordIndex); + info.AddValue("CurrentFieldIndex", currentFieldIndex); + } + + #endregion + } +} \ No newline at end of file diff --git a/Services/Olive.Services.CSV/LumenWorks.Framework/Exceptions/MissingFieldCsvException.cs b/Services/Olive.Services.CSV/LumenWorks.Framework/Exceptions/MissingFieldCsvException.cs new file mode 100644 index 000000000..6c7766041 --- /dev/null +++ b/Services/Olive.Services.CSV/LumenWorks.Framework/Exceptions/MissingFieldCsvException.cs @@ -0,0 +1,102 @@ +// LumenWorks.Framework.IO.Csv.MissingFieldCsvException +// Copyright (c) 2005 Sébastien Lorion +// +// MIT license (http://en.wikipedia.org/wiki/MIT_License) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Runtime.Serialization; + +namespace LumenWorks.Framework.IO.Csv +{ + /// + /// Represents the exception that is thrown when a there is a missing field in a record of the CSV file. + /// + /// + /// MissingFieldException would have been a better name, but there is already a . + /// + [Serializable()] + internal class MissingFieldCsvException + : MalformedCsvException + { + #region Constructors + + /// + /// Initializes a new instance of the MissingFieldCsvException class. + /// + public MissingFieldCsvException() + { + } + + /// + /// Initializes a new instance of the MissingFieldCsvException class. + /// + /// The message that describes the error. + public MissingFieldCsvException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the MissingFieldCsvException class. + /// + /// The message that describes the error. + /// The exception that is the cause of the current exception. + public MissingFieldCsvException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the MissingFieldCsvException class. + /// + /// The raw data when the error occured. + /// The current position in the raw data. + /// The current record index. + /// The current field index. + public MissingFieldCsvException(string rawData, int currentPosition, long currentRecordIndex, int currentFieldIndex) + : base(rawData, currentPosition, currentRecordIndex, currentFieldIndex) + { + } + + /// + /// Initializes a new instance of the MissingFieldCsvException class. + /// + /// The raw data when the error occured. + /// The current position in the raw data. + /// The current record index. + /// The current field index. + /// The exception that is the cause of the current exception. + public MissingFieldCsvException(string rawData, int currentPosition, long currentRecordIndex, int currentFieldIndex, Exception innerException) + : base(rawData, currentPosition, currentRecordIndex, currentFieldIndex, innerException) + { + } + + /// + /// Initializes a new instance of the MissingFieldCsvException class with serialized data. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected MissingFieldCsvException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + #endregion + } +} \ No newline at end of file diff --git a/Services/Olive.Services.CSV/LumenWorks.Framework/ParseErrorEventArgs.cs b/Services/Olive.Services.CSV/LumenWorks.Framework/ParseErrorEventArgs.cs new file mode 100644 index 000000000..3f322b4f6 --- /dev/null +++ b/Services/Olive.Services.CSV/LumenWorks.Framework/ParseErrorEventArgs.cs @@ -0,0 +1,51 @@ +using System; + +namespace LumenWorks.Framework.IO.Csv +{ + /// + /// Provides data for the event. + /// + internal class ParseErrorEventArgs + : EventArgs + { + #region Fields + + /// + /// Contains the error that occured. + /// + MalformedCsvException _error; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the ParseErrorEventArgs class. + /// + /// The error that occured. + /// The default action to take. + public ParseErrorEventArgs(MalformedCsvException error, ParseErrorAction defaultAction) + { + _error = error; + Action = defaultAction; + } + + #endregion + + #region Properties + + /// + /// Gets the error that occured. + /// + /// The error that occured. + public MalformedCsvException Error => _error; + + /// + /// Gets or sets the action to take. + /// + /// The action to take. + public ParseErrorAction Action { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/Services/Olive.Services.CSV/Olive.Services.CSV.csproj b/Services/Olive.Services.CSV/Olive.Services.CSV.csproj new file mode 100644 index 000000000..2e8cfe09c --- /dev/null +++ b/Services/Olive.Services.CSV/Olive.Services.CSV.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.0 + Olive.Services.CSV + Olive.Services.CSV + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.CSV.xml + 1701;1702;1705;1591;1573 + + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.CSV/Package.nuspec b/Services/Olive.Services.CSV/Package.nuspec new file mode 100644 index 000000000..0f5165f6c --- /dev/null +++ b/Services/Olive.Services.CSV/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.CSV + 1.0.3 + Olive CSV (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Compression/Olive.Services.Compression.csproj b/Services/Olive.Services.Compression/Olive.Services.Compression.csproj new file mode 100644 index 000000000..096048337 --- /dev/null +++ b/Services/Olive.Services.Compression/Olive.Services.Compression.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.0 + Olive.Services.Compression + Olive.Services.Compression + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.Compression.xml + 1701;1702;1705;1591;1573 + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Compression/Package.nuspec b/Services/Olive.Services.Compression/Package.nuspec new file mode 100644 index 000000000..0f42afd92 --- /dev/null +++ b/Services/Olive.Services.Compression/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Compression + 1.0.3 + Olive Compression (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Compression/SevenZip.cs b/Services/Olive.Services.Compression/SevenZip.cs new file mode 100644 index 000000000..c807e6fb8 --- /dev/null +++ b/Services/Olive.Services.Compression/SevenZip.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Olive.Services.Compression +{ + public class SevenZip + { + public static string SEVEN_ZIP_EXE_FILE_PATH; // C:\Program Files\7-Zip\7z.exe + + static SevenZip() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + SEVEN_ZIP_EXE_FILE_PATH = GetSevenZipFileAddress(); + else + SEVEN_ZIP_EXE_FILE_PATH = "7z"; + } + + public enum CompressionMode + { + Fastest, + Fast, + Normal, + HighCompression, + MaximumCompression + } + + [EscapeGCop("This paths will not change.")] + static string GetSevenZipFileAddress() + { + var programFilesOptions = new List + { + @"C:\Program Files\", + @"C:\Program Files(x86)\", + @"D:\Program Files\", + @"D:\Program Files(x86)\", + @"E:\Program Files\", + @"E:\Program Files(x86)\", + @"F:\Program Files\", + @"F:\Program Files(x86)\" + }; + + try { programFilesOptions.Add(Environment.GetEnvironmentVariable("ProgramFiles(x86)")); } + catch { /* No Logging Needed */ } + + try { programFilesOptions.Add(Environment.GetEnvironmentVariable("ProgramFiles")); } + catch { /* No Logging Needed */ } + + foreach (var item in programFilesOptions.Distinct()) + { + try + { + var file = Path.Combine(item, "7-Zip\\7z.exe"); + if (File.Exists(file)) + return file; + } + catch { } + } + + throw new Exception("7Zip was not found in any of: \r\n" + programFilesOptions.ToLinesString()); + } + + static string GetCompressionModeSwitch(CompressionMode mode) + { + switch (mode) + { + case CompressionMode.Fastest: return "-mx1"; + case CompressionMode.Fast: return "-mx3"; + case CompressionMode.Normal: return "-mx5"; + case CompressionMode.HighCompression: return "-mx7"; + case CompressionMode.MaximumCompression: return "-mx9"; + default: throw new NotSupportedException(mode + " is not supported in GetCompressionModeSwitch()"); + } + } + + /// + /// Compresses the specified folders into a 7 Zip archive folder. + /// + public static Process Compress(string zipFileName, params string[] foldersToCompress) => + Compress(zipFileName, null, foldersToCompress); + + /// + /// Compresses the specified folders into a 7 Zip archive folder. + /// + /// The maximum size of each splitted size in Kilo Bytes + public static Process Compress(string zipFileName, int? splitSize, params string[] foldersToCompress) => + Compress(zipFileName, splitSize, CompressionMode.Normal, foldersToCompress); + + /// + /// Compresses the specified folders into a 7 Zip archive folder. + /// + /// The maximum size of each splitted size in Kilo Bytes + public static Process Compress(string zipFileName, int? splitSize, CompressionMode compressionMode, params string[] foldersToCompress) => + Compress(zipFileName, splitSize, compressionMode, null, foldersToCompress, new string[0]); + + /// + /// Compresses the specified source files into a temp 7Zip file and returns the temp 7Zip file. + /// + public static async Task Compress(IEnumerable sourceFiles, CompressionMode compressionMode = CompressionMode.Normal, string customParameters = null) + { + var result = Path.GetTempPath().AsDirectory().GetFile("Temp7Zip-" + Guid.NewGuid() + ".7z"); + await Compress(result, sourceFiles, compressionMode, customParameters); + + return result; + } + + /// + /// Compresses the specified source files into a 7Zip file and returns the data of the 7Zip file. The temp file is deleted. + /// + public static async Task CompressToBytes(IEnumerable sourceFiles, CompressionMode compressionMode = CompressionMode.Normal, string customParameters = null) + { + var tempFolder = Path.GetTempPath().AsDirectory().GetOrCreateSubDirectory("Temp7Zip-" + Guid.NewGuid()); + var tempFile = tempFolder.GetFile("Files.7z"); + + try + { + await Compress(tempFile, sourceFiles, compressionMode, customParameters); + return await tempFile.ReadAllBytes(); + } + finally + { + await tempFolder?.Delete(recursive: true, harshly: true); + } + } + + /// + /// Creates a 7Zip file from the specified files. + /// + public static async Task Compress(FileInfo destinationPath, IEnumerable sourceFiles, CompressionMode compressionMode = CompressionMode.Normal, string customParameters = null) + { + var files = sourceFiles.ToArray(); + if (files.Distinct(x => x.Name.ToLower()).Count() != files.Count()) throw new Exception("File names must be unique."); + + var tempFolder = Path.GetTempPath().AsDirectory().GetOrCreateSubDirectory("7ZipTemp." + Guid.NewGuid()); + var filesFolder = tempFolder.GetOrCreateSubDirectory(Path.GetFileNameWithoutExtension(destinationPath.FullName)); + try + { + await Task.WhenAll(files.Select(f => f.CopyTo(filesFolder))); + + Compress(destinationPath.FullName, default(int?), compressionMode, customParameters, new[] { filesFolder.FullName }).WaitForExit(); + } + finally + { + await tempFolder.Delete(recursive: true, harshly: true); + } + } + + /// + /// Compresses the specified folders into a 7 Zip archive folder. + /// + /// Use wildcards. Example: *\Folder\Sub-folder\* + public static Process Compress(string zipFileName, int? splitSize, CompressionMode compressionMode, string customParameters, string[] foldersToCompress, string[] excludedFilePatterns = null) + { + if (zipFileName.IsEmpty()) + throw new ArgumentNullException(nameof(zipFileName)); + + if (foldersToCompress == null || foldersToCompress.Length == 0) + throw new ArgumentNullException(nameof(foldersToCompress)); + + if (splitSize < 1) + throw new ArgumentException("The minimum possible split size for 7 Zipper is 1KB."); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !File.Exists(SEVEN_ZIP_EXE_FILE_PATH)) + throw new Exception("7 Zip is not installed. Could not find the file at " + SEVEN_ZIP_EXE_FILE_PATH); + + var command = new ProcessStartInfo(SEVEN_ZIP_EXE_FILE_PATH, "a " + GetCompressionModeSwitch(compressionMode)); + + if (splitSize.HasValue) + command.Arguments += " -v\"" + splitSize + "k\" "; + + if (customParameters.HasValue()) + command.Arguments += " " + customParameters + " "; + + if (excludedFilePatterns != null && excludedFilePatterns.Any()) + foreach (var ex in excludedFilePatterns) + command.Arguments += " -xr!\"" + ex + "\" "; + + // Add destination folder: + command.Arguments += " \"" + zipFileName + "\" "; + + // Add source folders: + command.Arguments += foldersToCompress.Select(pt => " \"" + pt + "\"").ToString(" "); + + return Process.Start(command); + } + } +} diff --git a/Services/Olive.Services.Drawing/BitmapHelper.cs b/Services/Olive.Services.Drawing/BitmapHelper.cs new file mode 100644 index 000000000..60c1853e5 --- /dev/null +++ b/Services/Olive.Services.Drawing/BitmapHelper.cs @@ -0,0 +1,133 @@ +// using System; +// using System.Drawing; +// using System.Drawing.Imaging; +// using System.IO_Dummy; +// using System.Linq; + +// namespace Olive.Services.Drawing +// { +// public static class BitmapHelper +// { +// static EncoderParameters GenerateEncoderParameters(int quality) => +// new EncoderParameters(1) { Param = { [0] = new EncoderParameter(Encoder.Quality, quality) } }; + +// static ImageCodecInfo GenerateCodecInfo(ImageFormat format) +// { +// var allCodecs = ImageCodecInfo.GetImageEncoders(); +// return allCodecs.FirstOrDefault(a => a.FormatID == format.Guid) ?? allCodecs.ElementAt(1); // Defauilt JPEG +// } + +// public static byte[] ToBuffer(this Image image) +// { +// if (image.RawFormat.Guid == ImageFormat.MemoryBmp.Guid) +// throw new ArgumentException("For a MemoryBMP the actual image format must be specified. Use the other overload of the ToBuffer method."); + +// return image.ToBuffer(image.RawFormat); +// } + +// /// +// /// Gets the binary data of this image. +// /// +// public static byte[] ToBuffer(this Image image, ImageFormat format, int quality = 100) +// { +// using (var stream = new MemoryStream()) +// { +// image.Save(stream, GenerateCodecInfo(format), GenerateEncoderParameters(quality)); +// return stream.ToArray(); +// } +// } + +// /// +// /// Converts the specified binary data to a bitmap. +// /// +// public static Image FromBuffer(byte[] buffer) +// { +// using (var stream = new MemoryStream(buffer)) +// { +// return Bitmap.FromStream(stream); +// } +// } + +// /// +// /// Determines whether the specified binary data is for a valid image. +// /// +// public static bool IsValidImage(byte[] buffer) +// { +// try +// { +// return FromBuffer(buffer) != null; +// } +// catch +// { +// return false; +// } +// } + +// /// +// /// Returns a resized version of this image. +// /// +// public static Image Resize(this Image source, int newWidth, int newHeight) +// { +// var result = new Bitmap(newWidth, newHeight); + +// using (var grraphics = Graphics.FromImage(result)) +// { +// grraphics.DrawImage(source, 0, 0, newWidth, newHeight); +// return result; +// } +// } + +// /// +// /// Brightens or darkens this image to the specified level. Level should be between 0 and 255. +// /// 0 Means totally dark and 255 means totally bright. +// /// +// public static Image Brighten(this Image source, int level) +// { +// if (level < 0 || level > 255) +// throw new ArgumentException("Level must be between 0 and 255."); + +// var result = new Bitmap(source); + +// using (var gr = Graphics.FromImage(result)) +// { +// using (var brush = new SolidBrush(Color.FromArgb(level, Color.White))) +// { +// gr.FillRectangle(brush, new Rectangle(Point.Empty, source.Size)); +// return result; +// } +// } +// } + +// /// +// /// Creates a graphics object for this image. +// /// +// public static Graphics CreateGraphics(this Image image) +// { +// var result = Graphics.FromImage(image); + +// result.PageUnit = GraphicsUnit.Pixel; +// result.RenderingOrigin = Point.Empty; +// result.PageScale = 1f; +// result.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; +// result.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + +// return result; +// } + +// /// +// /// Crops this image with the specified rectangle. +// /// +// public static Image Crop(this Image image, Rectangle rectangle) +// { +// //var result = new Bitmap(image, rectangle.Size); +// var result = new Bitmap(rectangle.Width, rectangle.Height); + +// using (var gr = result.CreateGraphics()) +// { +// gr.DrawImage(image, new Rectangle(Point.Empty, result.Size), rectangle, GraphicsUnit.Pixel); +// } + +// return result; +// } +// } +// } diff --git a/Services/Olive.Services.Drawing/Extensions.cs b/Services/Olive.Services.Drawing/Extensions.cs new file mode 100644 index 000000000..bf73bc5bf --- /dev/null +++ b/Services/Olive.Services.Drawing/Extensions.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Olive.Entities; + +namespace Olive.Services.Drawing +{ + public static class Extensions + { + const int IMAGE_DEFAULT_QUALITY = 70; + + /// + /// Optimizes the image based on the settings in the arguments. + /// + public static Task OptimizeImage(this Blob blob, int maxWidth, int maxHeight) => + blob.OptimizeImage(maxWidth, maxHeight, IMAGE_DEFAULT_QUALITY); + + /// + /// Optimizes the image based on the settings in the arguments. + /// + public static async Task OptimizeImage(this Blob blob, int maxWidth, int maxHeight, int quality, bool toJpeg = true) + { + if ((await blob.GetFileData()).Length > 10) + { + var optimizer = new ImageOptimizer(maxWidth, maxHeight, quality); + blob.SetData(optimizer.Optimize(await blob.GetFileData(), toJpeg)); + } + } + } +} diff --git a/Services/Olive.Services.Drawing/GifPalleteGenerator.cs b/Services/Olive.Services.Drawing/GifPalleteGenerator.cs new file mode 100644 index 000000000..e3268558c --- /dev/null +++ b/Services/Olive.Services.Drawing/GifPalleteGenerator.cs @@ -0,0 +1,74 @@ +// namespace Olive.Services.Drawing +// { +// using System; +// using System.Collections.Generic; +// using System.Drawing; +// using System.Drawing.Imaging; +// using System.Linq; + +// /// +// /// Generates a color pallete for a GIF image. +// /// +// public static class GifPalleteGenerator +// { +// const int STANDARD_COLOR_PALLETTE_SIZE = 256; + +// static readonly Color TRANSPARENT = Color.FromArgb(0, 0, 0, 0); + +// /// +// /// Generates a color pallete based on the colors used in a specified image. +// /// +// public static ColorPalette GeneratePallete(Bitmap image) +// { +// if (image == null) +// throw new ArgumentNullException("image"); + +// var result = CreateEmptyPallette(); + +// var usedColours = FindAllColours(image); + +// result.Entries[0] = TRANSPARENT; + +// for (int i = 1; i < STANDARD_COLOR_PALLETTE_SIZE; i++) +// if (usedColours.Count >= i) +// result.Entries[i] = usedColours[i - 1].Key; +// else result.Entries[i] = Color.White; + +// return result; +// } + +// static ColorPalette CreateEmptyPallette() +// { +// using (var temp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed)) +// { +// return temp.Palette; +// } +// } + +// /// +// /// Finds all colours used in the specified image. +// /// The result will be the list of colours sorted by then umber of times that is used. +// /// +// static List> FindAllColours(Bitmap image) +// { +// var colors = new Dictionary(); + +// for (int x = 0; x < image.Width; x++) +// for (int y = 0; y < image.Height; y++) +// { +// var pixel = image.GetPixel(x, y); + +// if (colors.ContainsKey(pixel)) +// colors[pixel]++; +// else +// colors.Add(pixel, 1); +// } + +// var result = from item in colors +// orderby item.Value descending +// select new KeyValuePair(item.Key, item.Value); + +// return result.ToList(); +// } +// } +// } \ No newline at end of file diff --git a/Services/Olive.Services.Drawing/GifProcessor.cs b/Services/Olive.Services.Drawing/GifProcessor.cs new file mode 100644 index 000000000..619ede314 --- /dev/null +++ b/Services/Olive.Services.Drawing/GifProcessor.cs @@ -0,0 +1,111 @@ +// namespace Olive.Services.Drawing +// { +// using System; +// using System.Drawing; +// using System.Drawing.Imaging; +// using System.Linq; + +// class GifProcessor +// { +// static object SyncLock = new object(); + +// Bitmap Source; +// ColorPalette CorrectPallete; + +// public GifProcessor(Bitmap sourceImage) +// { +// Source = sourceImage ?? throw new ArgumentNullException("sourceImage"); +// CorrectPallete = GifPalleteGenerator.GeneratePallete(sourceImage); +// } + +// Bitmap CreateBitmapWithIndexedColors(Bitmap source) +// { +// var width = source.Width; +// var height = source.Height; + +// var result = new Bitmap(width, height, PixelFormat.Format8bppIndexed) +// { +// Palette = CorrectPallete +// }; + +// var bitmapData = result.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); + +// var pixels = bitmapData.Scan0; + +// unsafe +// { +// var pointer = (byte*)pixels.ToPointer(); +// if (bitmapData.Stride <= 0) +// pointer += bitmapData.Stride * (height - 1); + +// var stride = (uint)Math.Abs(bitmapData.Stride); + +// for (uint x = 0; x < width; ++x) +// { +// for (uint y = 0; y < height; ++y) +// { +// var pixel = source.GetPixel((int)x, (int)y); + +// byte* newPointer = x + pointer + y * stride; + +// // Set that byte to the color in the new pallet +// *newPointer = FindPalleteEntryIndex(pixel); +// } +// } +// } + +// // Unlock the relevant area of the bitmap so the changes can be committed. +// result.UnlockBits(bitmapData); + +// return result; +// } + +// static Bitmap CreateCopy(Bitmap source) => +// source.Clone(new Rectangle(0, 0, source.Width, source.Height), PixelFormat.Format32bppArgb); + +// public void Save(string filename, bool transparent) +// { +// var width = Source.Width; +// var height = Source.Height; + +// lock (SyncLock) +// { +// using (var copy = CreateCopy(Source)) +// { +// var toSave = CreateBitmapWithIndexedColors(copy); + +// toSave.Save(filename, System.Drawing.Imaging.ImageFormat.Gif); +// } +// } +// } + +// /// +// /// Finds the index of the relevant entry in the new pallete to the specified color. +// /// +// byte FindPalleteEntryIndex(Color color) +// { +// if (color.A == 0) +// // First entry in the pallete is "Transparent" +// return 0; + +// // If there is an exact match in the entries for this color, then obviously that's our entry: +// for (byte index = 0; index < byte.MaxValue; index++) +// if (CorrectPallete.Entries[index] == color) return index; + +// // Otherwise, find the nearest color: +// return CorrectPallete.Entries.Select((c, i) => new { Index = (byte)i, Color = c }).WithMin(entry => GetDifference(color, entry.Color)).Index; +// } + +// /// +// /// Gets the difference between the 2 specified colors. +// /// +// static int GetDifference(Color c1, Color c2) +// { +// return +// Math.Abs(c1.A - c2.A) + +// Math.Abs(c1.R - c2.R) + +// Math.Abs(c1.G - c2.G) + +// Math.Abs(c1.B - c2.B); +// } +// } +// } \ No newline at end of file diff --git a/Services/Olive.Services.Drawing/GraphicExtensions.cs b/Services/Olive.Services.Drawing/GraphicExtensions.cs new file mode 100644 index 000000000..328b5195d --- /dev/null +++ b/Services/Olive.Services.Drawing/GraphicExtensions.cs @@ -0,0 +1,184 @@ +// using System; +// using System.Drawing; +// using System.Drawing.Imaging; +// using System.Drawing.Text; + +// namespace Olive.Services.Drawing +// { +// public static class GraphicExtensions +// { +// const double HALF_PI = Math.PI / 2.00; +// const double RADIANS = Math.PI / 180.0; + +// /// +// /// Gets an image which is a column of this image at the specified index. +// /// +// public static Bitmap GetColumn(this Bitmap image, int columnIndex) +// { +// var height = image.Height; + +// var result = new Bitmap(1, height, PixelFormat.Format32bppArgb); + +// for (int i = 0; i < height; i++) +// result.SetPixel(0, i, image.GetPixel(columnIndex, i)); + +// return result; +// } + +// /// +// /// Gets the width of a specified text in this font. +// /// +// public static int GetWidth(this Font font, string text, bool useAntialias) +// { +// using (var tempImage = new Bitmap(1, 1)) +// { +// using (var tempGraphics = Graphics.FromImage(tempImage)) +// { +// if (useAntialias) +// tempGraphics.TextRenderingHint = TextRenderingHint.AntiAlias; + +// var result = (int)(tempGraphics.MeasureString(text, font).Width); + +// // Measure string trims the text by default. God knows why: +// var spaces = text.Length - text.TrimEnd(' ').Length; + +// if (spaces > 0) +// { +// result += spaces * (int)(tempGraphics.MeasureString(" ", font).Width); +// } + +// return result; +// } +// } +// } + +// /// +// /// Inserts the specified image at the specified column inside this host image. +// /// +// public static Bitmap Insert(this Bitmap host, int columnIndex, Bitmap image) +// { +// if (image == null) +// throw new ArgumentNullException("image"); + +// if (image.Height != host.Height) +// throw new ArgumentException("The height of the specified image is different from the host image."); + +// var height = host.Height; +// var width = host.Width + image.Width; +// var right = columnIndex + image.Width; +// var result = new Bitmap(width, height); + +// Color pixel; + +// for (int y = 0; y < height; y++) +// { +// for (int x = 0; x < width; x++) +// { +// if (x < columnIndex) +// // Left section: +// pixel = host.GetPixel(x, y); + +// else if (x < right) +// // Middle section (inserting image) +// pixel = image.GetPixel(x - columnIndex, y); + +// else +// // Right section +// pixel = host.GetPixel(x - image.Width, y); + +// if (pixel.A == 0) +// // Transparent +// result.SetPixel(x, y, Color.Transparent); +// else +// result.SetPixel(x, y, pixel); +// } +// } + +// return result; +// } + +// /// +// /// Creates a rotated version from this image. +// /// +// /// The number of degrees to rotate this image. Direction of rotation will be clock-wise. +// public static Bitmap Rotate(this Image image, double degrees) +// { +// if (image == null) +// throw new ArgumentNullException("image"); + +// // Use radians: +// degrees *= RADIANS; + +// var originalWidth = (double)image.Width; +// var originalHeight = (double)image.Height; + +// double newWidth, newHeight, topLeft, topRight, bottomLeft, bottomRight; +// int resultWidth, resultHeight; + +// if ((degrees >= 0.0 && degrees < HALF_PI) || (degrees < (Math.PI + HALF_PI) && degrees >= Math.PI)) +// { +// topLeft = originalWidth * Math.Abs(Math.Cos(degrees)); +// topRight = originalWidth * Math.Abs(Math.Sin(degrees)); + +// bottomLeft = originalHeight * Math.Abs(Math.Cos(degrees)); +// bottomRight = originalHeight * Math.Abs(Math.Sin(degrees)); +// } +// else +// { +// topLeft = originalHeight * Math.Abs(Math.Sin(degrees)); +// topRight = originalHeight * Math.Abs(Math.Cos(degrees)); + +// bottomLeft = originalWidth * Math.Abs(Math.Sin(degrees)); +// bottomRight = originalWidth * Math.Abs(Math.Cos(degrees)); +// } + +// newWidth = bottomRight + topLeft; +// newHeight = topRight + bottomLeft; + +// resultWidth = (int)Math.Ceiling(newWidth); +// resultHeight = (int)Math.Ceiling(newHeight); + +// var result = new Bitmap(resultWidth, resultHeight); + +// using (var graph = Graphics.FromImage(result)) +// { +// Point[] points; + +// if (degrees >= 0.0 && degrees < HALF_PI) +// points = new[] { new Point((int)bottomRight, 0), new Point(resultWidth, (int)topRight), new Point(0, (int)bottomLeft) }; +// else if (degrees < Math.PI && degrees >= HALF_PI) +// points = new[] { new Point(resultWidth, (int)topRight), new Point((int)topLeft, resultHeight), new Point((int)bottomRight, 0) }; +// else if (degrees < (Math.PI + HALF_PI) && degrees >= Math.PI) +// points = new[] { new Point((int)topLeft, resultHeight), new Point(0, (int)bottomLeft), new Point(resultWidth, (int)topRight) }; +// else +// points = new[] { new Point(0, (int)bottomLeft), new Point((int)bottomRight, 0), new Point((int)topLeft, resultHeight) }; + +// graph.DrawImage(image, points); +// } + +// return result; +// } + +// /// +// /// Stretches the specified image. +// /// +// public static Bitmap Stretch(this Bitmap image, int width) +// { +// if (image == null || image.Width != 1) +// throw new Exception("Bitmap.Stretch() should be called on an image with one column only."); + +// var result = new Bitmap(width, image.Height); +// for (int column = 0; column < image.Height; column++) +// for (int row = 0; row < width; row++) +// result.SetPixel(row, column, image.GetPixel(0, column)); + +// return result; +// } + +// public static void SaveAsGif(this Bitmap image, string path, bool transparent) +// { +// var processor = new GifProcessor(image); +// processor.Save(path, transparent); +// } +// } +// } \ No newline at end of file diff --git a/Services/Olive.Services.Drawing/ImageOptimizer.cs b/Services/Olive.Services.Drawing/ImageOptimizer.cs new file mode 100644 index 000000000..e84b34025 --- /dev/null +++ b/Services/Olive.Services.Drawing/ImageOptimizer.cs @@ -0,0 +1,141 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using SkiaSharp; + +//using System.Drawing.Imaging; + +namespace Olive.Services.Drawing +{ + /// + /// A utility to resize and optimise image files. + /// + public class ImageOptimizer + { + const int DEFAULT_MAX_WIDTH = 900, DEFAULT_MAX_HEIGHT = 700, DEFAULT_QUALITY = 80; + /// + /// Creates a new instance of ImageOptimizer class with default settings. + /// + public ImageOptimizer() : this(DEFAULT_MAX_WIDTH, DEFAULT_MAX_HEIGHT, DEFAULT_QUALITY) { } + + /// + /// Creates a new instance of ImageOptimizer class. + /// + public ImageOptimizer(int maxWidth, int maxHeight, int quality) + { + MaximumWidth = maxWidth; + MaximumHeight = maxHeight; + Quality = quality; + OutputFormat = ImageFormat.Jpeg; + } + + public int MaximumWidth { get; set; } + public int MaximumHeight { get; set; } + public int Quality { get; set; } + public ImageFormat OutputFormat { get; set; } + + /// + /// Gets the available output image formats. + /// + public enum ImageFormat { Bmp = 0, Jpeg = 1, Gif = 2, Png = 4 } + + /// + /// Applies the settings of this instance on a specified source image, and provides an output optimized/resized image. + /// + public SKBitmap Optimize(SKBitmap source) + { + // Calculate the suitable width and heigth for the output image: + var width = source.Width; + var height = source.Height; + + if (width > MaximumWidth) + { + height = (int)(height * (1.0 * MaximumWidth) / width); + width = MaximumWidth; + } + + if (height > MaximumHeight) + { + width = (int)(width * (1.0 * MaximumHeight) / height); + height = MaximumHeight; + } + + if (width == source.Width && height == source.Height) + return source; + + var result = new SKBitmap(width, height); + + SKBitmap.Resize(result, source, SKBitmapResizeMethod.Lanczos3); + + return result; + } + + /// + /// Optimizes the specified source image and returns the binary data of the output image. + /// + public byte[] Optimize(byte[] sourceData, bool toJpeg = true) + { + try + { + using (var source = SKBitmap.Decode(sourceData)) + { + using (var resultBitmap = Optimize(source)) + { + using (SKImage image = SKImage.FromBitmap(resultBitmap)) + { + return image.Encode(toJpeg ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, Quality).ToArray(); + } + } + } + } + catch + { + // No Logging Needed + return sourceData; + } + } + + /// + /// Applies optimization settings on a a source image file on the disk and saves the output to another file with the specified path. + /// + public async Task Optimize(string souceImagePath, string optimizedImagePath) + { + if (!File.Exists(souceImagePath)) + throw new Exception("Could not find the file: " + souceImagePath); + + SKBitmap source; + + try + { + source = SKBitmap.Decode(souceImagePath); + } + catch (Exception ex) + { + throw new Exception("Could not obtain bitmap data from the file: {0}.".FormatWith(souceImagePath), ex); + } + + using (source) + { + using (var optimizedImage = SKImage.FromBitmap(Optimize(source))) + { + await File.WriteAllBytesAsync(optimizedImagePath, optimizedImage.Encode(SKEncodedImageFormat.Jpeg, Quality).ToArray()); + } + } + } + + /// + /// Applies optimization settings on a source image file. + /// Please note that the original file data is lost (overwritten) in this overload. + /// + public Task Optimize(string imagePath) => Optimize(imagePath, imagePath); + + // EncoderParameters GenerateEncoderParameters() + // { + // var result = new EncoderParameters(1); + // result.Param[0] = new EncoderParameter(Encoder.Quality, Quality); + // return result; + // } + + // ImageCodecInfo GenerateCodecInfo() => ImageCodecInfo.GetImageEncoders()[(int)OutputFormat]; + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Drawing/Olive.Services.Drawing.csproj b/Services/Olive.Services.Drawing/Olive.Services.Drawing.csproj new file mode 100644 index 000000000..38441db94 --- /dev/null +++ b/Services/Olive.Services.Drawing/Olive.Services.Drawing.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp2.0 + Olive.Services.Drawing + Olive.Services.Drawing + + + + true + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.Drawing.xml + 1701;1702;1705;1591;1573 + + + + + + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Drawing/Package.nuspec b/Services/Olive.Services.Drawing/Package.nuspec new file mode 100644 index 000000000..7ca0bd3d7 --- /dev/null +++ b/Services/Olive.Services.Drawing/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Drawing + 1.0.4 + Olive Drawing (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.DynamicExpressions/Compiler.cs b/Services/Olive.Services.DynamicExpressions/Compiler.cs new file mode 100644 index 000000000..97682b355 --- /dev/null +++ b/Services/Olive.Services.DynamicExpressions/Compiler.cs @@ -0,0 +1,165 @@ +using System; +using System.CodeDom.Compiler; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.CSharp; + +namespace Olive.Framework.Services +{ + internal class Compiler + { + List References = new List(); + + /// + /// Creates a new Compiler instance. + /// + public Compiler() + { + Reference(); + Reference(); + Reference(); + } + + internal Type CompileClass(string classCode) + { + #region Parameters + var parameters = new CompilerParameters + { + GenerateExecutable = false, + IncludeDebugInformation = true, + GenerateInMemory = true + //CompilerOptions = "/optimize" + /*TreatWarningsAsErrors = false, WarningLevel = 3*/ + }; + + // Add references: + foreach (var dll in References.Distinct().ToArray()) + { + parameters.ReferencedAssemblies.Add(dll.Location); + } + #endregion + + #region Compiler Options + var options = new Dictionary(); + //options.Add("CompilerVersion", "v3.5"); + options.Add("CompilerVersion", "v4.0"); + #endregion + + using (var codeProvider = new CSharpCodeProvider(options)) + { + var compileResult = codeProvider.CompileAssemblyFromSource(parameters, new[] { classCode }); + + EvaluateResult(compileResult); + + var types = compileResult.CompiledAssembly.GetTypes(); + if (types.None()) + throw new Exception("The dynamic type for the following class has no type, also no error messages were produced by the compiler:\r\n" + + classCode); + + + return types.Single(t => t.Name == "Class"); + } + } + + public void Reference(bool ignoreFailedAssemblies = false) + { + Reference(typeof(T), ignoreFailedAssemblies); + } + + public void Reference(Type type, bool ignoreFailedAssemblies = false) + { + Reference(type.Assembly, ignoreFailedAssemblies); + } + + static ConcurrentDictionary _AssemblyFullNames = new ConcurrentDictionary(); + static string GetAssemblyFullName(Assembly assembly) + { + return _AssemblyFullNames.GetOrAdd(assembly, a => a.GetName().FullName); + } + + bool IsReferenceAdded(Assembly assembly) + { + return References.Any(r => GetAssemblyFullName(r) == GetAssemblyFullName(assembly)); + } + + public void Reference(Assembly assembly, bool ignoreFailedAssemblies = false) + { + if (IsReferenceAdded(assembly)) return; + + References.Add(assembly); + + var currentlyLoaded = AppDomain.CurrentDomain.GetAssemblies(); + + foreach (var name in assembly.GetReferencedAssemblies()) + { + try + { + if (References.Any(re => GetAssemblyFullName(re) == name.FullName)) continue; + + var matched = currentlyLoaded.Where(x => GetAssemblyFullName(x) == name.FullName).ToArray(); + + var toRef = matched.LastOrDefault(); + + //Assembly.ReflectionOnlyLoad( + //var r = Assembly.Load(name); + if (toRef == null) continue; + + Reference(toRef); + } + catch + { + if (!ignoreFailedAssemblies) + throw; + // Ignore this assembly. + } + } + } + + private void EvaluateResult(CompilerResults result) + { + if (result.Errors.Count == 0) return; + + var errors = new List(); + var messages = new List(); + + foreach (var error in result.Errors.Cast().Where(e => !e.IsWarning)) + { + if (messages.Contains(error.ErrorText)) continue; + else messages.Add(error.ErrorText); + + errors.AddFormat("{0} ({1}:{2})", error.ErrorText, error.FileName, error.Line); + } + + if (messages.Any()) + { + throw new Exception("I cannot compile the dynamic assembly ", new Exception(errors.ToLinesString())); + } + } + + internal Type CompileMethods(string methods) + { + var r = new StringBuilder(); + + r.AppendLine("using System;"); + r.AppendLine("using System.Collections;"); + r.AppendLine("using System.Linq;"); + r.AppendLine("using System.Collections.Generic;"); + r.AppendLine("using System.Text;"); + r.AppendLine(""); + r.AppendLine(""); + r.AppendLine("using App;"); + r.AppendLine(); + + r.AppendLine("public static class Evaluator"); + r.AppendLine("{"); + + r.AppendLine(methods); + r.AppendLine("}"); + + return CompileClass(r.ToString()); + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.DynamicExpressions/DynamicExpressionsCompiler.cs b/Services/Olive.Services.DynamicExpressions/DynamicExpressionsCompiler.cs new file mode 100644 index 000000000..3284d1449 --- /dev/null +++ b/Services/Olive.Services.DynamicExpressions/DynamicExpressionsCompiler.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Olive.Framework.Services +{ + class DynamicExpressionsCompiler + { + Type ListType; + IEnumerable List; + + static Dictionary WhereCache = new Dictionary(); + + static Dictionary> SelectCache = new Dictionary>(); + + /// + /// Creates a new DynamicExpressionsCompiler instance. + /// + public DynamicExpressionsCompiler(IEnumerable list) + : this(list, typeof(T)) + { + } + + /// + /// Creates a new DynamicExpressionsCompiler instance. + /// + public DynamicExpressionsCompiler(IEnumerable list, Type listType) + { + List = list; + ListType = listType; + } + + string FullTypeName + { + get + { + return ListType.FullName; + } + } + + internal IEnumerable Where(string criteria) + { + var key = ListType.FullName + "|" + criteria; + + lock (WhereCache) + { + if (!WhereCache.ContainsKey(key)) + { + var method = CompileMethod(criteria, "each"); + WhereCache.Add(key, method); + } + } + + var arg = List.Cast(ListType); + + var result = WhereCache[key].Invoke(null, new object[] { arg }); + + return (result as IEnumerable).Cast(); + } + + internal IEnumerable Select(string query) + { + Dictionary pool; + + lock (SelectCache) + { + if (SelectCache.ContainsKey(typeof(K))) + pool = SelectCache[typeof(K)]; + else + { + pool = new Dictionary(); + SelectCache.Add(typeof(K), pool); + } + } + + lock (pool) + { + if (!pool.ContainsKey(query)) + { + var method = CompileMethod(condition: null, expression: query); + pool.Add(query, method); + } + } + + if (List.None()) + { + return new List(); + } + + //var actualT = List.First().GetType(); // pool[query].GetParameters().First().ParameterType.GetGenericArguments().First(); + //var listTType = typeof(System.Collections.Generic.List<>).MakeGenericType(actualT); + //var args = listTType.CreateInstance() as IList; + ////var args = new List(); + //foreach(var item in List) + //{ + // args.Add(item); + //} + ////var actualT = pool[query].GetParameters().First().ParameterType.GetGenericArguments().First() + + + var arg = List.Cast(ListType); + + var result = pool[query].Invoke(null, new object[] { arg }); + + return (result as IEnumerable).Cast(); + } + + string CreateCode(string condition, string expression) + { + var r = new StringBuilder(); + + var namespaces = new[] { ListType, typeof(K) }.Select(t => t.Namespace) + .Concat(new[] { "System", "System.Linq", "System.Collections", "System.Collections.Generic" }).Distinct(); + + foreach (var n in namespaces) + r.AddFormattedLine("using {0};", n); + + r.AppendLine("public static class Class"); + r.AppendLine("{"); + + r.AddFormattedLine("public static IEnumerable<{1}> Run(IEnumerable<{0}> list)", FullTypeName, typeof(K).FullName); + r.AppendLine("{"); + + r.AppendLine("foreach (var each in list)"); + r.AppendLine("{"); + + if (condition.HasValue()) + r.AddFormattedLine("if ({0})", condition); + + r.AddFormattedLine("yield return {0};", expression); + + r.AppendLine("}"); + + //r.AddFormattedLine("return list.{0}(each => {1});", method, expression); + r.AppendLine("}"); + + r.AppendLine("}"); // Class + + return r.ToString(); + } + + MethodInfo CompileMethod(string condition, string expression) + { + var code = CreateCode(condition, expression); + + var compiler = new Compiler(); + compiler.Reference(ListType, ignoreFailedAssemblies: true); + + var type = compiler.CompileClass(code); + + return type.GetMethod("Run"); + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.DynamicExpressions/Olive.Services.DynamicExpressions.csproj b/Services/Olive.Services.DynamicExpressions/Olive.Services.DynamicExpressions.csproj new file mode 100644 index 000000000..0aac81745 --- /dev/null +++ b/Services/Olive.Services.DynamicExpressions/Olive.Services.DynamicExpressions.csproj @@ -0,0 +1,9 @@ + + + + netcoreapp2.0 + Olive.Services.DynamicExpressions + Olive.Services.DynamicExpressions + + + \ No newline at end of file diff --git a/Services/Olive.Services.DynamicExpressions/Olive.Services.DynamicExpressions.nuspec b/Services/Olive.Services.DynamicExpressions/Olive.Services.DynamicExpressions.nuspec new file mode 100644 index 000000000..3e63f875e --- /dev/null +++ b/Services/Olive.Services.DynamicExpressions/Olive.Services.DynamicExpressions.nuspec @@ -0,0 +1,13 @@ + + + + Olive + 0.0.1 + Olive + Geeks Ltd + https://github.com/Geeksltd/Olive + + Copyright ©2017 Geeks Ltd - All rights reserved. + + + \ No newline at end of file diff --git a/Services/Olive.Services.Email/Email.Sending/EmailExtensions.cs b/Services/Olive.Services.Email/Email.Sending/EmailExtensions.cs new file mode 100644 index 000000000..f62306fc5 --- /dev/null +++ b/Services/Olive.Services.Email/Email.Sending/EmailExtensions.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Olive.Entities; + +namespace Olive.Services.Email +{ + public static class EmailExtensions + { + /// + /// Gets the mandatory placeholder tokens for this template. + /// + public static IEnumerable GetPlaceholderTokens(this IEmailTemplate template) => + template.MandatoryPlaceholders.Or("").Split(',').Trim().Select(t => $"[#{t.ToUpper()}#]"); + + /// + /// Ensures the mandatory placeholders are all specified in this template. + /// + public static void EnsurePlaceholders(this IEmailTemplate template) + { + // Make sure that all place holders appear in the email body or subject. + var missingElements = template.GetPlaceholderTokens().Except(t => (template.Subject + template.Body).Contains(t)); + if (missingElements.Any()) + throw new ValidationException("Email template subject or body must have all place-holders for {0}. The missing ones are: {1}", template.Key, missingElements.ToString(", ")); + } + + /// + /// Merges the subjcet of this email template with the specified data. + /// + /// The email template + /// An anonymouse object. All property names should correspond to the placeholder names. + /// For example: new {FirstName = GetFirstName() , LastName = "john"} + public static string MergeSubject(this IEmailTemplate template, object mergeData) => Merge(template.Subject, mergeData); + + /// + /// Merges the body of this email template with the specified data. + /// + /// The email template + /// An anonymouse object. All property names should correspond to the placeholder names. + /// For example: new {FirstName = GetFirstName() , LastName = "john"} + public static string MergeBody(this IEmailTemplate template, object mergeData) => Merge(template.Body, mergeData); + + /// + /// Merges the specified template with the provided. + /// + static string Merge(string template, object mergeData) + { + var result = template; + + foreach (var p in mergeData.GetType().GetProperties()) + { + var key = $"[#{p.Name.ToUpper()}#]"; + var value = $"{p.GetValue(mergeData)}"; + + result = result.Replace(key, value); + } + + return result; + } + + /// + /// Attaches a file to this email. + /// + public static void Attach(this IEmailQueueItem mail, Blob file) + { + if (file == null) throw new ArgumentNullException(nameof(file)); + if (file.IsEmpty()) return; + + if (file.LocalPath.IsEmpty()) + throw new ArgumentException("In-memory blob instances cannot be used for email attachment. It should be saved on disk first."); + + var reference = file.GetOwnerPropertyReference(); + if (reference.HasValue()) + { + var json = JsonConvert.SerializeObject(new { PropertyReference = reference }); + + if (mail.Attachments.IsEmpty()) mail.Attachments = json; + else mail.Attachments += "|" + json; + } + else + { + Attach(mail, file.LocalPath); + } + } + + /// + /// Attaches a file to this email. + /// + /// The email queue item. + /// The path of the attachment file. + /// This must be the physical path of a file inside the running application. + public static void Attach(this IEmailQueueItem mail, string filePath) + { + if (filePath.IsEmpty()) throw new ArgumentNullException(nameof(filePath)); + + var basePath = AppDomain.CurrentDomain.BaseDirectory.ToLower(); + + if (filePath.ToLower().StartsWith(basePath)) // Relative: + filePath = filePath.Substring(basePath.Length).TrimStart("\\"); + + if (mail.Attachments.IsEmpty()) mail.Attachments = filePath; + else mail.Attachments += "|" + filePath; + } + + /// + /// Attaches the specified byte array content to this email as an attachment. + /// + public static void Attach(this IEmailQueueItem mail, byte[] fileData, string name, string contentId, bool isLinkedResource = false) + { + var data = new { Contents = fileData.ToBase64String(), Name = name, ContentId = contentId, IsLinkedResource = isLinkedResource }; + var json = JsonConvert.SerializeObject(data); + + if (mail.Attachments.IsEmpty()) mail.Attachments = json; + else mail.Attachments += "|" + json; + } + + /// + /// Will send an email and returns true for successful sending. + /// + public static async Task Send(this IEmailQueueItem mailItem) => await EmailService.Send(mailItem); + + /// + /// Records an unsuccessful attempt to send this email. + /// + public static async Task RecordRetry(this IEmailQueueItem emailItem) + { + if (emailItem.IsNew) throw new InvalidOperationException(); + + var retries = emailItem.Retries + 1; + + if (!emailItem.IsNew) + await Entity.Database.Update(emailItem, e => e.Retries = retries); + + // Also update this local instance: + emailItem.Retries = retries; + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Email/Email.Sending/EmailSendingEventArgs.cs b/Services/Olive.Services.Email/Email.Sending/EmailSendingEventArgs.cs new file mode 100644 index 000000000..55333a70e --- /dev/null +++ b/Services/Olive.Services.Email/Email.Sending/EmailSendingEventArgs.cs @@ -0,0 +1,19 @@ +namespace Olive.Services.Email +{ + using System; + using System.Net.Mail; + + public class EmailSendingEventArgs + { + public MailMessage MailMessage { get; } + public IEmailQueueItem Item { get; } + + public Exception Error { get; internal set; } + + public EmailSendingEventArgs(IEmailQueueItem item, MailMessage message) + { + MailMessage = message; + Item = item; + } + } +} diff --git a/Services/Olive.Services.Email/Email.Sending/EmailService.Attachments.cs b/Services/Olive.Services.Email/Email.Sending/EmailService.Attachments.cs new file mode 100644 index 000000000..3de84c744 --- /dev/null +++ b/Services/Olive.Services.Email/Email.Sending/EmailService.Attachments.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Mail; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Olive.Entities; + +namespace Olive.Services.Email +{ + partial class EmailService + { + /// + /// Gets the Attachment objects to be attached to this email. + /// + public static async Task> GetAttachments(this IEmailQueueItem mail) + { + var result = new List(); + + foreach (var attachmentInfo in mail.Attachments.OrEmpty().Split('|').Trim()) + { + var item = await ParseAttachment(attachmentInfo); + if (item != null) result.Add(item); + } + + return result; + } + + public static async Task ParseAttachment(string attachmentInfo) + { + if (attachmentInfo.StartsWith("{")) + { + return await GetAttachmentFromJSon(attachmentInfo); + } + else + { + if (attachmentInfo.StartsWith("\\\\") || Path.IsPathRooted(attachmentInfo) /*(attachment[1] == ':' && attachment[2] == '\\')*/) + // absolute path: + return new Attachment(attachmentInfo); + else + return new Attachment(AppDomain.CurrentDomain.GetPath(attachmentInfo)); + } + } + + static async Task GetAttachmentFromJSon(string attachmentInfo) + { + var data = JsonConvert.DeserializeObject>(attachmentInfo); + + if (data == null) return null; + + var contents = data.GetOrDefault("Contents") as string; + + if (contents.HasValue()) + { + if (data.GetOrDefault("IsLinkedResource").ToStringOrEmpty().TryParseAs() == true) return null; // No attachment needed? + + var stream = new MemoryStream(Convert.FromBase64String(contents)); + var name = data["Name"] as string; + var contentId = data["ContentId"] as string; + + return new Attachment(stream, name) { ContentId = contentId }; + } + + var reference = data.GetOrDefault("PropertyReference") as string; + if (reference.HasValue()) + { + var blob = Blob.FromReference(reference); + return new Attachment(new MemoryStream(await blob.GetFileData()), blob.FileName); + } + + return null; + } + + /// + /// Gets the Linked Resource objects to be attached to this email. + /// + [EscapeGCop("It would cause error if you dispose the result.")] + public static IEnumerable GetLinkedResources(this IEmailQueueItem mail) + { + if (mail.Attachments.HasValue()) + { + foreach (var resource in mail.Attachments.Trim().Split('|').Where(x => x.StartsWith("{"))) + { + var data = JsonConvert.DeserializeObject>(resource); + + if (data == null) continue; + + var contents = data.GetOrDefault("Contents") as string; + + if (contents.IsEmpty()) continue; + + var isLinkedResource = data.GetOrDefault("IsLinkedResource").ToStringOrEmpty().TryParseAs() ?? false; + + if (!isLinkedResource) continue; + + var stream = new MemoryStream(Convert.FromBase64String(contents)); + var name = data["Name"] as string; + var contentId = data["ContentId"] as string; + + yield return new LinkedResource(stream) + { + ContentId = contentId, + ContentType = new System.Net.Mime.ContentType { Name = name } + }; + } + } + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Email/Email.Sending/EmailService.cs b/Services/Olive.Services.Email/Email.Sending/EmailService.cs new file mode 100644 index 000000000..af2c5f25f --- /dev/null +++ b/Services/Olive.Services.Email/Email.Sending/EmailService.cs @@ -0,0 +1,380 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Mail; +using System.Net.Mime; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Olive.Entities; + +namespace Olive.Services.Email +{ + /// + /// Provides email sending services. + /// + public static partial class EmailService + { + const string ALL_CATEGORIES = "*"; + static Type concreteEmailQueueItemType; + static AsyncLock AsyncLock = new AsyncLock(); + static Random Random = new Random(); + + public static int MaximumRetries => Config.Get("Email:Maximum.Retries", 4); + + /// + /// Specifies a factory to instantiate EmailQueueItem objects. + /// + public static Func EmailQueueItemFactory = CreateEmailQueueItem; + + /// + /// Provides a message which can dispatch an email message. + /// Returns whether the message was sent successfully. + /// + public static Func> EmailDispatcher = SendViaSmtp; + + #region Events + + /// + /// Occurs when the smtp mail message for this email is about to be sent. + /// + public static readonly AsyncEvent Sending = new AsyncEvent(); + + /// + /// Occurs when the smtp mail message for this email is sent. Sender is the IEmailQueueItem instance that was sent. + /// + public static readonly AsyncEvent Sent = new AsyncEvent(); + + /// + /// Occurs when an exception happens when sending an email. Sender parameter will be the IEmailQueueItem instance that couldn't be sent. + /// + public static readonly AsyncEvent SendError = new AsyncEvent(); + + #endregion + + #region Factory + + static IEmailQueueItem CreateEmailQueueItem() + { + if (concreteEmailQueueItemType != null) + return Activator.CreateInstance(concreteEmailQueueItemType) as IEmailQueueItem; + + var possible = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => a.References(typeof(IEmailQueueItem).Assembly)) + .SelectMany(a => { try { return a.GetExportedTypes(); } catch { return new Type[0]; /* No logging needed */ } }) + .Where(t => t.IsClass && !t.IsAbstract && t.Implements()).ToList(); + + if (possible.Count == 0) + throw new Exception("No type in the currently loaded assemblies implements IEmailQueueItem."); + + if (possible.Count > 1) + throw new Exception("More than one type in the currently loaded assemblies implement IEmailQueueItem:" + possible.Select(x => x.FullName).ToString(" and ")); + + concreteEmailQueueItemType = possible.Single(); + return CreateEmailQueueItem(); + } + + #endregion + + static bool IsSendingPermitted(string to) + { + var permittedDomains = Config.Get("Email:Permitted.Domains").Or("geeks.ltd.uk|uat.co").ToLowerOrEmpty(); + if (permittedDomains == "*") return true; + + if (permittedDomains.Split('|').Trim().Any(d => to.TrimEnd(">").EndsWith("@" + d))) return true; + + var permittedAddresses = Config.Get("Email:Permitted.Addresses").ToLowerOrEmpty().Split('|').Trim(); + + return permittedAddresses.Any() && new MailAddress(to).Address.IsAnyOf(permittedAddresses); + } + + /// + /// Tries to sends all emails. + /// + public static async Task SendAll() => await SendAll(ALL_CATEGORIES, TimeSpan.Zero); + + /// + /// Tries to sends all emails. + /// + /// The category of the emails to send. Use "*" to indicate "all emails". + public static async Task SendAll(string category) => await SendAll(category, TimeSpan.Zero); + + /// + /// Tries to sends all emails. + /// + /// The time to wait in between sending each outstanding email. + public static async Task SendAll(TimeSpan delay) => await SendAll(ALL_CATEGORIES, delay); + + /// + /// Tries to sends all emails. + /// + /// The category of the emails to send. Use "*" to indicate "all emails". + public static async Task SendAll(string category, TimeSpan delay) + { + using (await AsyncLock.Lock()) + { + foreach (var mail in (await Entity.Database.GetList()).OrderBy(e => e.Date).ToArray()) + { + if (mail.Retries >= MaximumRetries) continue; + + if (category != ALL_CATEGORIES) + { + if (category.IsEmpty() && mail.Category.HasValue()) continue; + if (category != mail.Category) continue; + } + + if (delay > TimeSpan.Zero) + { + var multiply = 1 + (Random.NextDouble() - 0.5) / 4; // from 0.8 to 1.2 + + try + { + await Task.Delay(TimeSpan.FromMilliseconds(delay.TotalMilliseconds * multiply)); + } + catch (ThreadAbortException) + { + // Application terminated. + return; + } + } + + try + { + if (await mail.Send() && !mail.IsNew) + await Entity.Database.Delete(mail); + } + catch (Exception ex) + { + Log.Error("Could not send a queued email item " + mail.GetId(), ex); + } + } + } + } + + /// + /// Will try to send the specified email and returns true for successful sending. + /// + public static async Task Send(IEmailQueueItem mailItem) + { + if (mailItem == null) throw new ArgumentNullException(nameof(mailItem)); + + if (mailItem.Retries >= MaximumRetries) return false; + + MailMessage mail = null; + + try + { + using (mail = await CreateMailMessage(mailItem)) + { + if (mail == null) return false; + return await EmailDispatcher(mailItem, mail); + } + } + catch (Exception ex) + { + await SendError.Raise(new EmailSendingEventArgs(mailItem, mail) { Error = ex }); + await mailItem.RecordRetry(); + Log.Error($"Error in sending an email for this EmailQueueItem of '{mailItem.GetId()}'", ex); + return false; + } + } + + static async Task SendViaSmtp(IEmailQueueItem mailItem, MailMessage mail) + { + // Developer note: Web.config setting for SSL is designed to take priority over the specific setting of the email. + // If in your application you want the email item's setting to take priority, do this: + // 1. Remove the 'Email->Enable.Ssl' setting from appsettings.json totally. + // 2. If you need a default value, use your application's Global Settings object and use that value everywhere you create an EmailQueueItem. + using (var smtpClient = new SmtpClient { EnableSsl = Config.Get("Email:Enable.Ssl", mailItem.EnableSsl) }) + { + smtpClient.Configure(); + + if (mailItem.SmtpHost.HasValue()) + smtpClient.Host = mailItem.SmtpHost; + + if (mailItem.SmtpPort.HasValue) + smtpClient.Port = mailItem.SmtpPort.Value; + + if (mailItem.Username.HasValue()) + smtpClient.Credentials = new NetworkCredential(mailItem.Username, mailItem.Password.Or((smtpClient.Credentials as NetworkCredential).Get(c => c.Password))); + + if (Config.IsDefined("Email:Random.Usernames")) + { + var userName = Config.Get("Email:Random.Usernames").Split(',').Trim().PickRandom(); + smtpClient.Credentials = new NetworkCredential(userName, Config.Get("Email:Password")); + } + + await Sending.Raise(new EmailSendingEventArgs(mailItem, mail)); + + await smtpClient.SendMailAsync(mail); + + await Sent.Raise(new EmailSendingEventArgs(mailItem, mail)); + } + + return true; + } + + /// + /// Gets the email items which have been sent (marked as soft deleted). + /// + public static async Task> GetSentEmails() where T : IEmailQueueItem + { + using (new SoftDeleteAttribute.Context(bypassSoftdelete: false)) + { + return (await Entity.Database.GetList()) + .Where(x => EntityManager.IsSoftDeleted((Entity)(IEntity)x)); + } + } + + /// + /// Creates an SMTP mail message for a specified mail item. + /// + static async Task CreateMailMessage(IEmailQueueItem mailItem) + { + // Make sure it's due: + if (mailItem.Date > LocalTime.Now) return null; + + var mail = new MailMessage { Subject = mailItem.Subject.Or("[NO SUBJECT]").Remove("\r", "\n") }; + + #region Set Body + + if (mailItem.Html) + { + var htmlView = AlternateView.CreateAlternateViewFromString(mailItem.Body, new ContentType("text/html; charset=UTF-8")); + + // Add Linked Resources + htmlView.LinkedResources.AddRange(mailItem.GetLinkedResources()); + + mail.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(mailItem.Body.RemoveHtmlTags(), new ContentType("text/plain; charset=UTF-8"))); + mail.AlternateViews.Add(htmlView); + } + else + { + mail.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(mailItem.Body.RemoveHtmlTags(), new ContentType("text/plain; charset=UTF-8"))); + } + + if (mailItem.VCalendarView.HasValue()) + { + var calendarType = new ContentType("text/calendar"); + calendarType.Parameters.Add("method", "REQUEST"); + calendarType.Parameters.Add("name", "meeting.ics"); + + var calendarView = AlternateView.CreateAlternateViewFromString(mailItem.VCalendarView, calendarType); + calendarView.TransferEncoding = TransferEncoding.SevenBit; + + mail.AlternateViews.Add(calendarView); + } + + #endregion + + #region Set Sender + + mail.From = mailItem.GetSender(); + mail.ReplyToList.Add(mailItem.GetReplyTo()); + + #endregion + + #region Set Receivers + + // Add To: + foreach (var address in mailItem.To.Or("").Split(',').Trim().Where(a => IsSendingPermitted(a))) + mail.To.Add(address); + + // Add Cc: + foreach (var address in mailItem.Cc.Or("").Split(',').Trim().Where(a => IsSendingPermitted(a))) + mail.CC.Add(address); + + foreach (var address in Config.Get("Email:Auto.CC.Address").Or("").Split(',').Trim().Where(a => IsSendingPermitted(a))) + mail.CC.Add(address); + + // Add Bcc: + foreach (var address in mailItem.Bcc.Or("").Split(',').Trim().Where(a => IsSendingPermitted(a))) + mail.Bcc.Add(address); + + if (mail.To.None() && mail.CC.None() && mail.Bcc.None()) + return null; + + #endregion + + // Add attachments + mail.Attachments.AddRange(await mailItem.GetAttachments()); + + return mail; + } + + public static MailAddress GetSender(this IEmailQueueItem mailItem) + { + var addressPart = mailItem.SenderAddress.Or(Config.Get("Email:Sender:Address")); + var displayNamePart = mailItem.SenderName.Or(Config.Get("Email:Sender:Name")); + return new MailAddress(addressPart, displayNamePart); + } + + public static MailAddress GetReplyTo(this IEmailQueueItem mailItem) + { + var result = mailItem.GetSender(); + + var asCustomReplyTo = mailItem as ICustomReplyToEmailQueueItem; + if (asCustomReplyTo == null) return result; + + return new MailAddress(asCustomReplyTo.ReplyToAddress.Or(result.Address), + asCustomReplyTo.ReplyToName.Or(result.DisplayName)); + } + + #region Configuration + + /// + /// Configures this smtp client with the specified config file path. + /// + public static void Configure(this SmtpClient client) + { + var setting = Config.Bind("system.net:mailSettings"); + + client.Port = setting.Port; + + if (setting.TargetName.HasValue()) + client.TargetName = setting.TargetName; + + if (client.DeliveryMethod == SmtpDeliveryMethod.Network) + client.Host = setting.Host; + + if (setting.DefaultCredentials && setting.UserName.HasValue() && + setting.Password.HasValue()) + { + client.Credentials = new NetworkCredential(setting.UserName, setting.Password); + } + } + + #endregion + + /// + /// Creates a VCalendar text with the specified parameters. + /// + /// This uniquely identifies the meeting and is used for changes / cancellations. It is recommended to use the ID of the owner object. + public static string CreateVCalendarView(string meetingUniqueIdentifier, DateTime start, DateTime end, string subject, string description, string location) + { + var dateFormat = "yyyyMMddTHHmmssZ"; + + Func cleanUp = s => s.Or("").Remove("\r").Replace("\n", "\\n"); + + var r = new StringBuilder(); + r.AppendLine(@"BEGIN:VCALENDAR"); + r.AppendLine(@"PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN"); + r.AppendLine(@"VERSION:1.0"); + r.AppendLine(@"BEGIN:VEVENT"); + + r.AddFormattedLine(@"DTSTART:{0}", start.ToString(dateFormat)); + r.AddFormattedLine(@"DTEND:{0}", end.ToString(dateFormat)); + r.AddFormattedLine(@"UID:{0}", meetingUniqueIdentifier); + r.AddFormattedLine(@"SUMMARY:{0}", cleanUp(subject)); + r.AppendLine("LOCATION:" + cleanUp(location)); + r.AppendLine("DESCRIPTION:" + cleanUp(description)); + + // bodyCalendar.AppendLine(@"PRIORITY:3"); + r.AppendLine(@"END:VEVENT"); + r.AppendLine(@"END:VCALENDAR"); + + return r.ToString(); + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Email/Email.Sending/IEmailQueueItem.cs b/Services/Olive.Services.Email/Email.Sending/IEmailQueueItem.cs new file mode 100644 index 000000000..e7b2af19b --- /dev/null +++ b/Services/Olive.Services.Email/Email.Sending/IEmailQueueItem.cs @@ -0,0 +1,108 @@ +using System; +using Olive.Entities; + +namespace Olive.Services.Email +{ + public interface ICustomReplyToEmailQueueItem : IEmailQueueItem + { + string ReplyToAddress { get; set; } + string ReplyToName { get; set; } + } + + /// + /// Represents an email generated by the application. + /// + [LogEvents(false), CacheObjects(false)] + public interface IEmailQueueItem : IEntity + { + /// + /// Gets or sets the body of this email. + /// + string Body { get; set; } + + /// + /// Gets or sets the Date of this email. + /// + DateTime Date { get; set; } + + /// + /// Gets or sets whether SSL is enabled. + /// + bool EnableSsl { get; set; } + + /// + /// Gets or sets whether this email is HTML. + /// + bool Html { get; set; } + + /// + /// Gets or sets the Sender Address of this email. + /// + string SenderAddress { get; set; } + + /// + /// Gets or sets the Sender Name for this email. + /// + string SenderName { get; set; } + + /// + /// Gets or sets the Subject of this email. + /// + string Subject { get; set; } + + /// + /// Gets or sets the recipient of this email. + /// + string To { get; set; } + + /// + /// Gets or sets the Attachments information for this email. + /// + string Attachments { get; set; } + + /// + /// Gets or sets the Bcc recipients of this email. + /// + string Bcc { get; set; } + + /// + /// Gets or sets the Bcc recipients of this email. + /// + string Cc { get; set; } + + /// + /// Gets or sets the number of times sending this email has been tried. + /// + int Retries { get; set; } + + /// + /// Gets or sets the VCalendar View of this email. + /// + string VCalendarView { get; set; } + + /// + /// Gets or sets the Username to use for sending this email. + /// + string Username { get; set; } + + /// + /// Gets or sets the Password to use for sending this email. + /// + string Password { get; set; } + + /// + /// Gets or sets the Smtp host address to use for sending this email. + /// + string SmtpHost { get; set; } + + /// + /// Gets or sets the Smtp port to use for sending this email. + /// + int? SmtpPort { get; set; } + + /// + /// Gets or sets the Category for sending this email. + /// + string Category { get; set; } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Email/Email.Sending/IEmailTemplate.cs b/Services/Olive.Services.Email/Email.Sending/IEmailTemplate.cs new file mode 100644 index 000000000..ce46a3221 --- /dev/null +++ b/Services/Olive.Services.Email/Email.Sending/IEmailTemplate.cs @@ -0,0 +1,32 @@ +using Olive.Entities; + +namespace Olive.Services.Email +{ + /// + /// Represents an instance of Email template entity type. + /// + public partial interface IEmailTemplate : IEntity + { + /* -------------------------- Properties -------------------------*/ + + /// + /// Gets or sets the value of Body on this Email template instance. + /// + string Body { get; set; } + + /// + /// Gets or sets the value of Key on this Email template instance. + /// + string Key { get; set; } + + /// + /// Gets or sets the value of MandatoryPlaceholders on this Email template instance. + /// + string MandatoryPlaceholders { get; set; } + + /// + /// Gets or sets the value of Subject on this Email template instance. + /// + string Subject { get; set; } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Email/Email.Sending/SmtpNetworkSetting.cs b/Services/Olive.Services.Email/Email.Sending/SmtpNetworkSetting.cs new file mode 100644 index 000000000..1829ba2db --- /dev/null +++ b/Services/Olive.Services.Email/Email.Sending/SmtpNetworkSetting.cs @@ -0,0 +1,14 @@ +namespace Olive.Services.Email +{ + internal class SmtpNetworkSetting + { + public bool DefaultCredentials { get; set; } + public string Host { get; set; } + public string TargetName { get; set; } + public string ClientDomain { get; set; } + public string Password { get; set; } + public int Port { get; set; } + public string UserName { get; set; } + public bool EnableSsl { get; set; } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Email/EmailTestService.cs b/Services/Olive.Services.Email/EmailTestService.cs new file mode 100644 index 000000000..21e69d994 --- /dev/null +++ b/Services/Olive.Services.Email/EmailTestService.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Mail; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Olive.Entities; +using Olive.Web; + +namespace Olive.Services.Email +{ + public class EmailTestService + { + static readonly Regex LinkPattern = new Regex("(https?://[^ ]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + HttpRequest Request; + HttpResponse Response; + string To, ReturnUrl; + Attachment AttachmentFile; + IEmailQueueItem Email; + bool IsInitialized; + + public EmailTestService(HttpRequest request, HttpResponse response) + { + Request = request; + Response = response; + } + + public async Task Initialize() + { + To = Request.GetValue("to").ToStringOrEmpty().ToLower(); + ReturnUrl = Request.GetReturnUrl(); + if (Request.Has("attachmentInfo")) + AttachmentFile = await EmailService.ParseAttachment(Request.GetValue("attachmentInfo")); + + using (new SoftDeleteAttribute.Context(bypassSoftdelete: true)) + Email = await Request.GetOrDefault("id"); + + IsInitialized = true; + } + + public void ThrowIfItIsNotInitialized() + { + if (!IsInitialized) throw new InvalidOperationException("Initialize the instance before using it."); + } + + void Validate() + { + if (Request.Has("id") && Email == null) throw new Exception("Invalid Email id specified."); + } + + public async Task Process() + { + ThrowIfItIsNotInitialized(); + Validate(); + + string response; + if (AttachmentFile != null) + { + if (IsTextFile(AttachmentFile.Name)) + response = "<< Back to emails
    " + (await AttachmentFile.ContentStream.ReadAllText()).HtmlEncode() + "
    "; + else + { + await Response.Dispatch(await AttachmentFile.ContentStream.ReadAllBytes(), AttachmentFile.Name); + return; + } + } + else if (Email == null) + { + response = await GenerateInbox(); + } + else + { + response = GenerateEmailView(); + } + + await Dispatch(response); + } + + async Task Dispatch(string response) + { + Response.Clear(); + Response.ContentType = "text/html"; + await Response.WriteAsync(""); + + await Response.WriteAsync(""); + await Response.WriteAsync(""); + await Response.WriteAsync(""); + await Response.WriteAsync(""); + + await Response.WriteAsync(response); + + await Response.WriteAsync("Exit Mailbox".FormatWith(ReturnUrl)); + + // TDD hack: + await Response.WriteAsync("Restart Temp Database"); + + await Response.WriteAsync(""); + } + + async Task> GetEmails() + { + using (new SoftDeleteAttribute.Context(bypassSoftdelete: true)) + { + var items = (await Entity.Database.GetList()).Where(x => To.IsEmpty() || (x.To + "," + x.Cc + ", " + x.Bcc).ToLower().Contains(To)); + + return items.OrderByDescending(x => x.Date).Take(15).ToList(); + } + } + + static string GetBodyHtml(string body, bool wasHtml) + { + if (wasHtml) return body; + + body = body.HtmlEncode().Replace("\n", "
    ").Replace("\r", ""); + body = LinkPattern.Replace(body, "$1"); + return body; + } + + async Task GenerateInbox() + { + var r = new StringBuilder(); + + var emails = await GetEmails(); + + r.AppendLine("

    Emails sent to " + To.Or("ALL") + "

    "); + r.AppendLine(""); + r.AppendLine(""); + r.AppendLine(""); + r.AppendLine(""); + r.AppendLine(""); + r.AppendLine(""); + r.AppendLine(""); + r.AppendLine(""); + r.AppendLine(""); + r.AppendLine(""); + r.AppendLine(""); + + if (emails.None()) + { + r.AppendLine(""); + r.AppendLine(""); + r.AppendLine(""); + } + else + { + foreach (var item in emails) + { + r.AppendLine(""); + r.AddFormattedLine("", item.Date.ToString("yyyy-MM-dd")); + r.AddFormattedLine("", item.Date.ToSmallTime()); + r.AddFormattedLine("", GetFrom(item)); + r.AddFormattedLine("", item.To); + r.AddFormattedLine("", item.Cc); + r.AddFormattedLine("", item.Bcc); + + r.AddFormattedLine("", + item.GetId(), To, ReturnUrl.UrlEncode(), item.Subject.Or("[NO SUBJECT]").HtmlEncode()); + + r.AddFormattedLine("", GetAttachmentLinks(item)); + + r.AppendLine(""); + } + } + + r.AppendLine("
    DateTimeFromToCcBccSubjectAttachments
    No emails in the system
    {0}{0}{0}{0}{0}{0}{3}{0}
    "); + + return r.ToString(); + } + + string GetFrom(IEmailQueueItem email) => email.GetSender().Get(s => s.DisplayName.Or("").HtmlEncode() + s.Address.WithWrappers(" <", ">")); + + async Task GetAttachmentLinks(IEmailQueueItem email) + { + return (await email.Attachments.OrEmpty().Split('|').Trim() + .Select(async f => $"
    {(await EmailService.ParseAttachment(f))?.Name.HtmlEncode()}
    ") + .AwaitAll()).ToString(""); + } + + bool IsTextFile(string fileName) => Path.GetExtension(fileName).ToLower().IsAnyOf(".txt", ".csv", ".xml"); + + string GenerateEmailView() + { + var r = new StringBuilder(); + + r.AppendLine("<< Back"); + r.AppendLine("

    Subject: " + Email.Subject.Or("[NO SUBJECT]") + "

    "); + r.AppendLine(""); + + var body = GetBodyHtml(Email.Body.Or("[EMPTY BODY]"), Email.Html); + + var toShow = new Dictionary { + { "Date", Email.Date.ToString("yyyy-MM-dd") +" at " + Email.Date.ToString("HH:mm") }, + {"From", GetFrom(Email)}, + { "To", Email.To}, + {"Bcc", Email.Bcc}, + {"Cc", Email.Cc}, + {"Subject", Email.Subject.Or("[NO SUBJECT]").HtmlEncode().WithWrappers("", "")}, + {"Body", body.WithWrappers("
    " ,"
    ") }, + {"Attachments", GetAttachmentLinks(Email) } + }; + + foreach (var item in toShow.Where(x => x.Value.ToStringOrEmpty().HasValue())) + { + r.AppendLine("
    "); + r.AddFormattedLine("", item.Key.HtmlEncode()); + r.AddFormattedLine("", item.Value); + + r.AppendLine(""); + } + + r.AppendLine("
    {0}:{0}
    "); + + return r.ToString(); + } + } +} diff --git a/Services/Olive.Services.Email/ExtensionMethods.cs b/Services/Olive.Services.Email/ExtensionMethods.cs new file mode 100644 index 000000000..4ae9dc684 --- /dev/null +++ b/Services/Olive.Services.Email/ExtensionMethods.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; +using Olive.Entities; +using Olive.Web; + +namespace Olive.Services.Email +{ + public static class ExtensionMethods + { + /// + /// Sends this error as a notification email to the address in web.config as Error.Notification.Receiver. + /// + public static Task SendAsNotification(this Exception error) => + SendAsNotification(error, Config.Get("Error.Notification.Receiver")); + + /// + /// Sends this error as a notification email to the address in web.config as Error.Notification.Receiver. + /// + public static async Task SendAsNotification(this Exception error, string toNotify) + { + var context = Context.HttpContextAccessor.HttpContext; + + if (toNotify.IsEmpty()) + return null; + var email = EmailService.EmailQueueItemFactory(); + email.To = toNotify; + email.Subject = "Error In Application"; + email.Body = $"URL: {context?.Request?.ToRawUrl()}{Environment.NewLine}" + + $"IP: {context?.Connection.RemoteIpAddress}{Environment.NewLine}" + + $"User: {ApplicationEventManager.GetCurrentUserId(context?.User)}{Environment.NewLine}" + + error.ToLogString(error.Message); + await Entity.Database.Save(email); + return email; + } + } +} diff --git a/Services/Olive.Services.Email/Olive.Services.Email.csproj b/Services/Olive.Services.Email/Olive.Services.Email.csproj new file mode 100644 index 000000000..861a48edc --- /dev/null +++ b/Services/Olive.Services.Email/Olive.Services.Email.csproj @@ -0,0 +1,25 @@ + + + + netcoreapp2.0 + Olive.Services.Email + Olive.Services.Email + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.Email.xml + 1701;1702;1705;1591;1573 + + + + + + + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Email/Package.nuspec b/Services/Olive.Services.Email/Package.nuspec new file mode 100644 index 000000000..1b36ad032 --- /dev/null +++ b/Services/Olive.Services.Email/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Email + 1.0.4 + Olive Email (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Excel/ExcelCell.cs b/Services/Olive.Services.Excel/ExcelCell.cs new file mode 100644 index 000000000..a34ddf773 --- /dev/null +++ b/Services/Olive.Services.Excel/ExcelCell.cs @@ -0,0 +1,39 @@ +using System; + +namespace Olive.Services.Excel +{ + public class ExcelCell + { + /// + /// Initializes a new instance of the class. + /// + public ExcelCell() => Style = new ExcelCellStyle(); + + public ExcelCell(string text) : this() => Text = text; + + public ExcelCell SetStyle(Action setter) + { + setter?.Invoke(Style); + return this; + } + + /// + /// Gets or sets the text of this cell. + /// + public string Text { get; set; } + + /// + /// Gets or sets the type of this cell. + /// + public string Type { get; set; } + + public ExcelCellStyle Style { get; set; } + + /// + /// Determines if this cell has the same style as the specifying one. + /// + internal bool MatchStyle(ExcelCell other) => Style == other.Style; + + public override string ToString() => Text; + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Excel/ExcelCellStyle.cs b/Services/Olive.Services.Excel/ExcelCellStyle.cs new file mode 100644 index 000000000..ce6ec9b0d --- /dev/null +++ b/Services/Olive.Services.Excel/ExcelCellStyle.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Olive.Web; + +namespace Olive.Services.Excel +{ + /// + /// Provides styles for excel cells. + /// + public class ExcelCellStyle + { + /// + /// Initializes a new instance of the class. + /// + public ExcelCellStyle() => Italic = false; + + #region Alignment + + /// + /// Gets or sets the horizontal alignment of this style. + /// + public ExcelExporter.HorizentalAlignment Alignment + { + get => GetSetting("Alignment.Horizontal", ExcelExporter.HorizentalAlignment.Left); + set => Settings["Alignment.Horizontal"] = ((int)value).ToString(); + } + + #endregion + + #region VerticalAlignment + + /// + /// Gets or sets the vertical alignment of this style. + /// + public ExcelExporter.VerticalAlignment VerticalAlignment + { + get => GetSetting("Alignment.Vertical", ExcelExporter.VerticalAlignment.Center); + set => Settings["Alignment.Vertical"] = ((int)value).ToString(); + } + + #endregion + + #region Orientation + + /// + /// Gets or sets the cell orientation of this style. + /// + public ExcelExporter.CellOrientation Orientation + { + get => GetSetting("Alignment.Orientation", ExcelExporter.CellOrientation.Horizontal); + set => Settings["Alignment.Orientation"] = ((int)value).ToString(); + } + + #endregion + + #region FontSize + + /// + /// Gets or sets the size of the font. + /// + public int FontSize + { + get => GetSetting("Font.FontSize", 10); + set => Settings["Font.FontSize"] = value.ToString(); + } + + #endregion + + #region BackgroundColor + + /// + /// Gets or sets the background color of this style. + /// + public string BackgroundColor + { + get => Settings.TryGet("Interior.Color").Or("#ffffff"); + set => Settings["Interior.Color"] = value; + } + + #endregion + + #region Border Color + + /// + /// Gets or sets the border color of this style. + /// + public string BorderColor + { + get => Settings.TryGet("Border.Color").Or("#000000"); + set => Settings["Border.Color"] = value; + } + + #endregion + + #region BorderWidth + + /// + /// Gets or sets the width of the border. + /// + public int BorderWidth + { + get => Settings.TryGet("Border.Width").TryParseAs() ?? 0; + set + { + if (value < 0 || value > 2) throw new Exception("Border width should be 0, 1 or 2"); + Settings["Border.Width"] = value.ToString(); + } + } + + #endregion + + #region FontName + + /// + /// Gets or sets the font name of this style. + /// + public string FontName + { + get => Settings.TryGet("Font.FontName").Or("Arial"); + set => Settings["Font.FontName"] = value; + } + + #endregion + + #region NumberFormat + + /// + /// Gets or sets the Number format of this style. + /// + public string NumberFormat + { + get => Settings.TryGet("NumberFormat.Format"); + set => Settings["NumberFormat.Format"] = value; + } + + #endregion + + #region Bold + + /// + /// Gets or sets if font should be bold. + /// + public bool Bold + { + get => Settings.TryGet("Font.Bold").TryParseAs() ?? false; + set => Settings["Font.Bold"] = value.ToString(); + } + + #endregion + + #region WrapText + + /// + /// Gets or sets if the text should be wrapped. + /// + public bool WrapText + { + get => Settings.TryGet("WrapText").TryParseAs() ?? true; + set => Settings["WrapText"] = value.ToString(); + } + + #endregion + + #region Italic + + /// + /// Gets or sets if font should be Italic. + /// + public bool Italic + { + get => Settings.TryGet("Font.Italic").TryParseAs() ?? false; + set => Settings["Font.Italic"] = value.ToString(); + } + + #endregion + + #region ForeColor + + /// + /// Gets or sets the background color of this style. + /// + public string ForeColor + { + get => Settings.TryGet("Font.Color").Or("#000000"); + set => Settings["Font.Color"] = value; + } + + #endregion + + #region Manage Style items + + /// + /// Gets or sets the Style of this ExcelColumn. + /// Use ExcelExporter.Style.[Item] to add styles to this. + /// + public Dictionary Settings = new Dictionary(); + + /// + /// Use ExcelExporter.Style.[Item] to add styles. + /// + public ExcelCellStyle Set(string key, string value) + { + Settings[key] = value; + return this; + } + + #endregion + + T GetSetting(string settingKey, T defaultValue) => + (T)(object)(Settings.TryGet(settingKey).TryParseAs() ?? (int)(object)defaultValue); + + public override bool Equals(object obj) + { + var style2 = obj as ExcelCellStyle; + if (style2 == null) return false; + + if (ReferenceEquals(this, style2)) return true; + + if (((object)this == null) || ((object)style2 == null)) return false; + + return new[] { + new { Value1 = BackgroundColor , Value2 = style2.BackgroundColor }, + new { Value1 = FontName , Value2 = style2.FontName }, + new { Value1 = ForeColor , Value2 = style2.ForeColor }, + } + .All(s => s.Value2?.ToString().ToLower() == s.Value1.Get(v => v.ToLower())); + } + + public static bool operator ==(ExcelCellStyle style1, ExcelCellStyle style2) + { + if (ReferenceEquals(style1, style2)) return true; + + if ((object)style1 == null) return false; + + return style1.Equals(style2); + } + + public static bool operator !=(ExcelCellStyle style1, ExcelCellStyle style2) => !(style1 == style2); + + public override int GetHashCode() => base.GetHashCode(); + + /// + /// Gets a unique ID for this style. + /// + public string GetStyleId() => "s" + Settings.Select(i => "s" + i.Key + "_" + i.Value).ToString("__").GetHashCode(); + + internal string GenerateStyle() => GenerateStyleTemplate().Replace("[#Style.ID#]", GetStyleId()); + + string GenerateStyleTemplate() + { + var r = new StringBuilder(); + + r.AppendLine(@""); + + return r.ToString(); + } + + string GetCellRotation() + { + switch (Orientation) + { + case ExcelExporter.CellOrientation.Vertical: + return "90"; + case ExcelExporter.CellOrientation.Horizontal: + return "0"; + default: + throw new NotSupportedException("This orientation is not supported."); + } + } + + internal ExcelCellStyle OverrideWith(ExcelCellStyle overrideStyle) + { + var result = new ExcelCellStyle(); + result.Settings = new Dictionary(Settings); + + foreach (var setting in overrideStyle.Settings) + result.Settings[setting.Key] = setting.Value; + + return result; + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Excel/ExcelExporter.ExcelColumn.cs b/Services/Olive.Services.Excel/ExcelExporter.ExcelColumn.cs new file mode 100644 index 000000000..76fd22193 --- /dev/null +++ b/Services/Olive.Services.Excel/ExcelExporter.ExcelColumn.cs @@ -0,0 +1,95 @@ +using System; + +namespace Olive.Services.Excel +{ + partial class ExcelExporter + { + public class ExcelColumn + { + public int? Width { get; set; } + + /// + /// Gets or sets a Workbook Unique integer ID to use for creating styles. + /// + internal int UniqueId { get; set; } + + public ExcelColumn() + { + HeaderStyle = new ExcelCellStyle { BackgroundColor = "#DDDDDD", BorderWidth = 1, BorderColor = "#aaaaaa" }; + GroupingStyle = new ExcelCellStyle { BackgroundColor = "#777777", ForeColor = "#ffffff", Bold = true, Alignment = ExcelExporter.HorizentalAlignment.Center }; + RowStyle = new ExcelCellStyle(); + } + + /// + /// Creates a new ExcelColumn instance. + /// + public ExcelColumn(string headerText, string dataType) + : this() + { + HeaderText = headerText; + DataType = dataType; + } + + /// + /// Sets the specified row style attribute. + /// + public ExcelColumn SetRowStyle(Action setter) + { + setter?.Invoke(RowStyle); + return this; + } + + /// + /// Sets the specified header style attribute. + /// + public ExcelColumn SetHeaderStyle(Action setter) + { + setter?.Invoke(HeaderStyle); + return this; + } + + /// + /// Gets or sets the style of this columns's header cell. + /// + public ExcelCellStyle HeaderStyle { get; set; } + + public ExcelCellStyle GroupingStyle { get; set; } + + /// + /// Gets or sets the style of this columns's data cells. + /// + public ExcelCellStyle RowStyle { get; set; } + + /// + /// Gets or sets the HeaderText of this ExcelColumn. + /// + public string HeaderText { get; set; } + + /// + /// Gets or sets the Type of this ExcelColumn. + /// + public string DataType { get; set; } = "String"; + + /// + /// Gets or sets the Formula of this ExcelColumn. + /// + public string Formula { get; set; } + + /// + /// Gets or sets the group name of this ExcelColumn. + /// + public string GroupName { get; set; } + + public Func Data { get; set; } + + /// + /// Customizes this column. + /// + public ExcelColumn Customize(Action> customisations) + { + customisations?.Invoke(this); + return this; + } + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Excel/ExcelExporter.ExcelDropDownColumn.cs b/Services/Olive.Services.Excel/ExcelExporter.ExcelDropDownColumn.cs new file mode 100644 index 000000000..bd4974bd9 --- /dev/null +++ b/Services/Olive.Services.Excel/ExcelExporter.ExcelDropDownColumn.cs @@ -0,0 +1,25 @@ +namespace Olive.Services.Excel +{ + partial class ExcelExporter + { + class ExcelDropDownColumn : ExcelColumn + { + public ExcelDropDownColumn(string headerText, string dataType, string enumerationName, object[] possibleValues) + : base(headerText, dataType) + { + PossibleValues = possibleValues; + EnumerationName = enumerationName; + } + + /// + /// enumeration items to select from + /// + public object[] PossibleValues { get; set; } + + /// + /// Gets or sets the Name of this ExcelDropDownColumn. + /// + public string EnumerationName { get; set; } + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Excel/ExcelExporter.Output.cs b/Services/Olive.Services.Excel/ExcelExporter.Output.cs new file mode 100644 index 000000000..82882d005 --- /dev/null +++ b/Services/Olive.Services.Excel/ExcelExporter.Output.cs @@ -0,0 +1,14 @@ +namespace Olive.Services.Excel +{ + partial class ExcelExporter + { + /// + /// Specifies the type of output file. + /// + public enum Output + { + ExcelXml, + Csv + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Excel/ExcelExporter.T.cs b/Services/Olive.Services.Excel/ExcelExporter.T.cs new file mode 100644 index 000000000..c24650d56 --- /dev/null +++ b/Services/Olive.Services.Excel/ExcelExporter.T.cs @@ -0,0 +1,616 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Olive; +using Olive.Entities; + +namespace Olive.Services.Excel +{ + public partial class ExcelExporter + { + const int LINK_SEPRATOR_CHAR_CODE = 166, MAX_LENGTH_FOR_SUMMARIZE = 31; + + public static string LinkSeperator => Convert.ToChar(LINK_SEPRATOR_CHAR_CODE).ToString(); + /// + /// Creates a new ExcelExporter instance. + /// + public ExcelExporter(string documentName) + { + DocumentName = documentName; + HeaderGroupBackgroundColor = HeaderBackGroundColor = "#CCCCCC"; + HeaderFontName = "Arial"; + } + + /// + /// Creates a new ExcelExporter instance for a data table. + /// It automatically configures the exporter for all columns and rows of the data table. + /// + public ExcelExporter(System.Data.DataTable dataTable) + { + if (dataTable == null) + throw new ArgumentNullException(nameof(dataTable)); + + DocumentName = dataTable.TableName; + HeaderBackGroundColor = "#CCCCCC"; + + foreach (System.Data.DataColumn column in dataTable.Columns) + AddColumn(column.ColumnName);// TODO: Add data type when necessary + + foreach (System.Data.DataRow row in dataTable.Rows) + AddRow(row.ItemArray); + } + + #region DocumentName + /// + /// Gets or sets the DocumentName of this ExcelExporter. + /// + public string DocumentName { get; set; } + #endregion + + #region HeaderBackGroundColor + /// + /// Gets or sets the HeaderBackGroundColor of this ExcelExporter. + /// + public string HeaderBackGroundColor { get; set; } + #endregion + + #region HeaderFontName + /// + /// Gets or sets the HeaderFontName of this ExcelExporter. + /// + public string HeaderFontName { get; set; } + #endregion + + #region HeaderGroupBackgroundColor + /// + /// Gets or sets the HeaderGroupBackgroundColor of this ExcelExporter. + /// + public string HeaderGroupBackgroundColor { get; set; } + #endregion + + public bool FreezeHeader { get; set; } + + public bool FreezeFirstColumn { get; set; } + + public double DefaultColumnWidth { get; set; } + + /// + /// Gets or sets the IncludeHeader of this ExcelExporter. + /// + public bool ExcludeHeader { get; set; } + + public List> Columns = new List>(); + public List DataRows = new List(); + + public ExcelColumn GetColumn(string headerText) => Columns.FirstOrDefault(x => x.HeaderText == headerText); + + /// + /// Adds a header cell. + /// + public ExcelColumn AddColumn(string headerText) => AddColumn(headerText, "String"); + + /// + /// Adds a header cell. + /// + public ExcelColumn AddColumn(string headerText, string type) => AddColumn(headerText, type, default(Func)); + + /// + /// Adds a header cell. + /// + public ExcelColumn AddColumn(string headerText, string type, Func data) + { + if (headerText.IsEmpty()) + throw new ArgumentNullException(nameof(headerText)); + + if (type.IsEmpty()) + throw new ArgumentNullException(nameof(type)); + + var result = new ExcelColumn(headerText, type) { Data = data }; + Columns.Add(result); + return result; + } + + /// + /// Removes the column with the specified header text. + /// + public void RemoveColumn(string headerText) + { + var columns = Columns.Where(c => c.HeaderText == headerText); + if (columns.Count() > 1) + throw new ArgumentException($"There are {columns.Count()} columns with header text of '{headerText}'. Please use RemoveColumn(index) instead."); + + if (columns.None()) + throw new ArgumentException($"There is no column with header text of '{headerText}'."); + + RemoveColumn(Columns.IndexOf(columns.Single())); + } + + public void RemoveColumn(ExcelColumn column) => RemoveColumn(Columns.IndexOf(column)); + + /// + /// Removes the column at the specified index. + /// + public void RemoveColumn(int columnIndex) + { + if (columnIndex < 0 || columnIndex > Columns.Count - 1) + throw new ArgumentException("columnIndex should be between 0 and " + (Columns.Count - 1)); + + Columns.RemoveAt(columnIndex); + + for (var i = 0; i < DataRows.Count; i++) + DataRows[i] = DataRows[i].Where((r, ind) => ind != columnIndex).ToArray(); + } + + /// + /// Adds a data row to the excel output. + /// Either ExcelCell instances or value objects. + /// + public void AddRow(params object[] dataCells) + { + if (dataCells == null) + throw new ArgumentNullException(nameof(dataCells)); + + if (Columns.All(x => x.GroupName.IsEmpty())) + { + if (dataCells.Length != Columns.Count()) + throw new ArgumentException($"The number of row cell values does not match the number of columns ({dataCells.Length} <> {Columns.Count()})"); + } + else + { + // Do we need validation for grouping mode? + } + + DataRows.Add(dataCells); + } + + public void AddRows(IEnumerable dataItems) + { + if (dataItems == null) + throw new ArgumentNullException(nameof(dataItems)); + + foreach (var column in Columns.Where(c => c.Data == null)) + throw new Exception($"ExcelColumn.Data should be specified for ExcelExporter.AddRows() method to work. For '{column.HeaderText}' it is null."); + + foreach (var item in dataItems) + { + var dataCells = new List(); + + foreach (var column in Columns) + { + try + { + dataCells.Add(column.Data(item)); + } + catch (Exception ex) + { + throw new Exception($"Invoking the Data evaluator for excel column '{column.HeaderText}' failed on {item.GetType().Name} instance: '{item}'", ex); + } + } + + AddRow(dataCells.ToArray()); + } + } + + public ExcelColumn AddDropDownColumn(string headerText, string type, string enumerationName, IEnumerable possibleValues) + { + if (headerText.IsEmpty()) + throw new ArgumentNullException(nameof(headerText)); + + if (type.IsEmpty()) + throw new ArgumentNullException(nameof(type)); + + if (possibleValues == null) + throw new ArgumentNullException(nameof(possibleValues)); + + var result = new ExcelDropDownColumn(headerText, type, enumerationName, possibleValues.ToArray()); + Columns.Add(result); + + return result; + } + + /// + /// Generates the content of the output Excel file. + /// + public string Generate(ExcelExporter.Output output) + { + switch (output) + { + case ExcelExporter.Output.Csv: + return GenerateCsv(); + case ExcelExporter.Output.ExcelXml: + return GenerateExcelXml(this); + default: + throw new NotSupportedException(); + } + } + + string GenerateCsv() + { + var r = new StringBuilder(); + + // Header row: + if (!ExcludeHeader) + r.AppendLine(Columns.Select(c => EscapeCsvValue(c.HeaderText)).ToString(",")); + + // Data rows: + + foreach (var row in DataRows) + { + var fields = new List(); + for (int i = 0; i < row.Length; i++) + { + var cell = row[i]; + var column = Columns[i]; + + var value = cell?.ToString().OrEmpty(); + + if (column.DataType == "Link") + { + if (value.IsEmpty()) + fields.Add(value); + else + { + var parts = value.Split(LinkSeperator.ToCharArray().Single()); + + if (parts.Length != 2) + throw new Exception("Invalid Link value for ExporttoExcel: " + value); + + fields.Add(parts[0] + ": " + parts[1]); + } + } + else + { + fields.Add(value); + } + } + + r.AppendLine(fields.Select(f => EscapeCsvValue(f)).ToString(",")); + } + + return r.ToString(); + } + + static string EscapeCsvValue(string value) + { + if (value.IsEmpty()) return string.Empty; + + value = value.Remove("\r").Replace("\n", "\r\n"); + + if (value.Contains(",") || value.Contains("\"") || value.Contains("\n")) + value = "\"{0}\"".FormatWith(value.Replace("\"", "\"\"")); + + return value; + } + + #region Generate Excel Xml + + string GenerateExcelWorksheet() + { + var result = new StringBuilder(); + result.AddFormattedLine(@"", + DocumentName.Remove("/", @"\", "?", "*", ":", "[", "]", "\r", "\n").KeepReplacing(" ", " ").Summarize(MAX_LENGTH_FOR_SUMMARIZE, enforceMaxLength: true).XmlEncode()); + result.AddFormattedLine(@"", DefaultColumnWidth); + + result.AppendLine(Columns.Select((h, i) => GenerateColumnTag(h, i + 1)).Trim().ToLinesString()); + + result.AppendLine(GenerateHeaderGroupings()); + + if (!ExcludeHeader) + result.AppendLine(GenerateSheetHeaderRow()); + + result.AppendLine(GenerateDataRows()); + + result.AppendLine(@"
    "); + + result.AppendLine(GenerateDropDownDataValidation()); + + result.AppendLine(GenerateWorksheetSettings()); + + result.AppendLine(@"
    "); + + return result.ToString(); + } + + internal string GenerateColumnTag(ExcelColumn column, int index) + { + if (column.Width == null) return null; + + var r = new StringBuilder(); + + r.Append($""); + + return r.ToString(); + } + + string GenerateWorksheetSettings() + { + var frozenHeaderSetting = "11"; + var frozenFirstColumnSetting = "11"; + + return @" + + + + + {0} + {1} + False + False + " + .FormatWith( + frozenHeaderSetting.OnlyWhen(FreezeHeader), + frozenFirstColumnSetting.OnlyWhen(FreezeFirstColumn)); + } + + string GenerateHeaderGroupings() + { + var groups = GetGroups(); + + if (groups.None()) return string.Empty; + + var r = new StringBuilder(); + + r.AppendLine(@""); + + foreach (var g in groups) + r.AddFormattedLine("{2}", g.Style.GetStyleId(), g.Quantity, g.GroupName); + + r.AppendLine(""); + + return r.ToString(); + } + + IEnumerable GetGroups() + { + var result = new List(); + + if (Columns.All(i => i.GroupName.IsEmpty())) return result; // No grouping has been provided. + + foreach (var column in Columns) + { + var previousGroup = result.LastOrDefault(r => r.GroupName == column.GroupName); + + if (previousGroup != null) + { + previousGroup.Quantity++; + } + else + { + previousGroup = new ColumnGroup { GroupName = column.GroupName, Quantity = 0, Style = column.GroupingStyle }; + result.Add(previousGroup); + } + } + + return result; + } + + class ColumnGroup + { + internal string GroupName; + internal int Quantity; + internal ExcelCellStyle Style; + } + + public static string GenerateExcelXml(params ExcelExporter[] sheets) + { + if (sheets == null || sheets.None()) + throw new ArgumentException("No excel sheets specified."); + + if (sheets.GroupBy(s => s.DocumentName).Any(x => x.Count() > 1)) + throw new ArgumentException("Sheet names should be unique. At least 2 sheets in the provided list have the same DocumentName."); + + var r = new StringBuilder(); + + r.AppendLine(@""); + r.AppendLine(@""); + + // Generate styles + r.AppendLine(GenerateStyles(sheets)); + + // NamedRanges: + var namedRanges = sheets.SelectMany(s => s.Columns.OfType>()).Distinct(c => c.EnumerationName); + var nameRangeNodes = namedRanges.Select(c => "".FormatWith(c.EnumerationName, c.PossibleValues.Length)); + r.AddFormattedLine("{0}", nameRangeNodes.ToLinesString()); + + foreach (var sheet in sheets) + r.AppendLine(sheet.GenerateExcelWorksheet()); + + r.AppendLine(namedRanges.Select(c => GenerateDropDownSourceSheet(c)).ToLinesString()); + + r.AppendLine(@""); + + return r.ToString(); + } + + static string GenerateStyles(params ExcelExporter[] sheets) + { + var r = new StringBuilder(); + + r.AppendLine(""); + + // Link style + r.AddFormattedLine(@"", sheets.First().HeaderBackGroundColor); + + // Merge settings: + sheets.Do(s => s.MergeStyles()); + + var uniqueStyles = sheets.SelectMany(x => x.GetAllStyles()).Distinct(x => x.GetStyleId()).ToList(); + foreach (var style in uniqueStyles) + r.AppendLine(style.GenerateStyle()); + + r.AppendLine(""); + + return r.ToString(); + } + + IEnumerable GetAllStyles() + { + var header = Columns.SelectMany(x => new[] { x.HeaderStyle, x.RowStyle }); + var rows = DataRows.SelectMany(x => x.ExceptNull().OfType()).Select(x => x.Style); + var groupings = Columns.Where(c => c.GroupName.HasValue()).Select(x => x.GroupingStyle); + + return header.Concat(rows).Concat(groupings).Distinct(x => x.GetStyleId()).ToArray(); + } + + void MergeStyles() + { + foreach (var row in DataRows) + { + for (int i = 0; i < row.Length; i++) + { + var cell = row[i] as ExcelCell; + + if (cell == null) continue; + + var column = Columns[i]; + + cell.Style = column.RowStyle.OverrideWith(cell.Style); + } + } + } + + /// + /// Generates Hidden Worksheets that contain Possible Values for each DropDown + /// + static string GenerateDropDownSourceSheet(ExcelDropDownColumn column) + { + var rows = column.PossibleValues. + Select(v => $@"{v}"); + + return $@" + + {rows.ToLinesString()} +
    + + SheetHidden + +
    "; + } + + /// + /// DataValidation assigns a DropDown for each cell and restrics possible values to that drop down + /// + string GenerateDropDownDataValidation() + { + return Columns.OfType>().Select(c => + @" + List + R1C{0}:R{1}C{0} + {2} + ".FormatWith(Columns.IndexOf(c) + 1, DataRows.Count + 1, c.EnumerationName)).ToLinesString(); + } + + string GenerateSheetHeaderRow() + { + var r = new StringBuilder(); + + r.AppendLine(@""); + + foreach (var c in Columns) + { + r.AppendFormat("", c.HeaderStyle.GetStyleId()); + r.AddFormattedLine("{0}", c.HeaderText.XmlEncode()); + r.AppendLine(""); + } + + r.AppendLine(""); + + return r.ToString(); + } + + string GenerateDataRows() + { + var r = new StringBuilder(); + + foreach (var row in DataRows) + { + r.AppendLine(@""); + + for (int i = 0; i < row.Length; i++) + { + var cell = row[i]; + var column = Columns[i]; + + var cellInfo = cell as ExcelCell; + + var value = cell?.ToString().OrEmpty(); + + if (column.DataType == "Link") + { + if (value.IsEmpty()) + { + r.AppendLine(""); + } + else + { + var parts = value.Split(LinkSeperator.ToCharArray().Single()); + + if (parts.Length != 2) + throw new Exception("Invalid Link value for ExporttoExcel: " + value); + + r.AddFormattedLine("{1}", + parts[1].XmlEncode(), + parts[0].XmlEncode()); + } + } + else + { + if (value.HasValue()) value = value.XmlEncode(); + + r.Append(""); + + if (value.HasValue()) + r.AddFormattedLine("{1}", column.DataType, value); + + r.AppendLine(""); + } + } + + r.AppendLine(""); + } + + return r.ToString(); + } + + #endregion + + /// + /// Gets the file extension for a specified output format. + /// + public string GetFileExtension(ExcelExporter.Output output) + { + switch (output) + { + case ExcelExporter.Output.ExcelXml: + return ".xls"; + case ExcelExporter.Output.Csv: + return ".csv"; + default: + throw new NotSupportedException(); + } + } + + public Blob ToDocument(ExcelExporter.Output type) => + new Blob(Generate(type).GetUtf8WithSignatureBytes(), DocumentName + GetFileExtension(type)); + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Excel/ExcelExporter.cs b/Services/Olive.Services.Excel/ExcelExporter.cs new file mode 100644 index 000000000..2117160c5 --- /dev/null +++ b/Services/Olive.Services.Excel/ExcelExporter.cs @@ -0,0 +1,35 @@ +namespace Olive.Services.Excel +{ + public partial class ExcelExporter : ExcelExporter + { + /// + /// Creates a new ExcelExporter instance. + /// + public ExcelExporter(string documentName) : base(documentName) { } + + /// + /// Creates a new ExcelExporter instance. + /// + public ExcelExporter(System.Data.DataTable dataTable) : base(dataTable) { } + + public enum CellOrientation : int { Vertical, Horizontal } + public enum VerticalAlignment : int { Top, Center, Bottom } + public enum HorizentalAlignment : int { Left, Center, Right } + + public class Styles + { + public const string NumberFormat_Format = "NumberFormat.Format"; + public const string Alignment_Vertical = "Alignment.Vertical"; + public const string Alignment_Horizontal = "Alignment.Horizontal"; + + public const string Font_FontName = "Font.FontName"; + public const string Font_Bold = "Font.Bold"; + public const string Font_Italic = "Font.Italic"; + public const string Font_Size = "Font.Size"; + public const string Font_Color = "Font.Color"; + + public const string Interior_Color = "Interior.Color"; + public const string Interior_Pattern = "Interior.Pattern"; + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Excel/ExtensionMethods.cs b/Services/Olive.Services.Excel/ExtensionMethods.cs new file mode 100644 index 000000000..7c5548995 --- /dev/null +++ b/Services/Olive.Services.Excel/ExtensionMethods.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Olive.Entities; +using Olive.Entities.Data; + +namespace Olive.Services.Excel +{ + // The following piece of code is copied here due to compile error in data project. I will decide about it later. + public static class ExtensionMethods + { + public static async Task GenerateReport(this DataAccessProfiler profiler, bool snapshot = false) + { + var lines = DataAccessProfiler.GenerateReport(snapshot); + var exporter = new ExcelExporter("Sql.Profile.Report"); + + exporter.AddColumn("Command"); + exporter.AddColumn("Calls"); + exporter.AddColumn("Total ms"); + exporter.AddColumn("Longest ms"); + exporter.AddColumn("Average ms"); + exporter.AddColumn("Median ms"); + + foreach (var line in lines.OrderByDescending(x => x.Total)) + exporter.AddRow(line.Command, line.Calls, line.Total, line.Longest, line.Average, line.Median); + + var result = exporter.Generate(ExcelExporter.Output.Csv); + + var file = Blob.GetPhysicalFilesRoot(Blob.AccessMode.Secure).EnsureExists().GetFile("Sql.Profile." + DateTime.Now.ToOADate() + ".csv"); + + await file.WriteAllText(result); + + return file; + } + } +} diff --git a/Services/Olive.Services.Excel/Olive.Services.Excel.csproj b/Services/Olive.Services.Excel/Olive.Services.Excel.csproj new file mode 100644 index 000000000..6a6364bfc --- /dev/null +++ b/Services/Olive.Services.Excel/Olive.Services.Excel.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp2.0 + Olive.Services.Excel + Olive.Services.Excel + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.Excel.xml + 1701;1702;1705;1591;1573 + + + + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Excel/Package.nuspec b/Services/Olive.Services.Excel/Package.nuspec new file mode 100644 index 000000000..645a844b3 --- /dev/null +++ b/Services/Olive.Services.Excel/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Excel + 1.0.3 + Olive Excel (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.GeoLocation/GeoLocationExtensions.cs b/Services/Olive.Services.GeoLocation/GeoLocationExtensions.cs new file mode 100644 index 000000000..0743f17f0 --- /dev/null +++ b/Services/Olive.Services.GeoLocation/GeoLocationExtensions.cs @@ -0,0 +1,53 @@ +using System; +using System.ComponentModel; + +namespace Olive.Services.GeoLocation +{ + [EditorBrowsable(EditorBrowsableState.Never)] + public static class GeoLocationExtensions + { + const int EARTH_RADIUS = 3963; + + /// + /// Gets the geo distance in miles between this and another specified location. + /// + public static double? GetDistance(this IGeoLocation from, IGeoLocation to) + { + if (from == null) return null; + + if (to == null) return null; + + var dLat = (to.Latitude - from.Latitude).ToRadians(); + var dLon = (to.Longitude - from.Longitude).ToRadians(); + + var a1 = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + + Math.Cos(from.Latitude.ToRadians()) * Math.Cos(to.Latitude.ToRadians()) * + Math.Sin(dLon / 2) * Math.Sin(dLon / 2); + + var c1 = 2 * Math.Atan2(Math.Sqrt(a1), Math.Sqrt(1 - a1)); + + var result = EARTH_RADIUS * c1; + + if (result > 100) + return result.Round(0); + else + return result.Round(1); + } + + /// + /// Gets the geo distance in miles between this located object and a specified location. + /// + public static double? GetDistance(this IGeoLocated from, IGeoLocation to) => GetDistance(from.Get(l => l.GetLocation()), to); + + /// + /// Gets the geo distance in miles between this location and a specified located object. + /// + public static double? GetDistance(this IGeoLocation from, IGeoLocated to) => GetDistance(from, to.Get(l => l.GetLocation())); + + /// + /// Gets the geo distance in miles between this and another specified located object. + /// + public static double? GetDistance(this IGeoLocated from, IGeoLocated to) => + GetDistance(from.Get(l => l.GetLocation()), to.Get(l => l.GetLocation())); + } +} \ No newline at end of file diff --git a/Services/Olive.Services.GeoLocation/GeoLocationService.cs b/Services/Olive.Services.GeoLocation/GeoLocationService.cs new file mode 100644 index 000000000..6a111eac2 --- /dev/null +++ b/Services/Olive.Services.GeoLocation/GeoLocationService.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Xml.Linq; +using Olive.Web; + +namespace Olive.Services.GeoLocation +{ + /// + /// Provides location services. + /// + public class GeoLocationService + { + const string DIRECTION_URL = "https://" + "maps.googleapis.com/maps/api/distancematrix/xml?units=imperial"; + public static string GoogleClientKey = Config.Get("Google.Maps.Api.Client.Key"); + public static string GoogleSignatureKey = Config.Get("Google.Maps.Api.Signature"); + + static ConcurrentDictionary CachedLocations = new ConcurrentDictionary(); + + /// + /// Gets the Geo Location of a specified postcode using Google API. + /// This method has daily usage limit of 25000 calls. + /// + public static GeoLocation GetPostcodeLocation(string postcode, string countryCode = "GB") + { + var fullAddress = postcode + "," + countryCode; + + return CachedLocations.GetOrAdd(fullAddress, address => + { + var clientParameter = "key".OnlyWhen(GoogleSignatureKey.IsEmpty()).Or("client"); + + var url = "https://" + $"maps.googleapis.com/maps/api/geocode/xml?address={address}&sensor=false" + + GoogleClientKey.UrlEncode().WithPrefix($"&{clientParameter}=") + + GoogleSignatureKey.UrlEncode().WithPrefix("&signature="); + + var response = (new WebClient().DownloadString(url)).To(); + + var status = response.GetValue("status"); + + if (status == "ZERO_RESULTS") return null; + if (status != "OK") throw new Exception("Google API Error: " + status + "\r\n\r\n" + response); + + var location = response.Element("result").Get(x => x.Element("geometry")).Get(x => x.Element("location")); + + if (location == null) throw new Exception("Unexpected result from Google API: \r\n\r\n" + response); + + return new GeoLocation + { + Latitude = location.GetValue("lat").To(), + Longitude = location.GetValue("lng").To() + }; + }); + } + + /// + /// Gets the distance between 2 locations in miles. + /// + public static double? CalculateDistance(string postcode1, string postcode2, string countryCode = "GB") + { + var location1 = GetPostcodeLocation(postcode1, countryCode); + if (location1 == null) return null; + + var location2 = GetPostcodeLocation(postcode2, countryCode); + if (location2 == null) return null; + + return location1.GetDistance(location2); + } + + /// + /// Returns the traveling distance in miles using the Google Maps API. + /// + public static async Task CalculateTravelDistance(string fromPostCode, string toPostCode, string countryCode = "GB") + { + var fromLocation = GetPostcodeLocation(fromPostCode, countryCode).Get(x => x.Latitude + "," + x.Longitude); + var toLocation = GetPostcodeLocation(toPostCode, countryCode).Get(x => x.Latitude + "," + x.Longitude); + + var url = DIRECTION_URL.AsUri() + .AddQueryString("origins", fromLocation) + .AddQueryString("destinations", toLocation); + + if (GoogleClientKey.HasValue()) + url = url.AddQueryString("key".OnlyWhen(GoogleSignatureKey.IsEmpty()).Or("client"), GoogleClientKey); + + if (GoogleSignatureKey.HasValue()) + url = url.AddQueryString("signature", GoogleSignatureKey); + + var response = (await url.Download()).To(); + + var status = response.GetValue("status"); + + if (status == "ZERO_RESULTS") return null; + if (status != "OK") throw new Exception("Google API Error: " + status + "\r\n\r\n" + response); + + var miles = response.Element("row").Get(r => r.Element("element").Get(e => e.Element("distance")).Get(d => d.Element("text"))); + + if (miles == null) throw new Exception("Unexpected result from Google API: \r\n\r\n" + response); + + var result = miles.Value.Split(' ').FirstOrDefault().TryParseAs(); + if (result == null) + throw new Exception("Unexpected result format from Google API: \r\n\r\n" + response); + + return result; + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.GeoLocation/IGeoLocated.cs b/Services/Olive.Services.GeoLocation/IGeoLocated.cs new file mode 100644 index 000000000..ac9f9e25d --- /dev/null +++ b/Services/Olive.Services.GeoLocation/IGeoLocated.cs @@ -0,0 +1,7 @@ +namespace Olive.Services.GeoLocation +{ + public interface IGeoLocated + { + IGeoLocation GetLocation(); + } +} diff --git a/Services/Olive.Services.GeoLocation/IGeoLocation.cs b/Services/Olive.Services.GeoLocation/IGeoLocation.cs new file mode 100644 index 000000000..6c31e17e2 --- /dev/null +++ b/Services/Olive.Services.GeoLocation/IGeoLocation.cs @@ -0,0 +1,14 @@ +namespace Olive.Services.GeoLocation +{ + public interface IGeoLocation + { + double Longitude { get; } + double Latitude { get; } + } + + public class GeoLocation : IGeoLocation + { + public double Longitude { get; set; } + public double Latitude { get; set; } + } +} diff --git a/Services/Olive.Services.GeoLocation/Olive.Services.GeoLocation.csproj b/Services/Olive.Services.GeoLocation/Olive.Services.GeoLocation.csproj new file mode 100644 index 000000000..dfb769ca5 --- /dev/null +++ b/Services/Olive.Services.GeoLocation/Olive.Services.GeoLocation.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp2.0 + Olive.Services.GeoLocation + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.GeoLocation.xml + 1701;1702;1705;1591;1573 + + + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.GeoLocation/Package.nuspec b/Services/Olive.Services.GeoLocation/Package.nuspec new file mode 100644 index 000000000..da1759f22 --- /dev/null +++ b/Services/Olive.Services.GeoLocation/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.GeoLocation + 1.0.3 + Olive GeoLocation (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Globalization/ExtensionMethods.cs b/Services/Olive.Services.Globalization/ExtensionMethods.cs new file mode 100644 index 000000000..76d9c0a0e --- /dev/null +++ b/Services/Olive.Services.Globalization/ExtensionMethods.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using Olive.Entities; + +namespace Olive.Services.Globalization +{ + public static class ExtensionMethods + { + /// + /// Returns the translation of this object's string representation. + /// + public static Task ToString(this IEntity instance, ILanguage language) + { + if (language == null) + throw new ArgumentNullException(nameof(language)); + + return Translator.Translate(instance.ToString(), language); + } + } +} diff --git a/Services/Olive.Services.Globalization/GoogleAutoDetect.cs b/Services/Olive.Services.Globalization/GoogleAutoDetect.cs new file mode 100644 index 000000000..db07fde37 --- /dev/null +++ b/Services/Olive.Services.Globalization/GoogleAutoDetect.cs @@ -0,0 +1,59 @@ +using System.Runtime.Serialization; +using System.Threading.Tasks; +using Olive.Entities; + +namespace Olive.Services.Globalization +{ + [DataContract] + internal class GoogleAutoDetectJsonResponseRootObject + { + [DataMember] + public GoogleAutoDetectJsonResponseData data { get; set; } + } + + [DataContract] + internal class GoogleAutoDetectJsonResponseData + { + [DataMember] + public GoogleAutoDetectJsonResponseDetection[][] detections { get; set; } + } + + [DataContract] + internal class GoogleAutoDetectJsonResponseDetection + { + [DataMember] + public string language { get; set; } + [DataMember] + public bool isReliable { get; set; } + [DataMember] + public float confidence { get; set; } + } + + /// + /// Response returned by Google API for each auto-detect language request + /// + public class GoogleAutodetectResponse + { + /// ISO Code + public string ISOCode { get; private set; } + /// Confidence [0;1] about the detection + public double? Confidence { get; private set; } + // public bool IsReliable { get; set; } // Deprecated + + /// + /// Initialize a new Google auto-detect response + /// + public GoogleAutodetectResponse(string isoCode, double? confidence) + { + ISOCode = isoCode; + Confidence = confidence; + } + + /// Language detected based on iso639-1 + public async Task GetLanguage() + { + var iso6391Code = ISOCode.Substring(0, 2).ToLowerInvariant(); // ISO639-1 are two letters code, but for Chinese Google returns 2 different codes (zh-CN for simplified and zh-TW for traditional) + return await Entity.Database.FirstOrDefault(l => l.IsoCode.ToLowerInvariant() == iso6391Code); + } + } +} diff --git a/Services/Olive.Services.Globalization/GoogleTranslate.cs b/Services/Olive.Services.Globalization/GoogleTranslate.cs new file mode 100644 index 000000000..b0871983b --- /dev/null +++ b/Services/Olive.Services.Globalization/GoogleTranslate.cs @@ -0,0 +1,25 @@ +using System.Runtime.Serialization; + +namespace Olive.Services.Globalization +{ + [DataContract] + internal class GoogleTranslateJsonResponseRootObject + { + [DataMember] + public GoogleTranslateJsonResponseData data { get; set; } + } + + [DataContract] + internal class GoogleTranslateJsonResponseData + { + [DataMember] + public GoogleTranslateJsonResponseTranslation[] translations { get; set; } + } + + [DataContract] + internal class GoogleTranslateJsonResponseTranslation + { + [DataMember] + public string translatedText { get; set; } + } +} diff --git a/Services/Olive.Services.Globalization/ILanguage.cs b/Services/Olive.Services.Globalization/ILanguage.cs new file mode 100644 index 000000000..894ede70b --- /dev/null +++ b/Services/Olive.Services.Globalization/ILanguage.cs @@ -0,0 +1,11 @@ +using Olive.Entities; + +namespace Olive.Services.Globalization +{ + public interface ILanguage : IEntity + { + string Name { get; } + string IsoCode { get; } + bool IsDefault { get; } + } +} diff --git a/Services/Olive.Services.Globalization/IPhrase.cs b/Services/Olive.Services.Globalization/IPhrase.cs new file mode 100644 index 000000000..1d7210702 --- /dev/null +++ b/Services/Olive.Services.Globalization/IPhrase.cs @@ -0,0 +1,11 @@ +using Olive.Entities; + +namespace Olive.Services.Globalization +{ + public interface IPhraseTranslation : IEntity + { + string Phrase { get; } + string Translation { get; } + ILanguage Language { get; } + } +} diff --git a/Services/Olive.Services.Globalization/LanguageExtensions.cs b/Services/Olive.Services.Globalization/LanguageExtensions.cs new file mode 100644 index 000000000..87e6de64c --- /dev/null +++ b/Services/Olive.Services.Globalization/LanguageExtensions.cs @@ -0,0 +1,9 @@ +using System.ComponentModel; + +namespace Olive.Services.Globalization +{ + [EditorBrowsable(EditorBrowsableState.Never)] + public static class LanguageExtensions + { + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Globalization/Olive.Services.Globalization.csproj b/Services/Olive.Services.Globalization/Olive.Services.Globalization.csproj new file mode 100644 index 000000000..8a8ff3a18 --- /dev/null +++ b/Services/Olive.Services.Globalization/Olive.Services.Globalization.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp2.0 + Olive.Services.Globalization + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.Globalization.xml + 1701;1702;1705;1591;1573 + + + + + + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Globalization/Package.nuspec b/Services/Olive.Services.Globalization/Package.nuspec new file mode 100644 index 000000000..89faad09e --- /dev/null +++ b/Services/Olive.Services.Globalization/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Globalization + 1.0.3 + Olive Globalization (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Globalization/TranslationDownloadedEventArgs.cs b/Services/Olive.Services.Globalization/TranslationDownloadedEventArgs.cs new file mode 100644 index 000000000..606a70705 --- /dev/null +++ b/Services/Olive.Services.Globalization/TranslationDownloadedEventArgs.cs @@ -0,0 +1,38 @@ +using System; + +namespace Olive.Services.Globalization +{ + public class TranslationDownloadedEventArgs : EventArgs + { + /// + /// Creates a new TranslationDownloadedEventArgs instance. + /// + public TranslationDownloadedEventArgs(string word, ILanguage language, string translation) + { + Word = word; + Language = language; + Translation = translation; + } + + #region Word + /// + /// Gets or sets the Word of this TranslationDownloadedEventArgs. + /// + public string Word { get; private set; } + #endregion + + #region Language + /// + /// Gets or sets the Language of this TranslationDownloadedEventArgs. + /// + public ILanguage Language { get; private set; } + #endregion + + #region Translation + /// + /// Gets or sets the Translation of this TranslationDownloadedEventArgs. + /// + public string Translation { get; private set; } + #endregion + } +} diff --git a/Services/Olive.Services.Globalization/TranslationRequestedEventArgs.cs b/Services/Olive.Services.Globalization/TranslationRequestedEventArgs.cs new file mode 100644 index 000000000..db28128b1 --- /dev/null +++ b/Services/Olive.Services.Globalization/TranslationRequestedEventArgs.cs @@ -0,0 +1,13 @@ +namespace Olive.Services.Globalization +{ + using System; + using System.ComponentModel; + + public class TranslationRequestedEventArgs : CancelEventArgs + { + public string PhraseInDefaultLanguage { get; internal set; } + public ILanguage Language { get; internal set; } + + public Func TranslationProvider; + } +} diff --git a/Services/Olive.Services.Globalization/Translator.cs b/Services/Olive.Services.Globalization/Translator.cs new file mode 100644 index 000000000..ffd581878 --- /dev/null +++ b/Services/Olive.Services.Globalization/Translator.cs @@ -0,0 +1,394 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Web; +using HtmlAgilityPack; +using Olive.Entities; +using Olive.Web; + +namespace Olive.Services.Globalization +{ + /// + /// Provides translation services. + /// + public static class Translator + { + /// Length of the query without the phrase + static readonly int GOOGLE_TRANSLATE_QUERY_LENGTH = 115; + + /// Maximum number of characters for each request to Google API + static readonly int GOOGLE_TRANSLATE_LIMIT = 2000; + + /// Maximum number of characters for each phrase that can be sent to Google Translate + public static readonly int GOOGLE_PHRASE_LIMIT = GOOGLE_TRANSLATE_LIMIT - GOOGLE_TRANSLATE_QUERY_LENGTH; + + /// Message returned by Google if suspected terms of service abuse. + const string GOOGLE_TERMS_OF_SERVICE_ABUSE_MESSAGE = "Suspected Terms of Service Abuse. Please see http://code.google.com/apis/errors"; + + /// HTML tag for a line break + static readonly string LINE_BREAK_HTML = "
    "; + + /// Unicode value of a HTML line break + static readonly string LINE_BREAK_UNICODE = "\u003cbr /\u003e"; + + public static bool AttemptAutomaticTranslation = true; + static bool IsGoogleTranslateMisconfigured; + + /// + /// Gets the language of the current user from cookie. + /// If no language is specified, then the default language will be used as configured in the database. + /// + public static Func> GetCurrentLanguage = async () => await CookieProperty.Get() ?? await GetDefaultLanguage(); + + static ILanguage DefaultLanguage; + static async Task GetDefaultLanguage() + { + if (DefaultLanguage == null) + { + DefaultLanguage = await Entity.Database.FirstOrDefault(l => l.IsDefault); + + if (DefaultLanguage == null) + { + throw new Exception("There is no default language specified in the system."); + } + } + + return DefaultLanguage; + } + + #region Translate Html + + public static async Task TranslateHtml(string htmlInDefaultLanguage) => await TranslateHtml(htmlInDefaultLanguage, null); + + public static async Task TranslateHtml(string htmlInDefaultLanguage, ILanguage language) + { + if (language == null) language = await GetCurrentLanguage(); + + var htmlDoc = new HtmlDocument(); + htmlDoc.LoadHtml(htmlInDefaultLanguage); + + var docNode = htmlDoc.DocumentNode; + await TranslateNode(docNode, language); + + return docNode.OuterHtml; + } + + static async Task TranslateNode(HtmlNode node, ILanguage language) + { + if (node.InnerHtml.Length == 0 || + (node.NodeType == HtmlNodeType.Text && + !Regex.IsMatch(node.InnerHtml, @"\w+" /* whitespaces */, RegexOptions.Multiline))) + return; + + if (node.Name == "img") + { + var alt = node.Attributes["alt"]; + if (alt != null) + alt.Value = await Translate(alt.Value, language); + } + + if (!node.HasChildNodes && node.InnerHtml.Length <= GOOGLE_TRANSLATE_LIMIT) + { + node.InnerHtml = await Translate(node.InnerHtml, language); + return; + } + else if (node.ChildNodes.Count > 0) + { + foreach (var child in node.ChildNodes) + await TranslateNode(child, language); + } + else + { + var lines = Wrap(node.InnerHtml, GOOGLE_TRANSLATE_LIMIT); + var sb = new StringBuilder(); + + foreach (var line in lines) + sb.Append(await Translate(line, language)); + + node.InnerHtml = sb.ToString(); + return; + } + } + + static string[] Wrap(string text, int eachLineLength) + { + text = text.Replace("\n\r", "\n"); + var splites = new[] { '\n', ' ', '.', ',', ';', '!', '?' }; + + var resultLines = new List(); + + var currentLine = new StringBuilder(); + + for (int i = 0; i < text.Length; i++) + { + if (currentLine.Length <= eachLineLength) + { + currentLine.Append(text[i]); + } + else // currentLineLength > eachLineLength + { + while (!splites.Contains(currentLine[currentLine.Length - 1])/* last char is not splitter*/) + { + currentLine.Remove(currentLine.Length - 1, 1); // remove last char + i--; + } + + i--; + resultLines.Add(currentLine.ToString()); + currentLine = new StringBuilder(); + } + } + + return resultLines.ToArray(); + } + + #endregion + + public static async Task Translate(string phraseInDefaultLanguage) + { + var retries = 3; + while (true) + { + try + { + return await Translate(phraseInDefaultLanguage, null); + } + catch + { + if (retries == 0) throw; + + await Task.Delay(10); // Wait and try again: + retries--; + } + } + } + + /// + /// Occurs when a translation is requested. + /// + public static readonly AsyncEvent TranslationRequested = + new AsyncEvent(); + + [EscapeGCop("It is ok for try methods to have out parameters.")] + static async Task> TryTranslateUsingTheEvent(string phraseInDefaultLanguage, ILanguage language) + { + if (TranslationRequested.IsHandled()) + { + var args = new TranslationRequestedEventArgs { PhraseInDefaultLanguage = phraseInDefaultLanguage, Language = language }; + + await TranslationRequested.Raise(args); + + if (args.Cancel) + return Tuple.Create(true, phraseInDefaultLanguage); + + if (args.TranslationProvider != null) + return Tuple.Create(true, args.TranslationProvider()); + } + + return Tuple.Create(false, default(string)); + } + + public static async Task Translate(string phraseInDefaultLanguage, ILanguage language) + { + if (language == null) language = await GetCurrentLanguage(); + + var byEvent = await TryTranslateUsingTheEvent(phraseInDefaultLanguage, language); + if (byEvent.Item1) return byEvent.Item2; + + if (phraseInDefaultLanguage.IsEmpty()) + return phraseInDefaultLanguage; + + if (language.Equals(await GetDefaultLanguage())) + { + return phraseInDefaultLanguage; + } + else + { + // First try: Exact match: + var translation = await GetLocalTranslation(phraseInDefaultLanguage, language); + + if (translation.HasValue()) + { + return translation; + } + + // special characters aren't translated: + if (phraseInDefaultLanguage.ToCharArray().None(c => char.IsLetter(c))) + return phraseInDefaultLanguage; + + // Next try: Remove special characters: + var leftDecorators = FindLeftDecorators(phraseInDefaultLanguage); + var rightDecorators = FindRightDecorators(phraseInDefaultLanguage); + + if (leftDecorators.HasValue()) + phraseInDefaultLanguage = phraseInDefaultLanguage.TrimStart(leftDecorators); + + if (rightDecorators.HasValue()) + phraseInDefaultLanguage = phraseInDefaultLanguage.TrimEnd(rightDecorators); + + translation = await GetLocalTranslation(phraseInDefaultLanguage, language); + + if (translation.IsEmpty()) + { + if (phraseInDefaultLanguage.Length <= GOOGLE_TRANSLATE_LIMIT && AttemptAutomaticTranslation) + { + translation = await GoogleTranslate(phraseInDefaultLanguage, language.IsoCode); + } + else + { + translation = phraseInDefaultLanguage; + } + + if (translation.HasValue()) + { + try + { + var arg = new TranslationDownloadedEventArgs(phraseInDefaultLanguage, language, translation); + await TranslationDownloaded.Raise(arg); + } + catch { /* No Logging needed*/ } + } + } + + return leftDecorators + translation.Or(phraseInDefaultLanguage) + rightDecorators; + } + } + + static async Task GetLocalTranslation(string phraseInDefaultLanguage, ILanguage language) + { + return (await Entity.Database.FirstOrDefault(p => + p.Phrase == phraseInDefaultLanguage && p.Language.Equals(language)) + ) + .Get(p => p.Translation); + } + + /// + /// Occurs when a word's translation is downloaded off the Internet. + /// + public static readonly AsyncEvent TranslationDownloaded = + new AsyncEvent(); + + static string FindLeftDecorators(string phraseInDefaultLanguage) + { + var result = new StringBuilder(); + + for (int i = 0; i < phraseInDefaultLanguage.Length && !char.IsLetter(phraseInDefaultLanguage[i]); i++) + result.Append(phraseInDefaultLanguage[i]); + + return result.ToString(); + } + + static string FindRightDecorators(string phraseInDefaultLanguage) + { + var result = new StringBuilder(); + + for (int i = phraseInDefaultLanguage.Length - 1; i >= 0 && !char.IsLetter(phraseInDefaultLanguage[i]); i--) + result.Insert(0, phraseInDefaultLanguage[i]); + + return result.ToString(); + } + + /// Check the configuration status of Google Translate + public static bool IsGoogleMisconfigured() => IsGoogleTranslateMisconfigured; + + /// Set the status of Google Translate as well configured + public static void ReconfigureGoogleTranslate() => IsGoogleTranslateMisconfigured = false; + + /// + /// Uses Google Translate service to translate a specified phrase to the specified language. + /// + public static async Task GoogleTranslate(string phrase, string languageIsoCodeTo, string languageIsoCodeFrom = "en") + { + if (IsGoogleTranslateMisconfigured) + return null; + + if (Config.Get("Enable.Google.Translate", defaultValue: false) == false) return null; + + var key = Config.Get("Google.Translate.Key"); + if (key.IsEmpty()) + throw new InvalidOperationException("There is no key specified for Google Translate."); + + // Replace line breaks by HTML tag, otherwise the API will remove lines + phrase = phrase.Replace(Environment.NewLine, LINE_BREAK_HTML); + + var request = "https://www.googleapis.com/language/translate/v2?key={0}&q={1}&source={2}&target={3}".FormatWith(key, HttpUtility.UrlEncode(phrase), languageIsoCodeFrom.ToLower(), languageIsoCodeTo.ToLower()); + if (request.Length > GOOGLE_TRANSLATE_LIMIT) + throw new ArgumentOutOfRangeException("Cannot use google translate with queries larger than {0} characters".FormatWith(GOOGLE_TRANSLATE_LIMIT)); + + try + { + var response = (await new WebClient().DownloadDataTaskAsync(request)).ToString(Encoding.UTF8); + + if (response.Contains(GOOGLE_TERMS_OF_SERVICE_ABUSE_MESSAGE, caseSensitive: false)) + { + IsGoogleTranslateMisconfigured = true; + return null; + } + else + { + var ser = new DataContractJsonSerializer(typeof(GoogleTranslateJsonResponseRootObject)); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(response)); + var rootObjectResponse = ser.ReadObject(stream) as GoogleTranslateJsonResponseRootObject; + var result = rootObjectResponse.data.translations[0].translatedText; + result = result.Replace(LINE_BREAK_UNICODE, Environment.NewLine); // Decode line breaks + return HttpUtility.HtmlDecode(result); + } + } + catch + { + // No Logging needed + return null; + } + } + + /// + /// Detect the language of a phrase. + /// The API can translate multiple piece of text in the same time, if needed create a function with parameter "params string phrase" and return a list of GoogleAutoDetectLanguage. + /// + public static async Task GoogleAutodetectLanguage(string phrase) + { + if (IsGoogleTranslateMisconfigured) + return null; + + if (!Config.Get("Enable.Google.Autodetect", defaultValue: false)) + return null; + + var key = Config.Get("Google.Translate.Key"); + if (key.IsEmpty()) + throw new InvalidOperationException("There is no key specified for Google Translate."); + + var request = "https://www.googleapis.com/language/translate/v2/detect?key={0}&q={1}".FormatWith(key, HttpUtility.UrlEncode(phrase)); + if (request.Length > GOOGLE_TRANSLATE_LIMIT) + throw new ArgumentOutOfRangeException("Cannot use google translate with queries larger than {0} characters".FormatWith(GOOGLE_TRANSLATE_LIMIT)); + + try + { + var response = Encoding.UTF8.GetString(await new WebClient().DownloadDataTaskAsync(request)); + + if (response.Contains(GOOGLE_TERMS_OF_SERVICE_ABUSE_MESSAGE, caseSensitive: false)) + { + IsGoogleTranslateMisconfigured = true; + return null; + } + else + { + var ser = new DataContractJsonSerializer(typeof(GoogleAutoDetectJsonResponseRootObject)); + var stream = new MemoryStream(Encoding.UTF8.GetBytes(response)); + var rootObjectResponse = ser.ReadObject(stream) as GoogleAutoDetectJsonResponseRootObject; + var dectection = rootObjectResponse.data.detections[0][0]; + return new GoogleAutodetectResponse(dectection.language, dectection.confidence); + } + } + catch + { + // No Logging needed + return null; + } + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.ImpersonationSession/ImpersonationSession.cs b/Services/Olive.Services.ImpersonationSession/ImpersonationSession.cs new file mode 100644 index 000000000..db16ad646 --- /dev/null +++ b/Services/Olive.Services.ImpersonationSession/ImpersonationSession.cs @@ -0,0 +1,125 @@ +using System; +using System.Security.Principal; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Olive.Entities; +using Olive.Services.Globalization; +using Olive.Web; + +namespace Olive.Services.ImpersonationSession +{ + /// + /// Defines an admin user who can impersonate other users. + /// + public interface IImpersonator : IUser, IIdentity, IPrincipal + { + /// + /// A unique single-use-only cookie-based token to specify the currently impersonated user session. + /// + string ImpersonationToken { get; set; } + + /// + /// Determines if this user can impersonate the specified other user. + /// + bool CanImpersonate(IUser user); + } + + /// + /// Provides the business logic for ImpersonationContext class. + /// + public class ImpersonationSession + { + /// + /// Provides the current user. + /// + public static Func CurrentUserProvider = GetCurrentUser; + + static HttpContext Context => Web.Context.Http; + + /// + /// Determines if the current user is impersonated. + /// + public static async Task IsImpersonated() => await GetImpersonator() != null; + + /// + /// Impersonates the specified user by the current admin user. + /// + /// If not specified, the current HTTP request's URL will be used. + public static async Task Impersonate(IUser user, bool redirectToHome = true, string originalUrl = null) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + var admin = CurrentUserProvider?.Invoke() as IImpersonator; + + if (admin == null) + throw new InvalidOperationException("The current user is not an IImpersonator."); + + if (!admin.CanImpersonate(user)) + throw new InvalidOperationException("The current user is not allowed to impersonate the specified user."); + + var token = Guid.NewGuid().ToString(); + + await Entity.Database.Update(admin, o => o.ImpersonationToken = token); + + SetImpersonationToken(token); + + SetOriginalUrl(originalUrl.Or(Context.Request.ToRawUrl())); + + user.LogOn(); + + if (redirectToHome && !Context.Request.IsAjaxCall()) + Context.Response.Redirect("~/"); + } + + /// + /// Ends the current impersonation session. + /// + public static async Task End() + { + if (!await IsImpersonated()) return; + + var admin = await GetImpersonator(); + + await Entity.Database.Update(admin, o => o.ImpersonationToken = null); + + admin.LogOn(); + + var returnUrl = await GetOriginalUrl(); + SetOriginalUrl(null); + SetImpersonationToken(null); + + if (!Context.Request.IsAjaxCall()) + Context.Response.Redirect(returnUrl); + } + + static IUser GetCurrentUser() + { + var result = Context.User as IIdentity; + if (result == null || !result.IsAuthenticated) return null; + + return result as IUser; + } + + /// + /// Gets the original user who impersonated the current user. + /// + public static async Task GetImpersonator() + { + var user = CurrentUserProvider?.Invoke(); + if (user == null || user.IsInRole("Guest") || user.IsInRole("Anonymous")) return null; + + var token = await ImpersonationToken; + if (token.IsEmpty()) return null; + + return await Entity.Database.FirstOrDefault(x => x.ImpersonationToken == token); + } + + static Task ImpersonationToken => CookieProperty.Get("Impersonation.Token"); + + static void SetImpersonationToken(string value) => CookieProperty.Set("Impersonation.Token", value); + + public static async Task GetOriginalUrl() => (await CookieProperty.Get("Impersonation.Original.Url")).Or("~/"); + + public static void SetOriginalUrl(string value) => CookieProperty.Set("Impersonation.Original.Url", value); + } +} \ No newline at end of file diff --git a/Services/Olive.Services.ImpersonationSession/Olive.Services.ImpersonationSession.csproj b/Services/Olive.Services.ImpersonationSession/Olive.Services.ImpersonationSession.csproj new file mode 100644 index 000000000..4e3ce4227 --- /dev/null +++ b/Services/Olive.Services.ImpersonationSession/Olive.Services.ImpersonationSession.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp2.0 + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.ImpersonationSession.xml + 1701;1702;1705;1591;1573 + + + + + + + + + + + + + + diff --git a/Services/Olive.Services.ImpersonationSession/Package.nuspec b/Services/Olive.Services.ImpersonationSession/Package.nuspec new file mode 100644 index 000000000..ad1a1ece7 --- /dev/null +++ b/Services/Olive.Services.ImpersonationSession/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.ImpersonationSession + 1.0.3 + Olive ImpersonationSession (service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Integration/IIntegrationQueueItem.cs b/Services/Olive.Services.Integration/IIntegrationQueueItem.cs new file mode 100644 index 000000000..5b1a8dce8 --- /dev/null +++ b/Services/Olive.Services.Integration/IIntegrationQueueItem.cs @@ -0,0 +1,47 @@ +using System; +using Olive.Entities; + +namespace Olive.Services.Integration +{ + /// + /// Stores one item requested for processing via an external service. + /// + public interface IIntegrationQueueItem : IEntity + { + /// + /// The name of the integration service used for processing this item. + /// + string IntegrationService { get; set; } + + /// + /// The body of the request being sent to the integration service. + /// + string Request { get; set; } + + /// + /// The response generated by the remote service. + /// + string Response { get; set; } + + /// + /// The error encountered in processing this item. + /// + string ErrorResponse { get; set; } + + /// + /// Specifies the date and time when this item was picked for processing. + /// If it's null, it means this item has not been picked yet. + /// + DateTime? DatePicked { get; set; } + + /// + /// Specifies the date and time when this item was first requested and added to the queue. + /// + DateTime RequestDate { get; set; } + + /// + /// Specifies the date when the response was retrieved from the remote service. + /// + DateTime? ResponseDate { get; set; } + } +} diff --git a/Services/Olive.Services.Integration/IServiceImplementor.cs b/Services/Olive.Services.Integration/IServiceImplementor.cs new file mode 100644 index 000000000..4a95c0944 --- /dev/null +++ b/Services/Olive.Services.Integration/IServiceImplementor.cs @@ -0,0 +1,23 @@ +namespace Olive.Services.Integration +{ + // /// + // /// All integration services should implement this interface. + // /// Each service should be registered At application start-up time by calling: + // /// IntegrationManager.Register[] + // /// + // public interface IIntegrationService : IIntegrationService + // { + // /// + // /// It will process the specified request, send it to the remote service, and return the response. + // /// + // string GetResponse(string request); + // } + + public interface IServiceImplementor + { + /// + /// It will process the specified request, send it to the remote service, and return the response. + /// + TResponse GetResponse(TRequest request); + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Integration/IntegrationExtensions.cs b/Services/Olive.Services.Integration/IntegrationExtensions.cs new file mode 100644 index 000000000..b0fd2c062 --- /dev/null +++ b/Services/Olive.Services.Integration/IntegrationExtensions.cs @@ -0,0 +1,11 @@ +namespace Olive +{ + using Services.Integration; + + public static class IntegrationExtensions + { + public static bool IsInProcess(this IIntegrationQueueItem item) => item.DatePicked != null; + + public static bool IsProcessed(this IIntegrationQueueItem item) => item.ResponseDate != null; + } +} diff --git a/Services/Olive.Services.Integration/IntegrationManager.cs b/Services/Olive.Services.Integration/IntegrationManager.cs new file mode 100644 index 000000000..6dd4704b4 --- /dev/null +++ b/Services/Olive.Services.Integration/IntegrationManager.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Olive.Entities; + +namespace Olive.Services.Integration +{ + /// + /// Provides services for integration services. + /// + public class IntegrationManager + { + internal const int WaitInterval = 50; + + static Type IntegrationQueueItemType; + + /// + /// Key = TRequest|TResponse + /// Value = TService + /// + internal static Dictionary IntegrationServices = new Dictionary(); + + static AsyncLock PickLock = new AsyncLock(); + + /// + /// Will find a Service Registered to process the item. + /// + public static async Task Process(IIntegrationQueueItem item) + { + if (item.ResponseDate.HasValue) return item; // Already processed: + + #region Pick the item + + Type serviceType; + + using (await PickLock.Lock()) + { + if (item.DatePicked.HasValue) + // Already picked, let the other thread finish its job: + return null; + + if (!item.IsNew) item = (IIntegrationQueueItem)item.Clone(); + + serviceType = IntegrationServices.GetOrDefault(item.IntegrationService); + if (serviceType == null) return null; + + item.DatePicked = LocalTime.Now; + await Entity.Database.Save(item); + item = (IIntegrationQueueItem)(await Entity.Database.Reload(item)).Clone(); + } + + // TOOD: This is not thread safe in multi-server (web farm) scenarios. + // To make it completely safe, we need a single StoredProc or SQL command that will do both at the same time, with a lock at the DB level. + #endregion + + var service = serviceType.CreateInstance(); + + var serviceInterface = serviceType.GetInterfaces().Single(x => x.Name.Contains("IServiceImplementor")); + + var typeOfRequest = serviceInterface.GetGenericArguments().First(); + + var request = JsonConvert.DeserializeObject(item.Request, typeOfRequest); + + var method = serviceType.GetMethod("GetResponse", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); + + try + { + var response = method.Invoke(service, new[] { request }); + item.Response = JsonConvert.SerializeObject(response); + } + catch (Exception ex) + { + item.ErrorResponse = ex.ToString(); + } + + item.ResponseDate = LocalTime.Now; + await Entity.Database.Save(item); + return item; + } + + public static async Task Process(string id) => await Process(await Entity.Database.Get(id)); + + /// + /// Uses the right Integration Service to process the outstanding items in all queues. + /// This should be called as an Automated Task in the application. + /// + public static async Task> ProcessOutstandingItems() + { + var result = new List(); + + foreach (var serviceType in IntegrationServices.Keys) + result.AddRange(await ProcessOutstandingItems(serviceType)); + + return result; + } + + /// + /// Uses the right Integration Service to process the next item in that queue. + /// + public static async Task> ProcessOutstandingItems(string serviceName) + { + var serviceType = IntegrationServices.GetOrDefault(serviceName); + if (serviceType == null) throw new Exception("Integration service not registered:" + serviceName); + + var result = new List(); + + foreach (var item in await Entity.Database.GetList(x => x.IntegrationService == serviceName && x.DatePicked == null)) + { + await Entity.Database.Update(item, x => x.DatePicked = LocalTime.Now); + // TODO: Use T-SQL to fetch an item and return it too. + + await Process(item); + result.Add(item); + } + + return result; + } + + static void DiscoverQueueType() + { + if (IntegrationQueueItemType != null) return; + + try + { + IntegrationQueueItemType = typeof(IIntegrationQueueItem).FindImplementerClasses().Single(x => !x.IsAbstract); + } + catch (Exception ex) + { + throw new Exception("Cannot find the correct implementation type for IIntegrationQueueItem.", ex); + } + } + + public static string GetServiceKey(Type serviceType) + { + var serviceBaseType = serviceType.WithAllParents() + .FirstOrDefault(x => x.IsGenericType && x.Name.StartsWith("IntegrationService")); + + if (serviceBaseType == null) + throw new Exception("TService should inherit from IntegrationService"); + + var types = serviceBaseType.GetGenericArguments(); + return GetServiceKey(types.First(), types.Last()); + } + + public static string GetServiceKey() where TService : IntegrationService => GetServiceKey(typeof(TService)); + + static string GetServiceKey(Type request, Type response) => request.Name + "|" + response.Name; + + /// + /// Registers an integration service. + /// + public static void Register() where TService : IServiceImplementor + { + DiscoverQueueType(); + + var key = GetServiceKey(typeof(TRequest), typeof(TResponse)); + + IntegrationServices.Add(key, typeof(TService)); + } + + /// + /// Inserts a queu item to call this service and waits until the item is processed. + /// Then it will return the response. + /// + public static async Task Request(TRequest request) + { + var item = await RequestAsync(request); + + return await AwaitResponse(item); + } + + /// + /// Inserts a request in the queue and immediately returns without waiting for a response. + /// It will return the token string for this request, that can be queried later on for a response (using Await Response). + /// + public static async Task RequestAsync(TRequest request) + { + DiscoverQueueType(); + + var item = IntegrationQueueItemType.CreateInstance(); + item.IntegrationService = GetServiceKey(request.GetType(), typeof(TResponse)); + // item.Request = new JavaScriptSerializer().Serialize(request); + item.Request = JsonConvert.SerializeObject(request); + + item.RequestDate = LocalTime.Now; + await Entity.Database.Save(item); + + return item.ID.ToString(); + } + + /// + /// It will wait until a response is provided by another thread to the integration queue item specified by its token. + /// + public static async Task AwaitResponse(string requestToken, int waitIntervals = WaitInterval) + { + while (true) + { + // var item = Database.Get(requestToken); + + var processedItem = await Process(requestToken); + + if (processedItem != null) + { + // var result = new JavaScriptSerializer().Deserialize(processedItem.Response, typeof(TResponse)); + var result = JsonConvert.DeserializeObject(processedItem.Response, typeof(TResponse)); + return (TResponse)result; + } + + await Task.Delay(waitIntervals); + } + } + + /// + /// Injects an asyncronous waiter which will inject the provided response for one potential future request. + /// It will check every 5 milliseconds to see if a request item is inserted in the queue, and in that case respond to it. + /// + public static async Task InjectResponse(TResponse injectedResponse) + { + var service = GetServiceKey(typeof(TRequest), typeof(TResponse)); + + // Get queue item: + while (true) + { + var item = await Entity.Database.Of().OrderBy(x => x.RequestDate) + .Where(x => x.IntegrationService == service && x.ResponseDate == null) + .FirstOrDefault(); + + if (item != null) + { + item = item.Clone() as IIntegrationQueueItem; + + // item.Response = new JavaScriptSerializer().Serialize(injectedResponse); + item.Response = JsonConvert.SerializeObject(injectedResponse); + item.ResponseDate = LocalTime.Now; + + await Entity.Database.Save(item); + + return; + } + + await Task.Delay(5); + } + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Integration/IntegrationService.cs b/Services/Olive.Services.Integration/IntegrationService.cs new file mode 100644 index 000000000..b856ba3a7 --- /dev/null +++ b/Services/Olive.Services.Integration/IntegrationService.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; + +namespace Olive.Services.Integration +{ + public abstract class IntegrationService { } + + public class IntegrationService : IntegrationService + { + /// + /// Inserts a queu item to call this service and waits until the item is processed. + /// Then it will return the response. + /// + public static Task Request(TRequest request) => IntegrationManager.Request(request); + + /// + /// Registers an integration service implementor. + /// + public static void RegisterImplementor() where TService : IServiceImplementor => + IntegrationManager.Register(); + + /// + /// Injects an asyncronous waiter which will inject the provided response for one potential future request. + /// It will check every 5 milliseconds to see if a request item is inserted in the queue, and in that case respond to it. + /// + public static Task InjectResponse(TResponse injectedResponse) => + IntegrationManager.InjectResponse(injectedResponse); + + /// + /// It will wait until a response is provided by another thread to the integration queue item specified by its token. + /// + public static Task AwaitResponse(string requestToken, int waitIntervals = IntegrationManager.WaitInterval) => + IntegrationManager.AwaitResponse(requestToken, waitIntervals); + + /// + /// Inserts a request in the queue and immediately returns without waiting for a response. + /// It will return the token string for this request, that can be queried later on for a response (using Await Response). + /// + public static Task RequestAsync(TRequest request) => + IntegrationManager.RequestAsync(request); + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Integration/IntegrationTestInjector.cs b/Services/Olive.Services.Integration/IntegrationTestInjector.cs new file mode 100644 index 000000000..e2ba79f74 --- /dev/null +++ b/Services/Olive.Services.Integration/IntegrationTestInjector.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Olive.Entities; + +namespace Olive.Services.Integration +{ + public class IntegrationTestInjector + { + const int INJECTOR_AGENT_WAIT_INTERVALS = 10;// ms + + public static async Task Inject(Type serviceType, string request, string response) + { + var serviceKey = IntegrationManager.GetServiceKey(serviceType); + + while (true) + { + var queueItem = await Entity.Database.FirstOrDefault(i => + i.IntegrationService == serviceKey && + i.ResponseDate == null && + (request.IsEmpty() || i.Request == request)); + + if (queueItem == null) + { + Thread.Sleep(INJECTOR_AGENT_WAIT_INTERVALS); + continue; + } + + await Entity.Database.Update(queueItem, i => + { + i.Response = response; + i.ResponseDate = LocalTime.Now; + }); + + break; + } + } + } +} diff --git a/Services/Olive.Services.Integration/Olive.Services.Integration.csproj b/Services/Olive.Services.Integration/Olive.Services.Integration.csproj new file mode 100644 index 000000000..59bfd6669 --- /dev/null +++ b/Services/Olive.Services.Integration/Olive.Services.Integration.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.0 + Olive.Services.Integration + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.Integration.xml + 1701;1702;1705;1591;1573 + + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Integration/Package.nuspec b/Services/Olive.Services.Integration/Package.nuspec new file mode 100644 index 000000000..c2c866317 --- /dev/null +++ b/Services/Olive.Services.Integration/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Integration + 1.0.3 + Olive Integration (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.IpFilter/IpFilter.cs b/Services/Olive.Services.IpFilter/IpFilter.cs new file mode 100644 index 000000000..0d47f9645 --- /dev/null +++ b/Services/Olive.Services.IpFilter/IpFilter.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Olive.Entities; +using Olive.Services.CSV; + +namespace Olive.Web +{ + public class IpFilter + { + static List BlockedCountryCodes = new List(); + static FileInfo CountryIpsFile => + Path.Combine(Blob.GetPhysicalFilesRoot(Blob.AccessMode.Secure).FullName, "IPBlock\\dbip-country.csv").AsFile(); + + static List> BlockedIpRanges; + + public static List SpecificallyAllowedIps = new List(); + public static List SpecificallyDisallowedIps = new List(); + + public static string BlockedAttemptResponse = "This website is not available in your region."; + public static Action OnBlockedAccessAttempt = EndWithMessage; + + #region Country and Region codes + + public static readonly string[] CountryCodes = "US,AU,CN,JP,TH,IN,MY,KR,SG,TW,HK,PH,VN,FR,DE,ES,IL,AT,NL,GB,SE,IT,AR,BE,FI,RU,GR,IE,BR,DK,PL,AE,UA,KZ,PT,SA,IR,NO,AS,CA,BA,EE,HU,RS,BG,ZA,VU,SY,KW,BH,LB,QA,OM,JO,CZ,PK,CH,IQ,TR,RO,BZ,MX,CL,CO,GE,MA,LV,AF,VG,EG,CY,HR,NG,LU,GT,UZ,KE,UY,MK,MT,PA,AZ,BI,ZM,ZW,PS,LT,SK,IS,SI,MD,AO,LI,SC,SN,JE,PY,BY,KG,RE,IM,BS,GG,GI,LY,AM,YE,CU,CR,BD,BO,GP,MQ,GY,ID,LK,XK,CX,DZ,EH,GN,KI,SO,AW,BB,BF,BJ,BL,BM,BN,BV,BW,CC,CD,CF,CG,CI,CK,CM,CV,CW,DJ,DM,DO,EC,ET,FJ,FK,FM,FO,GA,GD,GF,GH,GL,GM,GQ,GS,GU,GW,HT,JM,KM,KN,KY,LA,LC,LR,LS,MC,ME,MG,MH,ML,MM,MN,MO,MR,MS,MU,MV,MW,MZ,NA,NC,NE,NF,NI,NP,NR,NU,NZ,PG,PM,PN,PR,RW,SD,SH,SL,SM,SR,ST,SV,SZ,TC,TD,TG,TJ,TM,TN,TO,TT,TV,TZ,UG,UM,VA,VC,VE,VI,WF,WS,YT,AD,HN,IO,PE,AL,AI,KH,TK,AG,AQ,AX,BQ,BT,ER,HM,KP,MP,PF,PW,SB,SJ,TL,SS,TF,SX,MF".Split(','); + + public static readonly Dictionary CountriesInRegions = "US:NA,AU:OC,CN:AS,JP:AS,TH:AS,IN:AS,MY:AS,KR:AS,SG:AS,TW:AS,HK:AS,PH:AS,VN:AS,FR:EU,DE:EU,ES:EU,IL:AS,AT:EU,NL:EU,GB:EU,SE:EU,IT:EU,AR:SA,BE:EU,FI:EU,RU:EU,GR:EU,IE:EU,BR:SA,DK:EU,PL:EU,AE:AS,UA:EU,KZ:AS,PT:EU,SA:AS,IR:AS,NO:EU,AS:OC,CA:NA,BA:EU,EE:EU,HU:EU,RS:EU,BG:EU,ZA:AF,VU:OC,SY:AS,KW:AS,BH:AS,LB:AS,QA:AS,OM:AS,JO:AS,CZ:EU,PK:AS,CH:EU,IQ:AS,TR:EU,RO:EU,BZ:NA,MX:NA,CL:SA,CO:SA,GE:AS,MA:AF,LV:EU,AF:AS,VG:NA,EG:AF,CY:AS,HR:EU,NG:AF,LU:EU,GT:NA,UZ:AS,KE:AF,UY:SA,MK:EU,MT:EU,PA:NA,AZ:AS,BI:AF,ZM:AF,ZW:AF,PS:AS,LT:EU,SK:EU,IS:EU,SI:EU,MD:EU,AO:AF,LI:EU,SC:AF,SN:AF,JE:EU,PY:SA,BY:EU,KG:AS,RE:AF,IM:EU,BS:NA,GG:EU,GI:EU,LY:AF,AM:AS,YE:AS,CU:NA,CR:NA,BD:AS,BO:SA,GP:NA,MQ:NA,GY:SA,ID:AS,LK:AS,XK:EU,CX:AS,DZ:AF,EH:AF,GN:AF,KI:OC,SO:AF,AW:NA,BB:NA,BF:AF,BJ:AF,BL:NA,BM:NA,BN:AS,BV:AN,BW:AF,CC:AS,CD:AF,CF:AF,CG:AF,CI:AF,CK:OC,CM:AF,CV:AF,CW:SA,DJ:AF,DM:NA,DO:NA,EC:SA,ET:AF,FJ:OC,FK:SA,FM:OC,FO:EU,GA:AF,GD:NA,GF:SA,GH:AF,GL:NA,GM:AF,GQ:AF,GS:AN,GU:OC,GW:AF,HT:NA,JM:NA,KM:AF,KN:NA,KY:NA,LA:AS,LC:NA,LR:AF,LS:AF,MC:EU,ME:EU,MG:AF,MH:OC,ML:AF,MM:AS,MN:AS,MO:AS,MR:AF,MS:NA,MU:AF,MV:AS,MW:AF,MZ:AF,NA:AF,NC:OC,NE:AF,NF:OC,NI:NA,NP:AS,NR:OC,NU:OC,NZ:OC,PG:OC,PM:NA,PN:OC,PR:NA,RW:AF,SD:AF,SH:AF,SL:AF,SM:EU,SR:SA,ST:AF,SV:NA,SZ:AF,TC:NA,TD:AF,TG:AF,TJ:AS,TM:AS,TN:AF,TO:OC,TT:NA,TV:OC,TZ:AF,UG:AF,UM:OC,VA:EU,VC:NA,VE:SA,VI:NA,WF:OC,WS:OC,YT:AF,AD:EU,HN:NA,IO:AS,PE:SA,AL:EU,AI:NA,KH:AS,TK:OC,AG:NA,AQ:AN,AX:EU,BQ:SA,BT:AS,ER:AF,HM:AN,KP:AS,MP:OC,PF:OC,PW:OC,SB:OC,SJ:EU,TL:AS,SS:AF,TF:AN,SX:SA,MF:NA".Split(',').ToDictionary(x => x.Split(':').First(), x => x.Split(':').Last()); + + #endregion + + public static string[] GetCountryCodes(string regionCode) + { + if (!CountryIpsFile.Exists()) + throw new Exception($"Could not find the file '{CountryIpsFile.FullName}'.\r\nYou can download it from https://db-ip.com/db/download/country"); + + return CountriesInRegions.Where(i => i.Value == regionCode).Select(x => x.Key).ToArray(); + } + + /// + /// Sets the default policy for all IP addresses. + /// + public static void SetGlobalPolicy(Policy policy) + { + if (policy == Policy.Disallow) BlockedCountryCodes = CountryCodes.ToList(); + else if (policy == Policy.Allow) BlockedCountryCodes = new List(); + } + + /// + /// Sets the policy for specific IP addresses. These will override the global, region and country policies. + /// + public static void SetSpecificIpPolicy(Policy policy, params string[] ipAddresses) + { + foreach (var ip in ipAddresses) + { + var value = ToIpValue(ip); + + if (policy == Policy.Allow) + { + SpecificallyAllowedIps.Add(value); + SpecificallyDisallowedIps.Remove(value); + } + + if (policy == Policy.Disallow) + { + SpecificallyDisallowedIps.Add(value); + SpecificallyAllowedIps.Remove(value); + } + } + } + + /// + /// Sets the IP Filter policy. All Disallow policies should be set first, then all Allow policies. + /// + public static void SetCountryPolicy(Policy policy, params string[] countryCodes) + { + if (policy == Policy.Disallow) + BlockedCountryCodes = BlockedCountryCodes.Concat(countryCodes).Distinct().ToList(); + + if (policy == Policy.Allow) + BlockedCountryCodes = BlockedCountryCodes.Except(countryCodes).ToList(); + } + + /// + /// Sets the IP Filter policy. All Disallow policies should be set first, then all Allow policies. + /// + public static void SetRegionPolicy(Policy policy, params string[] regionCodes) => + regionCodes.Do(r => SetCountryPolicy(policy, GetCountryCodes(r))); + + /// + /// If the IP address of the current user is in a blocked list, then it will terminate the request with a response saying: + /// This website is not available in your region. + /// + public static async Task BlockIfNecessary(HttpContext httpContext) + { + if (await IsAllowed(httpContext.Connection.RemoteIpAddress.ToStringOrEmpty())) + OnBlockedAccessAttempt(httpContext); + } + + public static async Task IsAllowed(string ipAddress) + { + if (BlockedIpRanges == null) await LoadBlockedIpRanges(); + + var address = ToIpValue(ipAddress); + + if (SpecificallyDisallowedIps.Contains(address)) return false; + if (SpecificallyAllowedIps.Contains(address)) return true; + + return BlockedIpRanges.None(range => range.Contains(address)); + } + + static void EndWithMessage(HttpContext httpContext) => httpContext.Response.EndWith(BlockedAttemptResponse); + + static async Task LoadBlockedIpRanges() + { + var table = await CsvReader.Read(CountryIpsFile, isFirstRowHeaders: false); + + BlockedIpRanges = new List>(); + + foreach (var row in table.GetRows()) + { + if (!BlockedCountryCodes.Contains((string)row[2])) + continue; + + var from = ToIpValue((string)row[0]); + var to = ToIpValue((string)row[1]); + + BlockedIpRanges.Add(new Range(from, to)); + } + } + + static uint ToIpValue(string ipAddress) + { + try + { + var bytes = System.Net.IPAddress.Parse(ipAddress).GetAddressBytes(); + if (BitConverter.IsLittleEndian) + bytes = bytes.Reverse().ToArray(); + + return BitConverter.ToUInt32(bytes, 0); + } + catch (Exception ex) + { + throw new Exception($"Cannot convert the specified IP address string of '{ipAddress}' to unit IP address value.", ex); + } + } + + public enum Policy { Allow, Disallow } + + public class Region + { + public readonly static string Antarctica = "AN"; + public readonly static string Africa = "AF"; + public readonly static string Asia = "AS"; + public readonly static string Europe = "EU"; + public readonly static string NorthAmerica = "NA"; + public readonly static string Oceania = "OC"; + public readonly static string SouthAmerica = "SA"; + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.IpFilter/Olive.Services.IpFilter.csproj b/Services/Olive.Services.IpFilter/Olive.Services.IpFilter.csproj new file mode 100644 index 000000000..fc077a791 --- /dev/null +++ b/Services/Olive.Services.IpFilter/Olive.Services.IpFilter.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp2.0 + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.IpFilter.xml + 1701;1702;1705;1591;1573 + + + + + + + + + + + + diff --git a/Services/Olive.Services.IpFilter/Package.nuspec b/Services/Olive.Services.IpFilter/Package.nuspec new file mode 100644 index 000000000..d11057ae4 --- /dev/null +++ b/Services/Olive.Services.IpFilter/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.IpFilter + 1.0.3 + Olive Ip Filter (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.PDF/IHtml2PdfConverter.cs b/Services/Olive.Services.PDF/IHtml2PdfConverter.cs new file mode 100644 index 000000000..919cb38b1 --- /dev/null +++ b/Services/Olive.Services.PDF/IHtml2PdfConverter.cs @@ -0,0 +1,7 @@ +namespace Olive.Services +{ + public interface IHtml2PdfConverter + { + byte[] GetPdfFromUrlBytes(string url); + } +} diff --git a/Services/Olive.Services.PDF/Olive.Services.PDF.csproj b/Services/Olive.Services.PDF/Olive.Services.PDF.csproj new file mode 100644 index 000000000..f6918c34b --- /dev/null +++ b/Services/Olive.Services.PDF/Olive.Services.PDF.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.0 + Olive.Services.PDF + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.PDF.xml + 1701;1702;1705;1591;1573 + + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.PDF/Package.nuspec b/Services/Olive.Services.PDF/Package.nuspec new file mode 100644 index 000000000..28a88ea1b --- /dev/null +++ b/Services/Olive.Services.PDF/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.PDF + 1.0.3 + Olive PDF (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.PDF/PdfService.cs b/Services/Olive.Services.PDF/PdfService.cs new file mode 100644 index 000000000..bec2595f1 --- /dev/null +++ b/Services/Olive.Services.PDF/PdfService.cs @@ -0,0 +1,38 @@ +using System; + +namespace Olive.Services +{ + /// + /// Provides PDF services. + /// + public static class PdfService + { + const string HTML2PDF_CONVERTER_CONFIG_KEY = "Html2Pdf.Converter.Type"; + const string DEFAULT_HTML2PDF_TYPE = "Geeks.Html2PDF.Winnovative.Html2PdfConverter, Geeks.Html2PDF.Winnovative"; + + /// + /// Creates an instance of Html 2 PDF converter service. + /// + public static IHtml2PdfConverter CreateHtml2PdfConverter() + { + var typeName = Config.Get(HTML2PDF_CONVERTER_CONFIG_KEY, DEFAULT_HTML2PDF_TYPE); + + if (typeName.IsEmpty()) + throw new Exception($"Could not find the Html2Pdf converter type. The AppSetting of '{HTML2PDF_CONVERTER_CONFIG_KEY}' is not defined."); + + Type type; + + try + { + type = Type.GetType(typeName); + if (type == null) throw new Exception("Could not load the type: " + typeName); + } + catch (Exception ex) + { + throw new Exception("Unable to find the specified type: " + typeName, ex); + } + + return type.CreateInstance(); + } + } +} diff --git a/Services/Olive.Services.SMS/ISMSSender.cs b/Services/Olive.Services.SMS/ISMSSender.cs new file mode 100644 index 000000000..60de5f9be --- /dev/null +++ b/Services/Olive.Services.SMS/ISMSSender.cs @@ -0,0 +1,15 @@ +namespace Olive.Services.SMS +{ + /// + /// Represents a component that actually delivers SMS messages. + /// This should be implemented for any 3rd party SMS gateway. + /// + public interface ISMSSender + { + /// + /// Delivers the specified SMS message. + /// The implementation of this method should not handle exceptions. Any exceptions will be logged by the engine. + /// + void Deliver(ISmsQueueItem sms); + } +} diff --git a/Services/Olive.Services.SMS/ISmsQueueItem.cs b/Services/Olive.Services.SMS/ISmsQueueItem.cs new file mode 100644 index 000000000..0d8c7f684 --- /dev/null +++ b/Services/Olive.Services.SMS/ISmsQueueItem.cs @@ -0,0 +1,44 @@ +using System; +using Olive.Entities; + +namespace Olive.Services.SMS +{ + /// + /// Represents a sendable SMS item generated by the application. + /// + [LogEvents(false)] + [CacheObjects(false)] + public interface ISmsQueueItem : IEntity + { + /// + /// Gets or sets the date this SMS should be sent. + /// + DateTime Date { get; set; } + + /// + /// Gets or sets the date when this SMS was successfully sent. + /// + DateTime? DateSent { get; set; } + + /// + /// Gets or sets the Sender Name. + /// + string SenderName { get; set; } + + /// + /// Gets or sets the SMS text. + /// + string Text { get; set; } + + /// + /// Gets or sets the SMS recipient number. + /// + string To { get; set; } + + /// + /// Gets or sets the number of times sending this email has been tried. + /// + int Retries { get; set; } + + } +} diff --git a/Services/Olive.Services.SMS/Olive.Services.SMS.csproj b/Services/Olive.Services.SMS/Olive.Services.SMS.csproj new file mode 100644 index 000000000..dee204e27 --- /dev/null +++ b/Services/Olive.Services.SMS/Olive.Services.SMS.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.0 + Olive.Services.SMS + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.SMS.xml + 1701;1702;1705;1591;1573 + + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.SMS/Package.nuspec b/Services/Olive.Services.SMS/Package.nuspec new file mode 100644 index 000000000..174173e2c --- /dev/null +++ b/Services/Olive.Services.SMS/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.SMS + 1.0.3 + Olive SMS (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.SMS/SmsExtensions.cs b/Services/Olive.Services.SMS/SmsExtensions.cs new file mode 100644 index 000000000..4ad056240 --- /dev/null +++ b/Services/Olive.Services.SMS/SmsExtensions.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using Olive.Entities; + +namespace Olive.Services.SMS +{ + public static class SmsExtensions + { + /// + /// Records an unsuccessful attempt to send this SMS. + /// + public static async Task RecordRetry(this ISmsQueueItem sms) + { + if (sms.IsNew) throw new InvalidOperationException(); + + await Entity.Database.Update(sms, s => s.Retries++); + + // Also update this local instance: + sms.Retries++; + } + + /// + /// Updates the DateSent field of this item and then soft deletes it. + /// + public static Task MarkSent(this ISmsQueueItem sms) + { + return Entity.Database.EnlistOrCreateTransaction(() => Entity.Database.Update(sms, o => o.DateSent = LocalTime.Now)); + } + + /// + /// Sends the specified SMS item. + /// It will try several times to deliver the message. The number of retries can be specified in AppConfig of "SMS.Maximum.Retries". + /// If it is not declared in web.config, then 3 retires will be used. + /// Note: The actual SMS Sender component must be implemented as a public type that implements ISMSSender interface. + /// The assembly qualified name of that component, must be specified in AppConfig of "SMS.Sender.Type". + /// + public static Task Send(this ISmsQueueItem sms) => SmsService.Send(sms); + } +} \ No newline at end of file diff --git a/Services/Olive.Services.SMS/SmsSendingEventArgs.cs b/Services/Olive.Services.SMS/SmsSendingEventArgs.cs new file mode 100644 index 000000000..859b1710c --- /dev/null +++ b/Services/Olive.Services.SMS/SmsSendingEventArgs.cs @@ -0,0 +1,16 @@ +namespace Olive.Services.SMS +{ + using System; + + public class SmsSendingEventArgs + { + public ISmsQueueItem Item { get; } + + public Exception Error { get; internal set; } + + public SmsSendingEventArgs(ISmsQueueItem item) + { + Item = item; + } + } +} diff --git a/Services/Olive.Services.SMS/SmsService.cs b/Services/Olive.Services.SMS/SmsService.cs new file mode 100644 index 000000000..80ccf2438 --- /dev/null +++ b/Services/Olive.Services.SMS/SmsService.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; +using Olive.Entities; + +namespace Olive.Services.SMS +{ + public static class SmsService + { + /// + /// Occurs when an exception happens when sending an sms. Sender parameter will be the ISmsQueueItem instance that couldn't be sent. + /// + public static readonly AsyncEvent SendError = new AsyncEvent(); + + /// + /// Sends the specified SMS item. + /// It will try several times to deliver the message. The number of retries can be specified in AppConfig of "SMS.Maximum.Retries". + /// If it is not declared in web.config, then 3 retires will be used. + /// Note: The actual SMS Sender component must be implemented as a public type that implements ISMSSender interface. + /// The assembly qualified name of that component, must be specified in AppConfig of "SMS.Sender.Type". + /// + public static async Task Send(ISmsQueueItem smsItem) + { + if (smsItem.Retries > Config.Get("SMS.Maximum.Retries", 3)) + return false; + try + { + ISMSSender sender; + try + { + sender = Activator.CreateInstance(Type.GetType(Config.Get("SMS.Sender.Type"))) as ISMSSender; + + if (sender == null) + throw new Exception("Type is not defined, or it does not implement ISMSSender"); + } + catch (Exception ex) + { + Log.Error("Can not instantiate the sms sender from App config of " + Config.Get("SMS.Sender.Type"), ex); + return false; + } + + sender.Deliver(smsItem); + + await Entity.Database.Update(smsItem, o => o.DateSent = LocalTime.Now); + return true; + } + catch (Exception ex) + { + await SendError.Raise(new SmsSendingEventArgs(smsItem) { Error = ex }); + Log.Error("Can not send the SMS queue item.", ex); + await smsItem.RecordRetry(); + return false; + } + } + + public static async Task SendAll() + { + foreach (var sms in await Entity.Database.GetList(i => i.DateSent == null)) + await sms.Send(); + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.TaskAutomation/AutomatedTask.Logic.cs b/Services/Olive.Services.TaskAutomation/AutomatedTask.Logic.cs new file mode 100644 index 000000000..1fb785fae --- /dev/null +++ b/Services/Olive.Services.TaskAutomation/AutomatedTask.Logic.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Olive.Entities; + +namespace Olive.Services.TaskAutomation +{ + partial class AutomatedTask + { + Action Action; + + public TimeSpan Intervals { get; set; } + + Task RunnerTask; + + CancellationTokenSource CancellationTokenSource; + + static AsyncLock ExecutionPersistenceAsyncLock = new AsyncLock(); + + /// + /// Creates a new AutomatedTask instance. + /// + public AutomatedTask(Action action) + : this() + { + Action = action ?? throw new ArgumentNullException(nameof(action)); + + CancellationTokenSource = new CancellationTokenSource(); + + Status = AutomatedTaskStatus.AwaitingFirstRun; + } + + public AutomatedTaskStatus Status { get; private set; } + + /// + /// Starts this automated task. + /// + public void Start() => RunnerTask = Process(CancellationTokenSource.Token); + + /// + /// Restarts this task. + /// + public void Restart() + { + try + { + CancellationTokenSource.Cancel(); + } + catch + { + // No Logging needed + } + + CancellationTokenSource = new CancellationTokenSource(); + RunnerTask = Process(CancellationTokenSource.Token); + } + + #region Persistent Execution Log + + async Task GetInitialNextTry() + { + var result = LocalTime.Now; + + using (await ExecutionPersistenceAsyncLock.Lock()) + { + if (ShouldPersistExecution()) + { + var file = GetExecutionStatusPath(); + + if (file.Exists()) + { + try + { + var content = await file.ReadAllText(); + if (content.HasValue()) + { + var taskData = XElement.Parse(content).Elements().FirstOrDefault(e => e.GetValue("@Name") == Name); + + if (taskData != null) + { + result = DateTime.FromOADate(taskData.GetValue("@LastRun").To()).ToLocalTime(); + result = result.Add(Intervals); + } + } + } + catch + { + // No Logging needed + // The file is perhaps corrupted. + } + } + } + } + + return result; + } + + static FileInfo GetExecutionStatusPath() + { + var result = Config.Get("Automated.Tasks:Status.Path"); + + if (result.HasValue()) + { + if (!result.StartsWith("\\") && result[1] != ':') + { + // Relative pth: + result = AppDomain.CurrentDomain.GetPath(result); + } + + result.AsFile().Directory.EnsureExists(); + return result.AsFile(); + } + + return Blob.GetPhysicalFilesRoot(Blob.AccessMode.Secure).EnsureExists().GetFile("AutomatedTasks.Status.xml"); + } + + static bool ShouldPersistExecution() => Config.Get("Automated.Tasks:Persist.Execution", defaultValue: false); + + public static async Task DeleteExecutionStatusHistory() + { + using (await ExecutionPersistenceAsyncLock.Lock()) + await GetExecutionStatusPath().Delete(harshly: true); + } + + async Task PersistExecution() + { + if (!ShouldPersistExecution()) return; + + var path = GetExecutionStatusPath(); + + using (await ExecutionPersistenceAsyncLock.Lock()) + { + var data = new XElement("Tasks"); + + if (path.Exists()) + { + await new Func(async () => + { + try + { + var content = await path.ReadAllText(); + if (content.HasValue()) data = XElement.Parse(content); + } + catch (FileNotFoundException) + { + // Somehow another thread has deleted it. + } + }).Invoke(10, TimeSpan.FromMilliseconds(300)); + } + + var element = data.Elements().FirstOrDefault(e => e.GetValue("@Name") == Name); + + if (element == null) + data.Add(new XElement("Task", new XAttribute("Name", Name), new XAttribute("LastRun", LocalTime.Now.ToUniversalTime().ToOADate().ToString()))); + else + element.Attribute("LastRun").Value = LocalTime.Now.ToUniversalTime().ToOADate().ToString(); + + try + { + await path.WriteAllText(data.ToString()); + } + catch + { + // No Logging needed + // Error? + } + } + } + + #endregion + + [System.Diagnostics.DebuggerStepThrough] + async Task Process(CancellationToken cancellationToken) + { + NextTry = await GetInitialNextTry(); + + // Startup delay: + if (Delay > TimeSpan.Zero) + { + NextTry = NextTry.Value.Add(Delay); + await Task.Delay(Delay, cancellationToken); + } + + if (cancellationToken.IsCancellationRequested) return; + + // Should we still wait? + var stillWait = NextTry.Value - LocalTime.Now; + if (stillWait.TotalMilliseconds > int.MaxValue) await Task.Delay(int.MaxValue, cancellationToken); + else if (stillWait > TimeSpan.Zero) await Task.Delay(stillWait, cancellationToken); + + for (; /* ever */ ; ) + { + if (cancellationToken.IsCancellationRequested) + break; + + await Execute(); + + if (cancellationToken.IsCancellationRequested) + break; + + // Now wait for the next itnerval: + await WaitEnough(cancellationToken); + } + } + + [System.Diagnostics.DebuggerStepThrough] + async Task WaitEnough(CancellationToken cancellationToken) => await Task.Delay(Intervals, cancellationToken); + + public async Task Execute() + { + NextTry = null; + + if (AsyncGroup != null) + { + Status = AutomatedTaskStatus.WaitingForLock; + using (await AsyncGroup.Lock()) await DoExecute(); + } + else + { + await DoExecute(); + } + + NextTry = LocalTime.Now.Add(Intervals); + } + + async Task DoExecute() + { + CurrentStartTime = LastRunStart = LocalTime.Now; + + try + { + Status = AutomatedTaskStatus.Running; + Action?.Invoke(this); + + if (RecordSuccess) + { + try { await ApplicationEventManager.RecordScheduledTask(Name, CurrentStartTime.Value); } + catch { /*Problem in logging*/ } + } + + Status = AutomatedTaskStatus.CompletedAwaitingNextRun; + } + catch (Exception ex) + { + // if (!WebTestManager.IsTddExecutionMode()) + { + if (RecordFailure) + { + try { await ApplicationEventManager.RecordScheduledTask(Name, CurrentStartTime.Value, ex); } + catch { /*Problem in logging*/ } + } + } + + Status = AutomatedTaskStatus.FailedAwaitingNextRun; + } + finally + { + CurrentStartTime = null; + LastRunEnd = LocalTime.Now; + await PersistExecution(); + } + } + + public static IEnumerable GetAllTasks() + { + var classes = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType("TaskManager")).ExceptNull().Distinct().ToList(); + + if (classes.None()) + throw new Exception("There is no class named TaskManager in the current application domain."); + + if (classes.HasMany()) + throw new Exception("There are multiple classes named TaskManager in the current application domain."); + + var tasks = classes.First().GetProperty("Tasks", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public).GetValue(null) + as IEnumerable; + + if (tasks == null) + throw new Exception("Class TaskManager doesn't have a property named Tasks of type IEnumerable."); + + return tasks; + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.TaskAutomation/AutomatedTask.cs b/Services/Olive.Services.TaskAutomation/AutomatedTask.cs new file mode 100644 index 000000000..98af5dcf9 --- /dev/null +++ b/Services/Olive.Services.TaskAutomation/AutomatedTask.cs @@ -0,0 +1,155 @@ +using System; +using System.Threading.Tasks; +using Olive.Entities; + +namespace Olive.Services.TaskAutomation +{ + /// + /// Represents an instance of Automated task entity type. + /// + [TransientEntity] + public partial class AutomatedTask : GuidEntity + { + const int DEFAULT_DELAY_IN_SECONDS = 30, NAME_MAX_LENGTH = 200; + + /* -------------------------- Constructor -----------------------*/ + + /// + /// Initializes a new instance of the AutomatedTask class. + /// + public AutomatedTask() + { + RecordFailure = true; + + Delay = TimeSpan.FromSeconds(DEFAULT_DELAY_IN_SECONDS); + } + + /* -------------------------- Properties -------------------------*/ + + #region Current execution duration Property + + /// + /// Gets the CurrentExecutionDuration property. + /// + public string CurrentExecutionDuration => CurrentStartTime == null ? "" : "Since " + CurrentStartTime.Value.ToTimeDifferenceString(); + + #endregion + + #region Current start time Property + + /// + /// Gets or sets the value of CurrentStartTime on this Automated task instance. + /// + public DateTime? CurrentStartTime { get; set; } + + #endregion + + #region Last run duration Property + + /// + /// Gets the LastRunDuration property. + /// + public TimeSpan? LastRunDuration + { + get + { + if (LastRunStart == null || LastRunEnd == null) return null; + else return LastRunEnd.Value.Subtract(LastRunStart.Value); + } + } + + #endregion + + #region Last run end Property + + /// + /// Gets or sets the value of LastRunEnd on this Automated task instance. + /// + public DateTime? LastRunEnd { get; set; } + + #endregion + + #region Last run start Property + + /// + /// Gets or sets the value of LastRunStart on this Automated task instance. + /// + public DateTime? LastRunStart { get; set; } + + #endregion + + #region Name Property + + /// + /// Gets or sets the value of Name on this Automated task instance. + /// + public string Name { get; set; } + + #endregion + + #region Next try Property + + /// + /// Gets or sets the value of NextTry on this Automated task instance. + /// + public DateTime? NextTry { get; set; } + + #endregion + + #region Record failure Property + + /// + /// Gets or sets a value indicating whether this Automated task instance Record failure. + /// + public bool RecordFailure { get; set; } + + #endregion + + #region Record success Property + + /// + /// Gets or sets a value indicating whether this Automated task instance Record success. + /// + public bool RecordSuccess { get; set; } + + #endregion + + #region Delay + /// + /// Gets or sets the Delay of this AutomatedTask. + /// + public TimeSpan Delay { get; set; } + #endregion + + #region SyncGroup + /// + /// Gets or sets the SyncGroup of this AutomatedTask. + /// + public AsyncLock AsyncGroup { private get; set; } + #endregion + + /* -------------------------- Methods ----------------------------*/ + + /// + /// Returns a textual representation of this Automated task. + /// + /// A string value that represents this Automated task instance. + public override string ToString() => Name; + + /// + /// Validates the data for the properties of this Automated task. + /// It throws a ValidationException if an error is detected. + /// + protected override Task ValidateProperties() + { + // Validate Name property: + if (Name.IsEmpty()) + throw new ValidationException("Name cannot be empty."); + + if (Name.Length > NAME_MAX_LENGTH) + throw new ValidationException("Name field allows a maximum of 200 characters. You have provided {0} characters which exceeds this limit.", Name.Length); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.TaskAutomation/AutomatedTaskStatus.Logic.cs b/Services/Olive.Services.TaskAutomation/AutomatedTaskStatus.Logic.cs new file mode 100644 index 000000000..a1574e110 --- /dev/null +++ b/Services/Olive.Services.TaskAutomation/AutomatedTaskStatus.Logic.cs @@ -0,0 +1,16 @@ +namespace Olive.Services.TaskAutomation +{ + partial class AutomatedTaskStatus + { + /// + /// Creates a new AutomatedTaskStatus instance. + /// + public AutomatedTaskStatus(string name) => Name = name; + + internal static AutomatedTaskStatus AwaitingFirstRun = new AutomatedTaskStatus("Awaiting First Run"); + internal static AutomatedTaskStatus Running = new AutomatedTaskStatus("Running"); + internal static AutomatedTaskStatus WaitingForLock = new AutomatedTaskStatus("Waiting For Lock"); + internal static AutomatedTaskStatus CompletedAwaitingNextRun = new AutomatedTaskStatus("Completed, Awaiting Next Run"); + internal static AutomatedTaskStatus FailedAwaitingNextRun = new AutomatedTaskStatus("Failed, Awaiting Next Run"); + } +} \ No newline at end of file diff --git a/Services/Olive.Services.TaskAutomation/AutomatedTaskStatus.cs b/Services/Olive.Services.TaskAutomation/AutomatedTaskStatus.cs new file mode 100644 index 000000000..c97ebda1b --- /dev/null +++ b/Services/Olive.Services.TaskAutomation/AutomatedTaskStatus.cs @@ -0,0 +1,30 @@ +using Olive.Entities; + +namespace Olive.Services.TaskAutomation +{ + /// + /// Represents an instance of Automated Task Status entity type. + /// + [TransientEntity] + public partial class AutomatedTaskStatus //: Entity + { + /* -------------------------- Properties -------------------------*/ + + #region Name Property + + /// + /// Gets or sets the value of Name on this Automated Task Status instance. + /// + public string Name { get; set; } + + #endregion + + /* -------------------------- Methods ----------------------------*/ + + /// + /// Returns a textual representation of this Automated Task Status. + /// + /// A string value that represents this Automated Task Status instance. + public override string ToString() => Name.Or(string.Empty); + } +} \ No newline at end of file diff --git a/Services/Olive.Services.TaskAutomation/Olive.Services.TaskAutomation.csproj b/Services/Olive.Services.TaskAutomation/Olive.Services.TaskAutomation.csproj new file mode 100644 index 000000000..ffab01f85 --- /dev/null +++ b/Services/Olive.Services.TaskAutomation/Olive.Services.TaskAutomation.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp2.0 + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.TaskAutomation.xml + 1701;1702;1705;1591;1573 + + + + + + + + diff --git a/Services/Olive.Services.TaskAutomation/Package.nuspec b/Services/Olive.Services.TaskAutomation/Package.nuspec new file mode 100644 index 000000000..7fdbcfcb2 --- /dev/null +++ b/Services/Olive.Services.TaskAutomation/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.TaskAutomation + 1.0.3 + Olive Task Automation (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Testing/DatabaseChangeWatcher.cs b/Services/Olive.Services.Testing/DatabaseChangeWatcher.cs new file mode 100644 index 000000000..74b24e21f --- /dev/null +++ b/Services/Olive.Services.Testing/DatabaseChangeWatcher.cs @@ -0,0 +1,45 @@ +using Olive.Entities.Data; +using Olive.Web; +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml.Linq; + +namespace Olive.Services.Testing +{ + class DatabaseChangeWatcher + { + static List Changes = new List(); + + static DatabaseChangeWatcher() + { + DatabaseStateChangeCommand.ExecutedChangeCommand += DatabaseStateChangeCommand_ExecutedChangeCommand; + } + + static void DatabaseStateChangeCommand_ExecutedChangeCommand(DatabaseStateChangeCommand change) + { + var node = new XElement("Change"); + if (change.CommandType != System.Data.CommandType.Text) + node.Add(new XAttribute("Type", change.CommandType.ToString())); + + node.Add(new XAttribute("Command", change.CommandText)); + + foreach (var p in change.Params) + node.Add(new XElement("Param", + new XAttribute("Name", p.ParameterName), + new XAttribute("Value", p.Value), + new XAttribute("Type", p.DbType))); + + Changes.Add(node); + } + + internal static void Restart() => Changes.Clear(); + + internal static void DispatchChanges() + { + var response = new XElement("Changes", Changes).ToString(); + Changes.Clear(); + Context.Response.EndWith(response, "text/xml"); + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Testing/Example.cs b/Services/Olive.Services.Testing/Example.cs new file mode 100644 index 000000000..297fd510f --- /dev/null +++ b/Services/Olive.Services.Testing/Example.cs @@ -0,0 +1,127 @@ +using System.Threading.Tasks; +using Olive.Services.Integration; + +namespace Olive.Services.Testing +{ + // Note: This is a service implementation example. + // MyService can be replaced by a proper name depending on your project's requirements. + + + /// + /// This simple class will not have any implementation body. It's used to invoke the service. + /// + class MyService : IntegrationService { } + + /// + /// A basic C# class to contain all parameters which are logically part of your application. + /// Do not add properties which are used only to establish the pipelines such as constant security keys, access tokens, etc. + /// This class should be minimal and only represent what has a meaning in yoru application's domain for the purpose of integration. + /// + class MyServiceRequest + { + public string Param1, Param2; + } + + /// + /// Same as MyServiceRequest, it should only have data fields that are relevant to the domain of your application. + /// Keep it pure and simple. It can have a full hierarchy such as List classes, nested objects, etc. + /// + class MyServiceResponse + { + public string Ourcome1, Ourcome2; + } + + namespace My.VSProject + { + /// + /// The actual service implementation. Keep it in a separate DLL (project). + /// Your Model project should not reference this DLL, but your website project should. + /// This dll should reference your Model dll. + /// It should be self sufficient class that can work simply from ServiceRequest and web.config data. + /// + class MyServiceImplementor : IServiceImplementor + { + /// + /// In this method, write the actual implementation. Make calls to the external web service, Http, etc. + /// You can use any set of implementation classes for this, including those typically generated by WSDL utilities. + /// You can make several calls to the external service, establish security, read from web.config, etc. + /// At the end of the process, the final relevant information should be written into an instance of MyServiceResponse and returned. + /// + public MyServiceResponse GetResponse(MyServiceRequest request) + { + // Dirty work goes here. + // ... + return new MyServiceResponse { Ourcome1 = "sample", Ourcome2 = "sample" }; + } + } + } + + class Website + { + public async Task Global_ASAX() + { + // The actual service implementation will be registerd here once for live use (non-test mode). + // Throughout the development period when the application is in TDD mode, this service is essentially not registered, so + // there is no dependency on the external service. + + // Perhaps only once when the ServiceImplementation is created during development cycle, + // you can remove the condition to test it once or twice. + if (WebTestManager.IsTddExecutionMode() == false) + { + MyService.RegisterImplementor(); + + // Optional: Add an Automated Task to call the following. + // It will frequently process all outstanding items in the queue. + // Mainly relevant for multi-try situations in live operations. Avoid unless necessary. + await IntegrationManager.ProcessOutstandingItems(); + } + else + { + // Note: In case you need to provide a Mock implementation for the service, instead of manually injecting the responses + // through sanity, you can create a class that fakes responses to requests and inject it here: + // e.g: MyService.Register(); + } + } + } + + class Model + { + internal void SomeBusinessLogicMethod() + { + // In the consumer classes (mostly in your Model project), all requests to the integration services will be via the following method. + // Note: The following method will serialize the request and insert it in the IntegrationQueueItem table. + // It will then wait for the response to be provided. + + // In TDD mode, the ServiceImplementation will not have been registered, and therefore this call will wait indefinitely for a response. + // In practice, whilst this method is waiting for the response, the idea is that you manually go to the Database and inject the response + // in the Queue. As soon as the response is provided, the waiting method will then pick the result and proceed. + + // In Sanity there is a command called "$check integartion queue" used to manually inject the response. + + var result = MyService.Request(new MyServiceRequest { Param1 = "1", Param2 = "X" }); + } + } + + class UnitTest + { + public void UnitTest_For_Business_Logic_Layer() + { + // When the service consumer unit (business logic layer) is invoked, it will in turn insert its IntegrationQueueItem and wait for its response. + // So instead of directly calling the method in your Unit Test, you should invoke that in a Task. + var app = new Model(); + var task = new System.Threading.Tasks.Task(app.SomeBusinessLogicMethod); + task.Start(); + + // At this stage the logic unit is waiting for the response. So here we just inject a response, so the unit will continue then. + var assumedResponse = new MyServiceResponse { Ourcome1 = "Something", Ourcome2 = "anything" }; + MyService.InjectResponse(assumedResponse); + + // At this stage, the MyService.Request() called inside the logic unit, will come out of freezing and return the injected + // response to the consumer unit. + + task.Wait(); + + // Note: This way, the dependency to the external service is bypassed by manually injecting the response. + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Testing/Olive.Services.Testing.csproj b/Services/Olive.Services.Testing/Olive.Services.Testing.csproj new file mode 100644 index 000000000..14b4aa9f3 --- /dev/null +++ b/Services/Olive.Services.Testing/Olive.Services.Testing.csproj @@ -0,0 +1,28 @@ + + + + netcoreapp2.0 + + + + ..\..\@Assemblies\ + ..\..\@Assemblies\netcoreapp2.0\Olive.Services.Testing.xml + 1701;1702;1705;1591;1573 + + + + + + + + + + + + + + + + + + diff --git a/Services/Olive.Services.Testing/Package.nuspec b/Services/Olive.Services.Testing/Package.nuspec new file mode 100644 index 000000000..780fdd33e --- /dev/null +++ b/Services/Olive.Services.Testing/Package.nuspec @@ -0,0 +1,18 @@ + + + + Olive.Testing + 1.0.3 + Olive Testing (Service) + Geeks Ltd + https://github.com/Geeksltd/Olive + http://licensing.msharp.co.uk/Images/OliveComponent.png + Copyright ©2017 Geeks Ltd - All rights reserved. + Olive Framework + + + + + + + \ No newline at end of file diff --git a/Services/Olive.Services.Testing/Snapshot.cs b/Services/Olive.Services.Testing/Snapshot.cs new file mode 100644 index 000000000..07d130bbd --- /dev/null +++ b/Services/Olive.Services.Testing/Snapshot.cs @@ -0,0 +1,454 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; +using Olive.Entities; +using Olive.Web; + +namespace Olive.Services.Testing +{ + class Snapshot + { + const string TEMP_DATABASES_LOCATION_KEY = "Temp.Databases.Location"; + const string URL_FILE_NAME = "url.txt"; + const string DATE_FILE_NAME = "date.txt"; + static string DatabaseName = GetDatabaseName(); + string SnapshotName; + bool IsInShareSnapshotMode; + static Mutex SnapshotRestoreLock; + + DirectoryInfo SnapshotsDirectory; + + public Snapshot(string name, bool isSharedSNapshotMode) + { + IsInShareSnapshotMode = isSharedSNapshotMode; + SnapshotName = CreateSnapshotName(name); + SnapshotsDirectory = GetSnapshotsRoot(IsInShareSnapshotMode).GetSubDirectory(SnapshotName); + } + + public async Task Create(HttpContext context) + { + if (IsSnapshotsDisabled) return; + + SetupDirecory(); + await SnapshotDatabase(); + await CreateSnapshotCookies(context); + await CopyUploadedFiles(CopyProcess.Backup); + await SaveDate(); + await SaveUrl(context); + } + + static bool IsSnapshotsDisabled => Config.Get("WebTestManager.DisableSnapshots"); + + public bool Exists() + { + if (IsSnapshotsDisabled) return false; + + return SnapshotsDirectory.Exists(); + } + + public async Task Restore(HttpContext context) + { + if (!Exists()) + throw new DirectoryNotFoundException("Cannot find snapshot " + SnapshotName); + + var restoreDatabase = LocalTime.Now; + await RestoreDatabase(); + Debug.WriteLine("Total time for restoring including mutex: " + LocalTime.Now.Subtract(restoreDatabase).Milliseconds); + + var restoreCookies = LocalTime.Now; + await RestoreCookies(context); + Debug.WriteLine("Total time for restoring cookies: " + LocalTime.Now.Subtract(restoreCookies).Milliseconds); + + var restoreFiles = LocalTime.Now; + await CopyUploadedFiles(CopyProcess.Restore); + Debug.WriteLine("Total time for restoring files: " + LocalTime.Now.Subtract(restoreFiles).Milliseconds); + + var restoreDate = LocalTime.Now; + await RestoreDate(); + Debug.WriteLine("Total time for restoring date: " + LocalTime.Now.Subtract(restoreDate).Milliseconds); + + var restoreUrl = LocalTime.Now; + await RestoreUrl(context); + Debug.WriteLine("Total time for restoring url: " + LocalTime.Now.Subtract(restoreUrl).Milliseconds); + } + + async Task CopyUploadedFiles(CopyProcess process) + { + var copyTasks = new List(); + + foreach (var key in new[] { "UploadFolder", "UploadFolder.Secure" }) + { + var source = Config.Get(key); + if (source.IsEmpty()) + { + Debug.WriteLine("Destination directory not configured in App.Config for key: " + key); + continue; + } + + string folder = Config.Get(key); + if (folder.ToCharArray()[0] == '/') folder = folder.Substring(1); + + if (process == CopyProcess.Restore) + { + source = Path.Combine(SnapshotsDirectory.ToString(), folder); + if (!Directory.Exists(source)) continue; + copyTasks.Add(new DirectoryInfo(source).CopyTo(AppDomain.CurrentDomain.GetPath(Config.Get(key)), overwrite: true)); + } + else if (process == CopyProcess.Backup) + { + source = AppDomain.CurrentDomain.GetPath(source); + if (!Directory.Exists(source)) continue; + copyTasks.Add(new DirectoryInfo(source).CopyTo(Path.Combine(SnapshotsDirectory.ToString(), folder), overwrite: true)); + } + } + + await Task.WhenAll(copyTasks); + } + + async Task SaveDate() + { + if (LocalTime.IsRedefined) + { + await File.WriteAllTextAsync(SnapshotsDirectory.GetFile(DATE_FILE_NAME).FullName, LocalTime.Now.ToString()); + } + } + + async Task RestoreDate() + { + var dateFile = SnapshotsDirectory.GetFile(DATE_FILE_NAME); + if (dateFile.Exists()) + { + var dateTime = Convert.ToDateTime(await dateFile.ReadAllText()); + LocalTime.RedefineNow(() => dateTime); + } + } + + public static void RemoveSnapshots() + { + var sharedSnapshots = GetSnapshotsRoot(isSharedSnapshotMode: true); + if (sharedSnapshots.Exists) + { + DeleteDirectory(sharedSnapshots); + sharedSnapshots.EnsureExists(); + } + + var normalSnapshots = GetSnapshotsRoot(isSharedSnapshotMode: false); + if (normalSnapshots.Exists) + { + DeleteDirectory(normalSnapshots); + normalSnapshots.EnsureExists(); + } + + Context.Response.Redirect("~/"); + } + + public static void RemoveSnapshot(string name) + { + var snapshotName = CreateSnapshotName(name); + + var normalSnapshotDirectory = Path.Combine(GetSnapshotsRoot(isSharedSnapshotMode: false).FullName, snapshotName).AsDirectory(); + if (normalSnapshotDirectory.Exists) + DeleteDirectory(normalSnapshotDirectory); + + var shardSnapshotDirectory = Path.Combine(GetSnapshotsRoot(isSharedSnapshotMode: true).FullName, snapshotName).AsDirectory(); + if (shardSnapshotDirectory.Exists) + DeleteDirectory(shardSnapshotDirectory); + + Context.Response.Redirect("~/"); + } + + public static void DeleteDirectory(DirectoryInfo targetDirectory) + { + var files = targetDirectory.GetFiles(); + var dirs = targetDirectory.GetDirectories(); + + foreach (var file in files) + { + file.Attributes = FileAttributes.Normal; + file.Delete(); + } + + foreach (var dir in dirs) + DeleteDirectory(dir); + + targetDirectory.Delete(); + } + + #region URL + + async Task SaveUrl(HttpContext context) + { + var uri = new Uri(Context.Request.ToAbsoluteUri()); + var url = uri.PathAndQuery; + + url = url.Substring(0, url.IndexOf("Web.Test.Command", StringComparison.OrdinalIgnoreCase) - 1); + if (url.HasValue()) + { + await File.WriteAllTextAsync(SnapshotsDirectory.GetFile(URL_FILE_NAME).FullName, url); + context.Response.Redirect(url); + } + } + + async Task RestoreUrl(HttpContext context) + { + var urlFile = SnapshotsDirectory.GetFile(URL_FILE_NAME); + if (urlFile.Exists()) + context.Response.Redirect(context.Request.GetWebsiteRoot() + (await urlFile.ReadAllText()).TrimStart("/")); + } + + #endregion + + #region Cookie + async Task CreateSnapshotCookies(HttpContext context) + { + var json = JsonConvert.SerializeObject(context.Request.GetCookies().ToArray()); + + await GetCookiesFile().WriteAllText(json); + } + + async Task RestoreCookies(HttpContext context) + { + var cookiesFile = GetCookiesFile(); + + if (!cookiesFile.Exists()) return; + + var cookies = JsonConvert.DeserializeObject[]>(await cookiesFile.ReadAllText()); + + foreach (var cookie in cookies) + context.Response.Cookies.Append(cookie.Key, cookie.Value); + } + + FileInfo GetCookiesFile() => SnapshotsDirectory.GetFile("cookies.json"); + + #endregion + + #region DB + async Task SnapshotDatabase() + { + FileInfo[] files; + + SqlConnection.ClearAllPools(); + + using (var connection = new SqlConnection(GetMasterConnectionString())) + { + connection.Open(); + files = await GetPhysicalFiles(connection); + + await TakeDatabaseOffline(connection); + await files.Do(async f => + { + if (IsInShareSnapshotMode) + { + await f.CopyTo(Path.Combine(SnapshotsDirectory.FullName, GetSnapshotFileName(f) + f.Extension).AsFile()); + + // keep the snashptname of the database in a .origin file + await File.WriteAllTextAsync(SnapshotsDirectory.GetFile( + GetSnapshotFileName(f) + f.Extension + ".origin").FullName, + f.FullName.Replace(DatabaseName, GetSnapshotFileName(f))); + } + else + { + await f.CopyTo(SnapshotsDirectory); + // keep the original location of the database file in a .origin file + await File.WriteAllTextAsync(SnapshotsDirectory.GetFile(f.Name + ".origin").FullName, f.FullName); + } + }); + await TakeDatabaseOnline(connection); + } + } + + string GetSnapshotFileName(FileInfo file) => file.Name.Split('.').First() + ".Temp"; + + // TODO: create a connection string for MASTER + async Task RestoreDatabase() + { + SnapshotRestoreLock = new Mutex(false, "SnapshotRestore"); + bool lockTaken = false; + + try + { + lockTaken = SnapshotRestoreLock.WaitOne(); + var restoreTime = LocalTime.Now; + using (var connection = new SqlConnection(GetMasterConnectionString())) + { + connection.Open(); + var detachTime = LocalTime.Now; + await DetachDatabase(connection); + + Debug.WriteLine("Total time for detaching database: " + LocalTime.Now.Subtract(detachTime).Milliseconds); + + FileInfo mdfFile = null, ldfFile = null; + + var copyTime = LocalTime.Now; + // copy each database file to its old place + foreach (var originFile in SnapshotsDirectory.GetFiles("*.origin")) + { + originFile.IsReadOnly = true; + + var destination = await File.ReadAllTextAsync(originFile.FullName); + var source = originFile.FullName.TrimEnd(originFile.Extension).AsFile(); + + if (IsInShareSnapshotMode) + { + destination = destination.Replace(GetSnapshotFileName(originFile), DatabaseName); + } + + if (destination.ToLower().EndsWith(".mdf")) + mdfFile = destination.AsFile(); + + if (destination.ToLower().EndsWith(".ldf")) + ldfFile = destination.AsFile(); + + await source.CopyTo(destination.AsFile(), overwrite: true); + // shall we backup the existing one and in case of any error restore it? + } + + Debug.WriteLine("Total time for copying database: " + LocalTime.Now.Subtract(copyTime).Milliseconds); + + if (mdfFile == null) + throw new Exception("Cannot find any MDF file in snapshot directory " + SnapshotsDirectory.FullName); + + if (ldfFile == null) + throw new Exception("Cannot find any LDF file in snapshot directory " + SnapshotsDirectory.FullName); + var attachTime = LocalTime.Now; + await AttachDatabase(connection, mdfFile, ldfFile); + Debug.WriteLine("Total time for attaching database: " + LocalTime.Now.Subtract(attachTime).Milliseconds); + await Entity.Database.Refresh(); + } + + Debug.WriteLine("Total time for restoreing database: " + LocalTime.Now.Subtract(restoreTime).Milliseconds); + } + finally + { + if (lockTaken == true) + { + SnapshotRestoreLock.ReleaseMutex(); + } + } + } + + async Task DetachDatabase(SqlConnection connection) + { + SqlConnection.ClearAllPools(); + + using (var cmd = new SqlCommand( + "USE Master; ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; ALTER DATABASE [{0}] SET MULTI_USER; exec sp_detach_db '{0}'" + .FormatWith(DatabaseName), connection)) + await cmd.ExecuteNonQueryAsync(); + } + + async Task AttachDatabase(SqlConnection connection, FileInfo mdfFile, FileInfo ldfFile) + { + using (var cmd = new SqlCommand( + "USE Master; CREATE DATABASE [{0}] ON (FILENAME = '{1}'), (FILENAME = '{2}') FOR ATTACH" + .FormatWith(DatabaseName, mdfFile.FullName, ldfFile.FullName), connection)) + await cmd.ExecuteNonQueryAsync(); + } + + async Task TakeDatabaseOffline(SqlConnection connection) + { + SqlConnection.ClearAllPools(); + + using (var cmd = new SqlCommand( + "USE Master; ALTER DATABASE [{0}] SET OFFLINE WITH ROLLBACK IMMEDIATE;" + .FormatWith(DatabaseName), connection)) + await cmd.ExecuteNonQueryAsync(); + } + + async Task TakeDatabaseOnline(SqlConnection connection) + { + using (var cmd = new SqlCommand( + "USE Master; ALTER DATABASE [{0}] SET ONLINE;" + .FormatWith(DatabaseName), connection)) + await cmd.ExecuteNonQueryAsync(); + } + + async Task GetPhysicalFiles(SqlConnection connection) + { + var files = new List(); + + using (var cmd = new SqlCommand( + "USE Master; SELECT physical_name FROM sys.master_files where database_id = DB_ID('{0}')" + .FormatWith(DatabaseName), connection)) + using (var reader = await cmd.ExecuteReaderAsync()) + { + while (reader.Read()) + files.Add(Convert.ToString(reader[0]).AsFile()); + } + + if (files.Count == 0) + throw new Exception("Cannot find physical file name for database: " + DatabaseName); + + return files.ToArray(); + } + + #endregion + + void SetupDirecory() + { + // make sure it is empty + if (SnapshotsDirectory.Exists()) + { + SnapshotsDirectory.Delete(recursive: true); + } + + SnapshotsDirectory.Create(); + } + + /// + /// Gets the list of current snapshots on disk. + /// + public static List GetList(bool isSharedSnapshotMode) + { + if (!GetSnapshotsRoot(isSharedSnapshotMode).Exists()) return null; + + return GetSnapshotsRoot(isSharedSnapshotMode).GetDirectories().Select(f => f.Name.Substring(0, f.Name.LastIndexOf('_'))).ToList(); + } + + static DirectoryInfo GetSnapshotsRoot(bool isSharedSnapshotMode) + { + if (isSharedSnapshotMode) + { + return Path.Combine(Config.Get(TEMP_DATABASES_LOCATION_KEY), DatabaseName.Split('.').First() + " SNAPSHOTS").AsDirectory(); + } + else + { + return Path.Combine(Config.Get(TEMP_DATABASES_LOCATION_KEY), DatabaseName, "SNAPSHOTS").AsDirectory(); + } + } + + static string GetMasterConnectionString() + { + var builder = new SqlConnectionStringBuilder(Config.GetConnectionString("AppDatabase")) + { + InitialCatalog = "master" + }; + + return builder.ToString(); + } + + static string GetDatabaseName() + { + return new SqlConnectionStringBuilder(Config.GetConnectionString("AppDatabase")) + .InitialCatalog + .Or("") + .TrimStart("[") + .TrimEnd("]"); + } + + static string CreateSnapshotName(string name) + { + var schemaHash = new TestDatabaseGenerator(false, false).GetCurrentDatabaseCreationHash(); + return "{0}_{1}".FormatWith(name, schemaHash).Except(Path.GetInvalidFileNameChars()).ToString(""); + } + + enum CopyProcess { Backup, Restore } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Testing/TestDatabaseGenerator.cs b/Services/Olive.Services.Testing/TestDatabaseGenerator.cs new file mode 100644 index 000000000..be2aa8f7b --- /dev/null +++ b/Services/Olive.Services.Testing/TestDatabaseGenerator.cs @@ -0,0 +1,427 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Olive.Entities.Data; + +namespace Olive.Services.Testing +{ + public class TestDatabaseGenerator + { + const string TEMP_DATABASES_LOCATION_KEY = "Temp.Databases.Location"; + + static AsyncLock AsyncLock = new AsyncLock(); + static AsyncLock ProcessAsyncLock = new AsyncLock(); + + readonly string ConnectionString; + SqlServerManager MasterDatabaseAgent; + string TempDatabaseName, ReferenceDatabaseName; + + FileInfo ReferenceMDFFile, ReferenceLDFFile; + DirectoryInfo TempBackupsRoot, ProjectTempRoot, DbDirectory, CurrentHashDirectory; + + readonly bool IsTempDatabaseOptional, MustRenew; + + public bool CreatedNewDatabase { get; private set; } + + /// + /// Creates a new TestDatabaseGenerator instance. + /// Determines whether use of the temp database is optional. + /// When this class is used in a Unit Test project, then it must be set to false. + /// For Website project, it must be set to true. + /// Specifies whether the temp database must be recreated on application start up even if it looks valid already. + /// + public TestDatabaseGenerator(bool isTempDatabaseOptional, bool mustRenew) + { + ConnectionString = Config.GetConnectionString("AppDatabase"); + + IsTempDatabaseOptional = isTempDatabaseOptional; + + MustRenew = mustRenew; + } + + FileInfo[] GetCreateDbFiles() + { + if (DbDirectory == null) + LoadMSharpMetaDirectory(); + + var potentialSources = new List(); + + var tableScripts = DbDirectory.GetSubDirectory("Tables").GetFilesOrEmpty("*.sql"); + + // Create tables: + potentialSources.Add(DbDirectory.GetFile("@Create.Database.sql")); + potentialSources.AddRange(tableScripts.Except(x => x.Name.ToLower().EndsWithAny(".fk.sql", ".data.sql"))); + + // Insert data: + potentialSources.Add(DbDirectory.GetFile("@Create.Database.Data.sql")); + potentialSources.AddRange(tableScripts.Where(x => x.Name.ToLower().EndsWith(".data.sql"))); + + potentialSources.Add(DbDirectory.GetFile("Customize.Database.sql")); + + // Add foreign keys + potentialSources.AddRange(tableScripts.Where(x => x.Name.ToLower().EndsWith(".fk.sql"))); + + var sources = potentialSources.Where(f => f.Exists()).ToArray(); + + if (sources.None()) + throw new Exception("No SQL creation script file was found. I checked:\r\n" + potentialSources.ToLinesString()); + + return sources; + } + + async Task> GetExecutableCreateDbScripts() + { + var sources = GetCreateDbFiles(); + + var result = new Dictionary(); + + foreach (var file in sources) + { + var script = await file.ReadAllText(); + + // The first few lines contain #DATABASE.NAME# which should be replaced. + script = script.ToLines().Select((line, index) => + { + if (index < 10) + { + return line + .Replace("#DATABASE.NAME#", ReferenceDatabaseName) + .Replace("#STORAGE.PATH#", CurrentHashDirectory.FullName); + } + + return line; + }).ToLinesString(); + + if (file.Name.Lacks("Create.Database.sql", caseSensitive: false)) + { + script = "USE [" + ReferenceDatabaseName + "];\r\nGO\r\n" + script; + } + + result.Add(file, script); + } + + return result; + } + + internal async Task GetCurrentDatabaseCreationHash() + { + var createScript = (await GetCreateDbFiles().Select(async x => await x.ReadAllText()).AwaitAll()).ToLinesString(); + + return createScript.ToSimplifiedSHA1Hash(); + } + + async Task CreateDatabaseFromScripts() + { + await MasterDatabaseAgent.DeleteDatabase(ReferenceDatabaseName); + + var newDatabaseAgent = MasterDatabaseAgent.CloneFor(ReferenceDatabaseName); + + foreach (var file in await GetExecutableCreateDbScripts()) + { + try + { + await MasterDatabaseAgent.ExecuteSql(file.Value); + } + catch (Exception ex) + { + throw new Exception("Could not execute sql file '" + file.Key.FullName + "' becuase '" + ex.Message + "'", ex); + } + } + } + + public async Task CloneReferenceDatabaseToTemp() + { + // Make sure if it exists in database already, it's deleted first. + await MasterDatabaseAgent.DeleteDatabase(TempDatabaseName); + + var directory = ProjectTempRoot.GetOrCreateSubDirectory("Current"); + + var newMDFPath = directory.GetFile(TempDatabaseName + ".mdf"); + var newLDFPath = directory.GetFile(TempDatabaseName + "_log.ldf"); + + try + { + await ReferenceMDFFile.CopyTo(newMDFPath); + await ReferenceLDFFile.CopyTo(newLDFPath); + } + catch (IOException ex) + { + if (ex.InnerException != null && ex.InnerException is UnauthorizedAccessException) + throw new Exception("Consider setting the IIS Application Pool identity to LocalSystem.", ex); + + throw; + } + + var script = "CREATE DATABASE [{0}] ON (FILENAME = '{1}'), (FILENAME = '{2}') FOR ATTACH" + .FormatWith(TempDatabaseName, newMDFPath.FullName, newLDFPath.FullName); + + try + { + await MasterDatabaseAgent.ExecuteSql(script); + } + catch (SqlException ex) + { + throw new Exception("Could not attach the database from file " + newMDFPath.FullName + "." + Environment.NewLine + + "Hint: Ensure SQL instance service has access to the folder. E.g. 'Local Service' may not have access to '{0}'" + + newMDFPath.Directory.FullName, ex); + } + } + + internal async Task TryAccessNewTempDatabase() + { + Exception error = null; + for (var i = 0; i < 10; i++) + { + try + { + await Database.Instance.GetAccess().ExecuteQuery("SELECT TABLE_NAME FROM [{0}].INFORMATION_SCHEMA.TABLES".FormatWith(TempDatabaseName)); + return; + } + catch (Exception ex) + { + SqlConnection.ClearAllPools(); + error = ex; + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(0.5)); + } + } + + throw new Exception("Could not access the new database:" + error.Message, error); + } + + public async Task Process() + { + if (ConnectionString.IsEmpty()) return false; + + var builder = new SqlConnectionStringBuilder(ConnectionString); + TempDatabaseName = builder.InitialCatalog.Or("").TrimStart("[").TrimEnd("]"); + + if (TempDatabaseName.IsEmpty()) + { + // None of my business. + return false; + } + else if (!TempDatabaseName.ToLower().EndsWith(".temp") && IsTempDatabaseOptional) + { + // Optional and irrelevant + return false; + } + + EnsurePermissions(); + + builder.InitialCatalog = "master"; + + MasterDatabaseAgent = new SqlServerManager(builder.ToString()); + + LoadTempDatabaseLocation(); + LoadMSharpMetaDirectory(); + + if (!IsTempDatabaseOptional) + { + if (!IsExplicitlyTempDatabase()) + { + throw new Exception("For unit tests project the database name must end in '.Temp'."); + } + } + + if (!IsExplicitlyTempDatabase()) + { + // Not Temp mode: + return false; + } + + return await DoProcess(); + } + + /// + /// Ensures the right permissions are configured. + /// + void EnsurePermissions() + { + var identity = System.Security.Principal.WindowsIdentity.GetCurrent()?.Name; + + var error = "\r\n\r\nRecommended action: If using IIS, update the Application Pool (Advanced Settings) and set Identity to LocalSystem."; + + if (identity.IsEmpty()) + { + error = "Current IIS process model Identity not found!" + error; + throw new Exception(error); + } + else + { + error = "Current IIS process model Identity: " + identity + error; + } + + if (identity.ContainsAny(new[] { "IIS APPPOOL", "LOCAL SERVICE", "NETWORK SERVICE" })) + { + error = "In TDD mode full system access is needed in order to create temporary database files." + error; + throw new Exception(error); + } + } + + void LoadTempDatabaseLocation() + { + var specifiedLocation = Config.Get(TEMP_DATABASES_LOCATION_KEY); + + if (specifiedLocation.IsEmpty()) + { + throw new Exception("You must specify a valid path for AppSetting of '{0}'.".FormatWith(TEMP_DATABASES_LOCATION_KEY)); + } + + if (!specifiedLocation.AsDirectory().Exists()) + { + // Try to build once: + try + { + Directory.CreateDirectory(specifiedLocation); + } + catch + { + throw new Exception("Could not create the folder '{0}'. Ensure it exists and is accessible. Otherwise specify a different location in AppSetting of '{1}'." + .FormatWith(specifiedLocation, TEMP_DATABASES_LOCATION_KEY)); + } + } + + TempBackupsRoot = specifiedLocation.AsDirectory(); + ProjectTempRoot = TempBackupsRoot.GetOrCreateSubDirectory(TempDatabaseName); + } + + void LoadMSharpMetaDirectory() + { + // Not explicitly specified. Take a guess: + var folder = AppDomain.CurrentDomain.BaseDirectory.AsDirectory().Parent; + while (folder.Parent != null) + { + DbDirectory = folder.GetSubDirectory("DB"); + if (DbDirectory.Exists()) return; + + folder = folder.Parent; + } + + throw new Exception("Failed to find the DB folder from which to create the temp database."); + } + + async Task DoProcess() + { + var hash = (await GetCurrentDatabaseCreationHash()).Replace("/", "-").Replace("\\", "-"); + + using (await AsyncLock.Lock()) + { + ReferenceDatabaseName = TempDatabaseName + ".Ref"; + + CurrentHashDirectory = ProjectTempRoot.GetOrCreateSubDirectory(hash); + ReferenceMDFFile = CurrentHashDirectory.GetFile(ReferenceDatabaseName + ".mdf"); + ReferenceLDFFile = CurrentHashDirectory.GetFile(ReferenceDatabaseName + "_log.ldf"); + + using (await ProcessAsyncLock.Lock()) + { + var createdNewReference = await CreateReferenceDatabase(); + + var tempDatabaseDoesntExist = !await MasterDatabaseAgent.DatabaseExists(TempDatabaseName); + + if (MustRenew || createdNewReference || tempDatabaseDoesntExist) + { + await RefreshTempDataWorld(); + } + } + + return true; + } + } + + async Task RefreshTempDataWorld() + { + await CloneReferenceDatabaseToTemp(); + + SqlConnection.ClearAllPools(); + + await CopyFiles(); + + // Do we really need this? + await TryAccessNewTempDatabase(); + + CreatedNewDatabase = true; + } + + async Task CreateReferenceDatabase() + { + if (ReferenceMDFFile.Exists() && ReferenceLDFFile.Exists()) + { + return false; + } + + var error = false; + + // create database + data + try + { + await CreateDatabaseFromScripts(); + } + catch + { + error = true; + throw; + } + finally + { + // Detach it + await MasterDatabaseAgent.DetachDatabase(ReferenceDatabaseName); + + if (error) + { + await ReferenceMDFFile.Delete(harshly: true); + await ReferenceLDFFile.Delete(harshly: true); + } + } + + return true; + } + + bool IsExplicitlyTempDatabase() => TempDatabaseName.ToLower().EndsWith(".temp"); + + public async Task CleanUp() => await MasterDatabaseAgent.DeleteDatabase(TempDatabaseName); + + async Task CopyFiles() + { + var copyTasks = new List(); + + foreach ( + var key in + new[] + { + Tuple.Create("Test.Files.Origin:Open", "UploadFolder"), + Tuple.Create("Test.Files.Origin:Secure", "UploadFolder.Secure") + }) + { + var source = Config.Get(key.Item1); + if (source.IsEmpty()) continue; + else source = AppDomain.CurrentDomain.GetPath(source); + if (!Directory.Exists(source) || source.AsDirectory().GetDirectories().None()) + { + // No files to copy + continue; + } + + var destination = Config.Get(key.Item2); + if (destination.IsEmpty()) + throw new Exception("Destination directory not configured in App.Config for key: " + key.Item2); + else destination = AppDomain.CurrentDomain.GetPath(destination); + + if (!Directory.Exists(destination)) + { + if (new DirectoryInfo(source).IsEmpty()) continue; + + Directory.CreateDirectory(destination); + } + + await new DirectoryInfo(destination).Clear(); + + copyTasks.Add(new DirectoryInfo(source).CopyTo(destination, overwrite: true)); + } + + await Task.WhenAll(copyTasks); + } + } +} \ No newline at end of file diff --git a/Services/Olive.Services.Testing/WebTestManager.cs b/Services/Olive.Services.Testing/WebTestManager.cs new file mode 100644 index 000000000..e0b45f853 --- /dev/null +++ b/Services/Olive.Services.Testing/WebTestManager.cs @@ -0,0 +1,253 @@ +using System; +using System.Data.SqlClient; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; +using Olive.Entities; +using Olive.Services.Email; +using Olive.Services.Integration; +using Olive.Services.TaskAutomation; +using Olive.Web; + +namespace Olive.Services.Testing +{ + public class WebTestManager + { + internal static bool IsDatabaseBeingCreated; + internal static bool? TempDatabaseInitiated; + static bool? isTddExecutionMode; + static Func ReferenceDataCreator; + + internal static void AwaitReadiness() + { + while (IsDatabaseBeingCreated) Thread.Sleep(100); // Wait until it's done. + } + + public static string CurrentRunner { get; set; } + + /// + /// Determines if the application is currently being ran by Sanity. + /// + public static bool IsSanityExecutionMode() => CurrentRunner == "Sanity"; + + /// + /// Registers a factory method that should be invoked upon creation of a new database to create reference objects. + /// + public static void CreateReferenceDataBy(Func referenceDataCreator) + => ReferenceDataCreator = referenceDataCreator; + + /// + /// Determines whether the application is running under Temp database mode. + /// + public static bool IsTddExecutionMode() + { + if (isTddExecutionMode.HasValue) return isTddExecutionMode.Value; + + var db = Config.GetConnectionString("AppDatabase").Get(c => + new SqlConnectionStringBuilder(c).InitialCatalog); + + db = db.Or("").ToLower().TrimStart("[").TrimEnd("]"); + + isTddExecutionMode = db.EndsWith(".temp"); + + return isTddExecutionMode.Value; + } + + public static async Task InitiateTempDatabase(bool enforceRestart, bool mustRenew) + { + if (!IsTddExecutionMode()) return; + + IsDatabaseBeingCreated = true; + var createdNew = false; + + try + { + SqlConnection.ClearAllPools(); + await AutomatedTask.DeleteExecutionStatusHistory(); + if (enforceRestart) TempDatabaseInitiated = null; + if (TempDatabaseInitiated.HasValue) return; + + var generator = new TestDatabaseGenerator(isTempDatabaseOptional: true, mustRenew: mustRenew); + TempDatabaseInitiated = await generator.Process(); + createdNew = generator.CreatedNewDatabase; + + await Entity.Database.Refresh(); + SqlConnection.ClearAllPools(); + } + finally { IsDatabaseBeingCreated = false; } + + if (ReferenceDataCreator != null && createdNew) + // A new database is created. Add the reference data + await ReferenceDataCreator(); + } + + public static async Task ProcessCommand(string command) + { + if (command.IsEmpty()) return; + + if (!IsTddExecutionMode()) throw new Exception("Invalid command in non TDD mode."); + + var request = Context.Http.Request; + var response = Context.Http.Response; + + var isShared = request.GetValue("mode") == "shared"; + + if (command == "snap") + { + await new Snapshot(request.GetValue("name"), isShared).Create(Context.Http); + } + else if (command == "restore") + { + await new Snapshot(request.GetValue("name"), isShared).Restore(Context.Http); + } + else if (command == "remove_snapshots") + { + Snapshot.RemoveSnapshots(); + } + else if (command == "snapshots_list") + { + response.EndWith(JsonConvert.SerializeObject(Snapshot.GetList(isShared))); + } + else if (command == "snapExists") + { + if (new Snapshot(request.GetValue("name"), isShared).Exists()) + { + response.EndWith("true"); + } + else + { + response.EndWith("false"); + } + } + else if (command.IsAnyOf("start", "run", "ran", "cancel", "restart")) + { + await InitiateTempDatabase(enforceRestart: true, mustRenew: true); + DatabaseChangeWatcher.Restart(); + if (request.Has("runner")) CurrentRunner = request.GetValue("runner"); + } + else if (command == "testEmail") + { + await new EmailTestService(request, response).Process(); + } + else if (command == "dbChanges") + { + DatabaseChangeWatcher.DispatchChanges(); + } + else if (command == "tasks") + { + await DispatchTasksList(); + } + else if (command == "setLocalDate") + { + if (request.GetValue("date") == "now") + { + // reset to normal + LocalTime.RedefineNow(overriddenNow: null); + response.EndWith(LocalTime.Now.ToString("yyyy-MM-dd @ HH:mm:ss")); + } + + var time = LocalTime.Now.TimeOfDay; + if (request.Has("time")) time = TimeSpan.Parse(request.GetValue("time")); + + var date = LocalTime.Today; + if (request.Has("date")) date = request.GetValue("date").To(); + + date = date.Add(time); + + var trueOrigin = DateTime.Now; + + LocalTime.RedefineNow(() => { return date.Add(DateTime.Now.Subtract(trueOrigin)); }); + response.Clear(); + response.EndWith(date.ToString("yyyy-MM-dd @ HH:mm:ss")); + } + else if (command == "remove_snapshot") + { + Snapshot.RemoveSnapshot(request.GetValue("name")); + } + else if (command == "inject.service.response") + { + var serviceType = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(a => a.GetTypes()) + .Where(x => x.InhritsFrom(typeof(IntegrationService))) + .SingleOrDefault(x => x.Name == request.GetValue("service")); + + if (serviceType == null) + throw new Exception("Cannot find a class named " + request.GetValue("service") + " in the currently loaded assemblies, which inherits from IntegrationService<,>."); + + new Thread(new ThreadStart(async () => + await IntegrationTestInjector.Inject(serviceType, request.GetValue("request"), request.GetValue("response")))) + { IsBackground = true } + .Start(); + } + } + + /// + /// To invoke this, send a request to /?web.test.command=tasks + /// + public static async Task DispatchTasksList() + { + var response = Context.Http.Response; + var request = Context.Http.Request; + + response.ContentType = "text/html"; + + response.WriteAsync("").RunSynchronously(); + + response.WriteAsync("").RunSynchronously(); + + if (request.Has("t")) + { + await AutomatedTask.GetAllTasks().Single(t => t.Name == request.GetValue("t")).Execute(); + response.WriteAsync($"Done: {request.GetValue("t")}

    ").RunSynchronously(); + } + + // Render a list of tasks + response.WriteAsync(AutomatedTask.GetAllTasks().Select(t => "{0}".FormatWith(t.Name)).ToString("
    ") + + "

    Restart Temp Database").RunSynchronously(); + + response.WriteAsync("").RunSynchronously(); + + response.WriteAsync("").RunSynchronously(); + + response.WriteAsync("").RunSynchronously(); + } + + internal static string GetSanityAdaptorScript() + { + return string.Empty; // 24 Oct 2016 - See if Sanity still needs it. + + // var r = new StringBuilder(); + + // r.AppendLine("window.OpenBrowserWindow = function(url, target) { if (target && target != '_parent' && target != 'parent') target='_self'; window.open(url, target); }"); + + // r.AppendLine("$(function() { "); + + // r.AppendLine("$(window).off('click.SanityAdapter').on('click.SanityAdapter', function(e) {"); + // r.AppendLine("var link = $(e.target).filter('a').removeAttr('target'); } );"); + + // r.AppendLine("});"); + + // return r.ToString(); + } + + public static string GetWebTestWidgetHtml(HttpRequest request) + { + var uri = new Uri(request.ToAbsoluteUri()); + var url = uri.RemoveQueryString("Web.Test.Command").ToString(); + if (url.Contains("?")) url += "&"; else url += "?"; + + return @"".Replace("[URL]", url + "Web.Test.Command="); + } + } +} diff --git a/nuget.exe b/nuget.exe new file mode 100644 index 0000000000000000000000000000000000000000..305300ad4ff3c7a1fbcd8a0a06b740eb39bf39da GIT binary patch literal 4266712 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Ö^{ErGn>!Ol~tp4^;2?4F+ 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 z z0MAN)sy~L!@MI92Xlu{od4?-8fD;4|x3P~Q0glQ5ssbD>08671_;}qUMh#1u^_aTugA2a40bRr<5? z*q$!$RD!wj`lEUc`#g2y_4`$LwzR?{1Y>)8W!el1#W3NPv_y2{wQDmNH**lJ?X}2X zb}U>p?rh%XTHVlLY3lzXDXKn_sg4{YVu8SKFb(MD)U`Kz zW8%ErbbJ^jI{qL;sy|1Fy6bKPSSSj(PnTO1>QkE5AIW~JOKewg6@yW{7K1Z*nwgB8 zX4LL6b7d&|zL)Rn*$iDabL-us%D}bBz;eAY?7hO(e3pgz0TrpKcrG@CXX-T^@@!*3 zWmBoC%R(U-P|+)+rV7RR7pA5q2hU`o(KKQcvoOWDEOcvV>kIGHOj#ouLlF>#jepzO6};G))|j0O)Vm!j zN?%{~H|W9WdGZn@eK_U zjnru@!|vuW(Ij*k*U;F78?mVNA~DTL+xW>U$42%o`3)2(Ti)U?AWKdtFKU znhSp#$R4_DQ(Z)PjjER%{5IwaA~CgF8@4PV7mO+t>PSul1kCYIHCfM^oxHQsqz+$2;e zNUaAcH3^5dX_W zO*NMB&nU(9L>(o7Q3`Cpg0Tmb*MSfkrO)0h~N>^Sx)|Gl1!7LJSzrD zW~>>Ka)bW}a1C-ejQO9bft{ZNx1*07(XMb!pxFoU_h>b5k|%D-+~eBhuA+&lp& z^5Rk?GLMbRwx^a_d=x)Z?!)KP6uS-Tpxu z-IkqRi9V}RbCg|tewO~sMav7!92TxL+w=vzW1FflNU8r4U(N_Uk9e!sV;aI6ffcJ8 zlCbcb=G3@Rc$1ugdwv;rWUCzDD*{BuDJ5yv=ELgYe4RBO;kWQa3z=0Xh5kR177p-7 zi^6XhVfU05|0a1>sv`DGtAByj_C5%Y@dz>JWaDK*>KPl(rpXvs1OrhOcp?LuR^`wl zPeZ>VGrt)t%x!7`HO!-`7q3G-T&(_-ZDQ38AlB>c$lM1L@#9_eq^G!RUp5w^$_m*= ze_wNMg#7&dT~>$*S(}Ly1~!8hPFTq{YMV!Z3YeN1*?Bp45Vpvf;G*w@h z*vGc1Y@0fn3LFC7iJ&{Yg^*S5u3(QqF_3iVC;yU!x7SyXgopNWR<~V^b~C)?|fOrole2B*afd#6>xhMR?bto}fBsg@H?Dvi}|0GpU|m3ob)s%#Vk6$lMjDmr>b zU*+Y0QyKF<)S_LdX>E)n9r#wMELNJrZ|gn$MV@U?)6|cU#Vg%SuZ=@T16f9^u2bIs}finFycwKAIP*vgNZQG)?P>44auWssxTB{0}3{>R2)st8qMz?1yU9?>29UjWO8{ zvy{a^?6g;0?bW8*%ap{FqtfQFA2F@HGMI|D7>wHPaliHq+%?#L?qgc1U;9m9xAD)^ zYf?V?PL4?2=0Bw5Kh9Ok+)4x1oNhU&;+eVV^ui%@lCQG*JpNG3a6Y1>TeFTYl#MTtFNGbs7v01`yQ|RwMd-KxQO>z zt}ga|pyDHI$hi)U-Aj6r%+U06G`X1uH|a?NZkF>CsLN@LB6`b6V5ppFu>7&gz04rY zt5W}E(DS$8G0$P6jLcJwP*0M_k@MF$tH0#YUR$R1^(iZQ-%H}~<<-vq~ zJ>Lbt6Vp>Y5gM}MqV3OxR;3OzYV0YWsw))7mhg{Bmmjm<;XSyD)nB+8G@lk<67ru| zC2CVp)j?WQ{*%J5;s1tLc$X1V#Sz|ZPMe;+L(WwC-mPj)(mcq=R;hOtPt3Db%$8Ay zi;bJiTG`d-Ug=oTtl8AVtLD5{OZJB?lURDx6)S!#ms z8|HRG1`OWXq>&s%YLxNvyO)os~pNCK^ALe1x3~5;P`ao~?$p;*=xhU>_GxWXy}N<< zBgov_5*}?)Lj-NF^nZ{t#pIZ>B=#E0UGcXiJX(?1s?V@c5hFHCHxa6*p^p`veNGF0D*ZBQr)re z=CSM~UYnc5Ym4S4Tg}hl^Bmie^VaITwZ?OOCRO#F26WnaR^b$-lNfWSU{Wob=xg;l zMeBRDDy81@YE){LMgAUCs>88Y>9EF5q&(8CkxO~x+XBzY819Hrdq;)zwn9OwJ1Lhs z32&Nc)74%5YMi!px#{Zeww_A2jYaX&@)=KqQI*b0kH0exJ0mJm=Wd&JC7|Y$7v5!E zOR9RCOzz4~COiL4u4bREn%!h##j7&iTTXCWKat1Od%J+3!j0zYlZ!%^A=7l%=wRSiP@qT#qh(CK1uA2e(!k_~;vOH6a)k?o5rg`Hhc z)bf4;6oVh}oDg;;psfizm!WJI;j9g8*_y4IvQaO!TU>wo|6lsC49@hfrT0==%e!&| znf3=PArJEG?yfGT&J#bA`r+2&*w1pN57NNZ0R`==mbPY$_93VxW3;Tv>cF(Iw#mKw zs`c{ov#g(O98dlqu|M8b@C6fn)kgXIdDb7}wi}@MHi*l3rzQ)vX3AEj@z(e+SN8CMLR;G-D)nkOBhCwcj;gs_PXj53hl@k9c@=jBbE zTmqtt1fJDv__sVIfo&uKRu&>oA&`Jx83`!V1PRbwQuKbk;yOibo9Flxvs!%6f1PEH zK>n#9o6G)YcpSI@aM@oX=jrA{385vCrU4tjx-`hdPo9RGjOM#fLtqTf!CVaPq6Ip+ z?vrrwb{VB3?mh_~CB8^P9P|E-)N$z+2V>FXJ7J4Miijr&<9^ib@cu*o!{o!n`N3O( z-N9eW+9Taj-X*#d+X30aCHT>g?KR^164kw|?`ZVaB_HOo&&JqKaFMr5@#g=HCS{jS zY>emOXD(W^u&`JteAaaGPlQA#%XlbO+bPK5i$=>m|AIRdS@t8_^REK9*#3so4bU6T zarOzK9FS#l0a9T0+xU72z6Ny;C0op)XysiW0 ztGiRXu=kvx-nJ9~|ATzXWbDB^olQ`pDKIbTHT-9uDlprrz;qi!LQr7z8t*(i9yn1b zNbL!!d74fTqW?RVR%3hSx$1i5nX_J(@4AA9ak9&M$_7gEtY4FOk!+kmEt110jYMri z>r=5WA5Mo}n$)M*NsXNe!=y05*rkYAt!+;lY+3#P2BP{e|BU`hl&jERx3=SR=$qDd zY-;N{yw}Oka1(QPhVSQH7wF5a?bx2YU~Pv0-kW%o0NmP+jSA<}R=L)6_n$-;-SIF0 zBh%*A5U?%nJ#ZR$#$D7ybqCwktccWW}q?+7X>_xfZ;1f$owpM zpZo-ZxRtPbqU&)xpS?Z6-4QBY6D8AD!uIQ^rYOiBDvVklTdeM-*#9lcE?9MM0bX|i z!To&zRQDD1O+j5sPKKfC5%eZFPX>M$*iM{!+fQMeXYE3JZ)c^lgLu7NoQaRd>y$R&Ajhv1bE zb3iQoQLb}T>pDlZNv&ZLadNV=svlH$YC18}hOdZe!)1<0ema75?#Q9Z{m;ltwOdB_ zE#hVm8BhOE;&*znG?WK}dhOzP-*}<$hjFBnizf0)efW-uIGM9hct^?nKxJhhgcb_x zJ>I!yPU!XCep>XyLSexf1rMZhthC)9FxM`LW?)Kr7>tGgux~-f>w08y4+rY)L|huG zPy#9q{t-$-N0g{Up}RJlN`2h_E)u2nv8zcG%KP3DMLH}|jZvZ!Id^SlJ8=!$9`&xQ z1!%FGC|OT@B*{r;{W6#)?`P0Q#cq20FLXKUUo_{jF7bUM41_+JBD`Vs$Rc6P;{ z?Ztl$+)+C~#iryP?TF7@)K~Z!;XZFRZyXf$^%$OMmL09^Ee3^d^k$AU_AfcUowWfvOBeQE9Hlf7!m}q53dR z|EGE!18r5p$MQ}Mg;GK#T&pOC|I3Fe;hm+l&zaH+K_#r$c;`j2&MFk7J_0G#!Gdc_ zzeZGClGjs|RTI(e*A4Q){YB*3b97L8<#yvHh62nmCLAIU^1;yjKpyy^uk|L`?=im_Eg=X z3fx^hZAp%DOnI6iw(EN!7K^3CHW)UV5#tMr2!*?u<;k3k}HpdXs50C}g@O+#yrhFN{GeK>tkT z_=zMotni5@D+Ty@l2?C|8KGfeyp!Z-8m?mCo>LY4Km|V;l&ruhyi+TnILVVXy@p$O zDo=KokWREBCj@z-*LdeA;yh6(NPQGivI2q+xOydxHDdiB#jU1_w!x=O6=jgcT@KqJ z7RAj{*$*o2Hh)P zJAfr)Osvt{k+pTKjMECcUJ_<|9G_Cvp)n=J&G=9ciPLz|^yu9ky=UkRjS*rfd8hNN zj#^}WoFua*OZb>EnV+6t!YREhvYJ6j3TKH6cYet_#Bqb^yYO~?iOf&5b|^l$qz~So zw)^{v7{%H(%tSG7>F$94LMFV;7*G~I3=xj;_!KWr=dieo!EBl_i|)M!QnBSwC%w zhvXa)Nji6qh|zDfE9APW3t&a(h|IxPDB&dMh-?HV(UbN?8`{-UUxI$gPO2&SKV_(+ z!+Wk5>x0%Mt6~>#&X-#uJNQw_DOd-x&bzOf__l(721;TlnpMxlMU_0kOP&8Q;3fYg zRA@ls+|KE1p{|WK+dUhl^t-99-o;S;iCP05J7pkC-T49)gLQ%U>o7DNB{JPfthMO=1^BX4@`{0%hD|+v+z7iq zdS7+Zttt2G;V;S0-6@|J;)mBoys8f%HI?Y)5C$ogGrUG9l)U2a;g7veoC*BdBg zTCTrr`Y8Nl<$81CTUEJkDaeGga*8uAUHG_Me*`#1>2e9PGqkw52Z<Apb6GZnz;Z6bd+RIZJKq+BcBY`M1YxLn_u$RR1$l60XDS|Ex|C~unn)#jTC1*uDJupipuKscFo%0v;Ny=Z~Cg+%iqRX zf9w(eimHY<+$PLtY=Q^xOzIHwK8EkWJt}~??nKB7RaF$?vk$m0MW|B9{qfQl?3w`3 z3=Gm&{h;Dq>|kzR2Jv1lA~Ai9!>VbdU&+pBVJA8P%rKdyNv5N6^HJh=3I3vG;f34X z84Gq!g8#fM4i!8KvN?XD;}@_G(Qz%DH1l5l_f_{(T9#(hvZ`|cRt01I{ik~ib#8w@ zayq~zG&-3;My0v~dphRt?id}}gG1iz_#L-gpS7lS1sS)m%5JC2>oLK*VeM@Aq|O!R zk&Uojwmvm2*W1E!RIaxd_bS&lc24ZqOdo}(T)(|o=Cp3sJMO@-?xl=`>-%kY(Qx{ypQ^X#i!T-7hh}# z9)=VJuM0G#%Xnq~DlGYzP^!aS#XKcfi2er@Z6J_;sTKKPC)|v!EEkAV(6krhI(cgF z8~O4nqK)BhAd9o!b|eln5(4=QS~m9DrUW}Rp}od`8`ZaS``T)qWP9+59Xw%q31#SQ){h(MwB((1Y{s)G|pC9|9+K z=rBok!d)Np5tROFTc`)Y817%gI$l5IXmd)B5`m6zt z@+`4g(xRNrviRe+$e}PTo3$gpLS-cGo#j?u;oe1VWmsa%jv)lL>=R_^=w&<-+pxTJ z;pJ-5>+lo~IvLTSu^VMzsIu)#RLkXN<8x2w(${(rL*uZ(@n_-k;}ui9l|C90vRzii%>Neh2sn$G~FS_}De6rv^mqEUSdno+#f z%b3Wm_dm_k--F(don~fx80wQ`RhnIM6ZMNL}59Z&&KmM7%O+oMYXZj}M z7mMzQ#5Vm~92SfqxHSW;GvBuSwd6lH0qoAQ>8qZM3XFaj-$~xnPf-SC`W^d&j_lZh z@`-WcuQM92g~@MN{>x8v_D2z*<`$OoR|~dlJ|)Fy1P9@3=x*+PbBEpxECyX1I`+KU zvcFjrFU6OgzT~J(&wK51dkiUWiX&Uz>|5oH;m*Q0 zlXN8IO{vY5H%moa-cF6m+rjt>l``ahsNCWyDPtq9jGZAl9|j^TXFK;StuKxM9m)A9 z`Hbbfuy9VPP`JhTlON>Vpw#o%UfW0bYxCqM`TvrCJBVla??fPzz0nPhCZxZ|d>sHM z(rKk85JNqVks3LlsNB&^i~*q0XA_eFGvsIF$&Xw>n269p#1To|g}(}gHV>KbGbe=q zcp84ciQ(CIe*B0$WWukJ4^P>#L>x=lk;BQvc&V6_biD3N^tE~wCb@T1NN)=UPrL_e zSY&$)pX`+9IdHbAkevQbG^0i?B*rKmD^k?Xpll1L3+3zEc8m=D=MmTe5smit+j(!@oO~;rLw%tHellME& z_1+lIY8eBlVR+N=weoRcPNMd3AavHA*mvCL_*PP06MLdkowFxaW@3BtvB;jRz*nfe zNo>gZvLUIRI2K%FCyv7jKRU-&?bL5uoWf7mU%VJntLiVd7Sta6P9}WZPE;n-FuIu+ zcHH7F20l^v$Dum@XVlU=K^)Oo6t;gq`3wZ|uK>xi8lBz95@{;`vVSw~4sW?CdsPw* zLqDj|iH+BQb$cet-rr zvrTQbltk1dGfwA1P3!dn#hcAj`;PPU);jiSq2teHs>04aCpImaXw6!uf;Gq~)@jTQ2uCdP@v)0$6V|XIq zNJmb_nbDE&S{j6(Oh>Mk^j0xe*jjj%9J?(OKGu=10Y~`1R?c_2xH%kwC^9OX7;H{~M zict>Tl7?)ZU40N&YFG0iqG`S#jkV7gS+f?DRGPCbkX16E2Co)qC zb21Y_?*CM?xvP3zB(o8Kp*Txq_GQT|E?3-BayrOTT#(XoG%9LFKRgp>RE|_V{lIt; z-pY}B=15Y}PB=6CW@1|9nc-6l7t>t3BSR-SGyGeSQRfOJJeC)Q${69G2I9*Us&m91 zg-Y0dx7bS{XLyU%8;LqRTYdsPoh1&HR0ptBTfrLn?V53qVPG2~&*GmX`v)|fNSi@q zT&UN{((nwC>bB@9TldOO%#Dh;_a>fPpz*q|!i*hE=AEk5K8NIEiH_&|p336P2`HUp z938B>zED{<-bmQ!iP;#GpNI_#vrE5;WsaynWp{fjS3nA!l=QDFr!=v!jEkK!q8|0@J41_DJkE3#3S$Wx%_(0AwzMWz2o=G&le^w7mwj~ZW-N{LE3 z13nm833>G1mVx-V$aPzk*r^*uCgxy8Les+}ao9zrX6P=9Qf<<~9YF(mB!vt($a5)V zU6F9ydhaCB^~BM|_Q=H15xS&VlIJLfcg!A1;@BgB6MJ;0a$%x9IvCmNpS4Fu*`yfR zBcq<+8?>#^LfY@ogpb>x_d^%S{Zo0-8Cu-M;8R4AQN7E>5g9LR z|JCvt2$XGu4(-P*wo^B|x~)9)ujx87M-`JDZB`!Ix{u!ZWU99{RLesf{)LNWF>EI`X4L-ieMVM{_!2^kW_Q5o}3EK8>$XnULtnPr+n#hTy4*Cf3HkK}>qSv4kyJ4c)TppaR)*mo?K2NV7hxhNGmxfq+VTpo$!ay7m}F__5Z zS9!Ty11^%w=Wu4q&W|ll;U_CQPeE!`Wv98I$)2BN!pCLjI?1$Y>`lq7Gqkul$%rU2 zW#@WvMA;Fx{|oZTDL?X}PJVcuup`$nmJ{9}XIJ-=`i6SRnk?P*tqHrIa0RxCjJujnxSacnZCCB0%+1Z_} z5J@=r5$8!E`n3c$(ay@w=$|b^CK)3cl_3*AQijBHR#)sh)|H16xhJ}!oXzQq(T{cI z3Aj=j`aHfuWk#YaV_=fL3+I)6hFJjJ<*Y3+$yfpKD+J#=g3nykQ}|}FPQ@uAIQ%&U zUCz)nM;zho{=hvtO$)gtd_u6!QLoIZS%sn{k~_*z%jGgjSZ&N&z&Cl!Z;@&=;a$gk zjA9OMz&|Hu@$|o-#}|3nMA(;bxb^m0nr_iD5%y)EQHrrW`zpm}TD(GNB24e`&a-Nw zn|&M$?CnFv#BCpq3jS`if$IsGTF(ND8+fu*$;tI3yXvKv7r(_|S;t@l+h z^Op@nl;5%ZyZDPOW;Y6`4_cDKHC8E=YMcPSD1tNG!L0!Wn_n&CIEepRe8)eP^;3Y2 z@LvNr%BDS#Qi78AHF3h;;$)2N7RqE~qrZ*QY_yx-|EZNR;jz&Z=J$Ih%l2(fu#d55Fan98tuJLVlmn zF7|$Fdb`;CZz%TL$G!V#V8H;wq zRO(#BJ#oZ4h#czzEZxrQ9K0_4anX~Av06saMQE5aoz%gIiKK$~vvPt8B0DaCgbMm+ z>jxK+f<{VjNtsKW^U zOho9%(_)I{`Hx7Rcj7BliW7OhmY3%};39ec5U1G% zH$U^3#VPz`ZMU}|wW_vTec>A9^ovaRxb3FVIu!MEiLo=ZxH(sVDBL>T2gD4TCqzGr zCRzUocX+Q5-3F(OE)^#7h7W~`m*mQKnKIK6%2o8lA|LXS6p%N>b~;A>$xpgsp|LXK z$OuMd!f3lPfzFGKY#!NnoJYS*(wpRwB$~@3qaWwd>+m6+zaL+rgplOXI2hKtOxdQu zU7U(D9{b&9sAu=+7Wxjm*_ix-ub(?V!J~RFVMav&ZX2@iIIPb3UWDYhz_C)j1&gJ) zzs0-ce_O`wJ3J_WJnP2mzRRn+fCWt6ADK>$$8M;`8s}?G-EE+4e4Y6Fs!ihaS6M#i zve>i)6f}2)c2{c?cUR($`PzhI1_;Z^4~*9nU`6)Z7TIu74Q5r7OKE-flu~H24S%bc z(|N_mEgyuROm92kW>tFIP0Jq@ z)eBV6ezb8)pdwvKoPIhWwY^o=EBlI^E%K6rkJuqxH(WI5>Oy z71FAK?-xBK2rU;Z(eidBQvHN@ed@jUrh4vDApen`8@WW!6>(P2?K{@vj6-pe5o=*pZZU@gOzPaeFIY=k4XJeQ-4CdB`_EqN)Z1g$dV;y20 z&`hIM^4E;sw`qmyD&T*YDlJDp=|DD)aEMB@_9%sM+rdgO&eD%o3uyhNXxJIjCI4TM zX4<0oTZuD1cO#Df8xbG&W=*HU4m6S;w{*$PnjXPzV(=e=+DV|^0^{T@zUNN{@2hS_nn&N3pE#W}Jdc3sIXRN& zJK`s@Tdbc}nDJ~F)-f+rpp8ni)t0w;N<^Q?h4f2c?4In2WS8b!Uv(GJD}qUQ$mZKn z(ffjht;L6TI!^Ct9$}3F5Jz8i4~4H=DSRnd18tt)Rq7bmz0i_#5B~LIdk-W^{)zTH ziD&gR9%ld9`y*aU{w)F@sj5I5LB2w}s9R@*$8utRvV}NHUEXIEG-I<{0Y^4_nqx9v z3-XlN6yYbc*$0u9RoU!v;Rg8rO(uM7v-by%H9UtGHrwLnI!B`Lu7fXUlH0`*CRx~M zuYf>ilD*I4jjM?xV?0$}yUl0D7#rEZ!k95;uYfqk7z>sd;{(J!#yGacS6!9b;*>;W zi;Y@hixqFy7Tb4hi+`J>JF&%*Y|a)N{n!@o4D_?Y_uKD=5s^&a^DLd&bEma zFq?M!j?+Gt@Q|ckoaWMQF~w;=B1(G?zAVZl?Z+zZvHs5F46A41{&z-yI-dR888sYx z6j-!Z_#rH4I+`;>v8wzrq*G^I2sfltSN%2qPHS=_KtG!_OnoLyy$jRq!aO`_m~tje z*@bCwVIG+@OhYD2g9{@&F-4Pl{H9GSY`htt=H9BXabxU0wXpHNvHR-8qZA z$VO+9@jX}0-H0SMVf>*LS?Ql0e@H1t zcF%|>c28_%%f5ZbW&hbk?n&8K&gRO#(T~f12vf3q>*C9@ASwH2f#E!~X@xxy&vVf2 zqC=!T9{!>AY?`)Hed_(ga5vYni}`RIem!fF4{p@w5$R{WYmWbNtOY{#19f`(O3u^7 z#VAy<&{|-3p0KQe?6$M1{y==0*c$tnf~>RHn%qtk)-Wi_wxaDdu|{WC%h-8%1Ebi> zc3o?X@GZP@XyNyakve0f&N1RhTEZIp)#h_iwbS`hirFY&ak+ghF-eUra%zi6KDsi= z?)eRrC%3Psxv={IE`7zqDTTpXoI5V>eboyP_2_!a*BG{mDRpnjbPqb5f48MB+M(Qk zk1nR(-*;~ZPs{WMkcrClhN2kPPhBS0Kbn5XPc&~pzb zAgiAYXDI()h-K&v`gd-(*ii&8I-ZBx&ev*l5lDS`n%A>pbdB^Yp6Rwhd%n zCYb*kV8=mEo&y;%*M<$mV74sScU%_!l%zE&3zA5#ELe)-vT$is7T$-iP^=|o;c}IQ zRK^>Fi)6eBPWXx2Yk#&lg`doBUJa>L+06|KL*&I@GT~#pxgKzgh^`N#5n*u$n-YUR zTQ|ilz!k6hmI`r$G%-|fM>~2)h4enm9DuhUkLKzRfO2XrcNo_M93jG8pa9xy`jvg` z)Vfn&wPpVp{k zC)B;X7T#-n-P-ELyKYGi7a$%hGnb4hAUDa$_5qwLi%@JBS)Um43A#uL`XTgsM}@4O z$G3NY;<^uDI0#IYgCx_h33K*O;`Hwa*O^B*~R>fi@Di}syT|e(h$BMiWE?#JX0k(6)_oz z@xd2tZHg3+DoNm^O1cu5jfH4#2K_3?es0fSvwvC)sh{9O`~&_k@>jO$C-_l_TWX^F z`m*qs_@-DCdhis(=^ESbXkhwh>%5fZsLnI)=fb_M=TMB~XJI*V{d*?9*hc&YIgqOxg2qM|uWayzC4Ua;G}4DFua=h(U1TJL zOpQdi1)sFTmMXB!L%5XylrK?@*9-_|<|5ou*kYhiW;epE1z^6$yzc^E#bJTWj1&WT zk4%ZNmzWYdlw`L?_dXy3-4-6DK0v^joZ1KxIkPWcD0&i~1C4je$e`lJQeZ63k^Z|i z0}?PuffN0|UP9McV0KPuYlNtORzFRmMl#Y*qwe%`MJ$|%jnvke?kYK1-L>yncb`x4 zGSOXWS59{=?zoP+4vwX}o8v2#1aU8uTltmf?v40joc!y&Mz;Zxv@6sXk$qFylv@oY zx#M=nVA1;~ff#YzUcuRi|H;#J>Z<7^&Y;ui9`Yy;^I+oq3DjcJ9;EMuivQiypE( zv!DAk#^nb!wLCP}+8_eED+Xq;_~5kKrv6J4R@q_;R_E^|>kb{LzI$)KzDzyYkV)`T zR!vZ^D9E73Pom^D)b3-+Y_H+s7dGmOjaq*?MUE4V6+nDPn>W>vbevL|s$OZWX&~_p zO`O`HyWLEfOAlmyOGX}XzU<&GVPdZ~1fyG#`l{bk{_X-K$=~^aoDE>jTr{U} zZLv^z$#iTtyfG#*U~GMmj}C7qgktJEG{3J957c*r9|lQ@-h-F_AyrgAkrmjFPm;AK z;ro9jGsA_j^6`+6`zXlpo;)yBtV3D0{k%>S5V5R71 z(6@1-^) zv1MvoaF_;>Wt`oMRoOdm&tj>RcO5>epWty?4AxSHm6PsLUiSCKtMfUieoBM^$ai@(_ew%SSuk`U$=*|5t%8 z=40Yok0XG6gunV@3+WoL)cjGXRM!9elJ_)>*!)Ug_17>q`lM1G z^UV14Q{whFJh;%(&BM6$FZA!{71lwnFPnY;Ek0%}oh6wEPvb86WA^+VPyhGke1_*d zMz`g4`eH(As9X5P{s;M}GjIO00Gj$Q15ocjhr794T6`8?mYA~t2S9c>Nhx)jQ5#TQ z6XET&pe^{0JlVJAJu>(V$}4jw&|n*QPM(H-sqFuWSHCqG9K`=KfO&2>V(?H5?J7nj zTw3Y|-v`a0Mk3`-&lVvu1=vVz4m|m3Y@4WrzqmLK# z@gk4h{P9q9(cVkKKHjU^;vaGb(H(nne%@XaO^5vxZ1+-@LlsKh{>yyOfe-%0n_&Y5 zd{ubW;pKwX7wA{=T_B1}p?iJ}t!&9F6XrF-3<%pZzouttWg`0Dpj82@u5Mq{R?sAZ zetDJ@egZGA$8@{U7xBOuOB14gLtbRmNZ}BMk|KS;_%-c9^XIPYKN6)Tm zjm8DseCPpGL+@DNah|CCL7_g;dCDG|`!CaZ;d)|4{x2Ush6HVX2uA9B#&2}(b&*%S zU*c&zPyyB89{3V|>dT9-)yrW`NiX5M>~lO@qhF_oE^+Aay4ZR*z>sDRrBk)s{jAsF zYKf>kg}dQEJ4Z4@cOpqUi&!@*4!SC%p17)I9=&D1L9cqhQPDT?aN|^i3u2|ujeaRV zZ5(cT;rDOk$EKLGY9@HeZ{bNBZHH*xT(y;NZz>OK<4%)9^VH@bjm%S5VwgD$=JG}5 zV-DZ?6@&G4)gY}*C#F*B^k$GW+0N{I<;+F13tNzPuPE=h0~$NXu0Nh7e-Ij66OG$@ znGE*Zh`;2w^BgQg1!d_FM-Jq7;`Yx&&i*WRLN)gP5-)ZBY}`#<&7~G=D3%+HD5sDp zmwv$GAZm71;X|XeFH7~_KZz)Nhn3$YvfVrwR9O#?I{rpqcK4JxoItD=KWvCHkI1BP z;UlXW72!5Pf^s^K#!kxvhmnirL5a{&t8NOGvBK$!iFrsd!I{xBct!a>chY>P(kyZU z$OnGwy6Ngs|8?UjGMY)BUF2uh=2a@+cY!bQ>ceA^r(%rLF_V1P-yI8C2-y`>vq@Z_A zyF|;s9Cu4pQORSLxJ>cDZjgdwJxD!*Bh z2!zmLW4*^aAB)?{3J0-S5Yu8~!FBbon4?oP8*t3;rvcHnG`2fjN&>;$!Dc4+eNEVT zpua>CUYf_av{k=GVbyW#5<;5%3J{~AlLtNS|6AbCfsc8`>Revor^JWljn951F2p-G zp`8h5=P_1yQZcN~MsB?>NY;m)OSqQFTRJ{o_X7G~JyzQ=PhusRw}&2g^6+Ol0tr)o zdZgi+%9EXm#>pyW-1!lCvuEw~{cpf$rQR%>&QDjq+F%g_p;x2fvo>G+gr($@Ab`ol z`D>G6hDsX-k2aLTWRg%RSJ#mT^wUX2f{cR3>Z9W73TdotqVI^{wH^i;Ii+=5=l4RZ ze|K@&(6d+;y3#Ox=@xoBZcK-J0WOx+o8C|HFU;`giG!vJb+XB4T|OhHPySV!d=Cie z#yU0*zO6pY3g(P$=v)f&_RJ=gj!mJW~UtOfphqTAfo?9IpF6kUt?ouW5~S1J`w z(VL4NlU@tPw8OR-2+_`Yyn2s!{vxO7&E5itwKY4qu8Wi07~5SR@I2m~G&kB?+giOY z94=BV<#&;`hF7JLw%P>Sg0ZV%pM4vOw3REnEz%Qmk+yn05Nm54?-ZO{<)%&|s;MR_ z$CJi^7PxU@lWY4lcYJn5Yy6EJOM9i_Erj)?BbUKU^ZLNdkNa^9Cl{XKzaK*C;H{tP zkB?#2KYf1IZ&F+uZ2}~!{uwj426Brv(oxD(*8^JtaNoY!ANrcKr_G5v zWl;|5dL&9&PxDwGukPltb$LZ|hL^59XUNs%O0bo~`DJVSSi5Z7!fE&B+hv!r`^voD zgi&L62yXUz+4J)}y(T1}@G}?9ENHam-&Pl24~j7$b+S0+V0}~IFIb>2+Ve~H{IWg2 z!qab&AQ88nk-Z`Bbg)FUI3xS3dJW&eQ)gr!se*B&(G`L-vh^D8d^}cBg<^j8eUQ=_ z*@Ekco*MykjWSQ$u2~GkkJn7>En{v*N4S%gFtnvsuPtw&Y4u;V^VEprPK^smlq2Em zblAu#P2h4~BX22sT|qV``t~{|sot+7EmKGpc1sfb556YvE=_es?Q z$2*_MC92NbM2V_%aMfyJ*=`rhR}2Q0A@8NildU&{-r@>ey$YQ1VexY*=cZ7#>OPNY z@cKc_*SNKQ(z0ui`wjdv;hT6$IgXYfA2zx|pd5N- zltZB=Do5EYcDZGY;|pO(tXPq=N%)SIu_F_3w?Bp^#PD4bxOsQ&f?*Q4szfQ7u0^2J zhFScrSf;Hyc~X?gllOwPe%^KRXd=;y`Zm#VGd9lwt&xd26u|Vy`zZef28({H~`?Juj%Yg@)1LYbV8ma)6cy|O~Rm-I|u zZF-Kk7~Z4ozhgqxb3o&y?0<@-Zr%2b##v+DrLQ^h*)sO${||I(#%j}bg~jk5({-z* zOV5F?C)^HRXHJ(1!u`KfubHc5`&oi{mKgyNZy9@k;_fwLG0#O38DQJhXl2Ii7P}f3XAN+QY4ASa%&N^DLep$;1^e2Q zZ^Ov^mAuWI=Xeb72nH{11Hm>jdw!p%|8MFh?DYQ?*H?X8W5c&YD(Ry$-0>Pz-I%X) z+nBG8m3+hMfjjXY_0PXUA~d0YmKy2UgL>!339yd9r3j}eD#hB=^aCCF5GzCoBxYvRTh);{1CEc z9~V^L70)fqP2G=YHb(IrSZvaiGc~`N1Ub2D)FErL4r)d{5 zm-e^*SK3wiOrCaE(Y@=mQ&Fb;TGE^pq9WX3t7tq`O9_NhSnyOwvA0#NoQ{531TGp$ zDlcaztg=EVULwzC*A}z8gDeH6YRqEgOhJlarJ+i9wk_5{`I^Z;(-xDuu4D&dd!RjN znwou+GO7Cr7uy|#-g3J7;h?!YLk_>-K0U<9jv}Qy;49J-Em9%W9nd>tZYa6LClO7hKGMwT_A!b6MpFk{iLVTm{Zz?qU;(oRAdMz1`Nef;rZd>`S4Jx^b=&Xs8h(nW>bB#hZBvMVZIFPdC-lmkSfNmm z%3c{w*%||?;=U^!)b%WWh}Xz38Soug5xe{y$dXlXBE_Ga6CJDYUO2Ht2anq7i{HYN z4aYFIEazhzA&MR0LThk#;_b#3RBadoS`jdwDP;ud0< zD7dW;Qo6fFa8;R-#n>LC`=oPAg<_=aUHuHK6jZAyTY|^nuxq?DUZF8)fL{O@RC$QL zAK_aA4h$RmC$S`;gEKPrgqN{d_+fNWUQNQiR-)(itH$yEQo+DH%?3~6NJ^Qeu#167 zLt{rHPZ&}J=8Z_t#_O78SeKKG-ccdFAyVF6SBd8kzi$ML%?Xm#ShmUbSCZ{6P-w4j ztM_)5aLuf#17*Uccs>K^NV;6iS(S9JFv>#cVotrsJAaoGZ@ssh#LLB;g0s5zE|Sp8 zi0uJ75BlEd<3N4fr;m~IxlzOa5l#fQ4&)`cwCEczrtB2`-JzTeEoLya-Uq?BH6n<- za4CNczTVG&5$@0PMlTJ1H10R~D_i29#zy8*gcj!OV->NIw}+9Y&Rmn4u26W}%JH+H zqBikT0>|Z;8>~UBt_&+6W%#dp4L`?IWthb|3&y5f$q|AwtXHNCE7Zzm_|HHyW%zLz zc@JfHa!xLhxPo`ji6uHJ!+(S)8(3wHg`92(;7@Y?LMrzmq^aV(7w@z<8!zSXZ+MHO zLUBGxQYjfPLQtIb8t?pltZycjqPI7sRGbA@ah6!d)HhspaaIoHin9SS#o4|m7H6A* zL^HM6^xYL{ktqgZrWpK%r@s`-9_zMsA}!UX1auu zQ`Hr|ECKNr{8ZdEd`#4B)mY2;XX-nt9ydQ4X>@z-LNmJmj_LN_L1pTwV;~&=xuaYu zSxp_K!07kOdJR9%Q~J%~x&>o5!$2_*0{zx2qu&ZOLBCP#ee8^z?TKHiT_$nwLA@tu zr1!ls;?f+c_lqzZskb?udcP#-FQwl5AyBFJ{=8H5HeSl%zwj0b1@&eb5D&%Ot|cwFSnvH5P}6kJZBeXQ@1S8hMEHj zy^2a)W`1%BxkC4@vnsy`P;5kt0OgD((mIUqG0lvxx>!oFa>ddBnPO?*6N{y0dEQld z%yif+-KLv#*am57+0sX~CR2>NI)2$i`q7+tZ6P2#PWLGs=K$AS&mq#R z2)+T1@A02l79R)3V!UiwR53~JpP$J}7u{VsZe{TmQ1s3AgeYeUNJOg82cJp5f5x|e zD!Sw!L{_WOcyOY7>KKUv1@hl|4Zq4$1@fcPy`LKqAt;b~jdwm9>z+bE>JaH3voL}; zGe>M3C=dKc(e@bda{(R!5PA41-z{D3H4SvE`n1?L!?_!C6PC(EZkjK@2>@Q68O~@{!^T?2Kl3PpOQc#>$q=y z2p5+F4}J$j47eq3q`w{r@P~ZI7HONRIf|B`HsFHUeOh`s?JX*V22gW%{)2_!LSfwg0%4~L8(6e~+{TApkAXJ>~mZwU|2#C+_IO zIeJEaj#h?UR`et*Fvf?a$NxHhyEc12OM;R;EuI8AaZk%agr&dQ!ushTUx zr4dwqLwDX>b9{GqS6B58t`V))Amh=k31RS_t{i-u7(RVw4!&&+pON>!T?(I`rN4aw z&(qlEWc240rI?Gddj?OMAJjds*g1>@%X5{(YErsXk?;gYP zp5^m{g5S-ZYHQ`;dnE9@{P&FEvl{aOJ|x8Rx4=*yzE=#N5b@rE&-_-+@oVP&?~}mu z3EMY@Phj-J2|Ul}eu6KC{#tqX{(^7&4G!4;FU8v_)d7O}dDwx8zbT9)IDZEv{!-Y% zf_?itIeLf0unC!SXbR6|&S5D$m-&YaeqWyU5eY2i;Yh(ggX}YDIx2ys{*I1eGxIW8 zk-#ZcnZkBV0-v6PADh6*?2P}9B=CIR9hboKg~1p6@I2qg$1qsT(mo-9=XoDW;CbGw z2|UmHi3vQ<`$-8r&-=*)}X@b4^P5kE5 z8YJ+1T8DvCUua!=TBn2nqdMhuhu1x^J7GWT%w`L({lWX#wH>X%sg4UXGk<*xuK#>@ zS8UPS3TymyeE<8_`iS-jZ&aw=)K%xj!sdnAt(<%2qO}SeQ(x6*; zvCB?nOYS*NID+|y3ww^R#Xv9rF*O4$bv<$F&XE5oaK=~U{DoxNLxh9tI=V(iP)BBZ zOlB}khKq3&X<$r9CW9-Ka8GK4SOiX5jax{y>TM2F(--jY?9kZDaJ~HsvySR+~~980vxh47ruF zNqc5{d}a3WpARl7rys{@eU-Yx$)vZz;uoHB`qn;Mt23)fVU^LiyuKfI{6`-RF1-Bd zPr2>mRrHoio`o@_&c`n9qr*OSbsv&y(c4WQvLy`m6$@JwYPXHc^e%E&gcb- z&3e`tZI&D|h+q&ExoRyB_g(!t8s1*RoI!@windrEqr!Kf*ku0JI%kg0w;i7ggh}~y z$4VQgJu#I@;>}kBFz!jHh~cY}F?u zn_2&HJZ)n<3FMBtE*C2du~@P-Trg1rPO#Hsc6EtCOp46~qGIgcRP>}!uq#*xGpZBW zYisb26=!!EXOXSC%s6|?aTeJs!JMtSL|B&e3t+a&L^mVm0YP@?u+G? z0wuO?Pkhn;J%S2n^hh2_-`#3Yv9M79V#$8fzUuyv z8@(J@LMMBc-Hg(#hVi22!mFbULujmQBav5MTmFs`1H`4iq~6V>xq6DFqZEEaY!B<{ zZQo%hE1wFcdNg6Yz>TA+eFe=GmX4BtO!VFv@0=TIj8{)#(}!rM?Xc@18QJ$Qh;xla z)fD!^Pm9T1c-Ib-u28D%FB~d_&v3hZKg^f#Y zS*YDEw$puuIlIJmy1j5SL|ZfQC+GV&gV>or%6%a(Y_laG_@X4kDu)FFZaKYuFX6kH zf~k*gZx}IoTQiaueuZyq!EwW;YrS=7YWvH8gBBVz<+d9Cbh5}6<~A^o{S^%{HTY-p z3crdknQX<|{}{<+(LPS^V<0iS-4`9es{7uqO6~RGjq(~Owu=Wwm$tq#R2w zgH&qP_9lKL;E86qkn8; zj)*hM$d7l4kj;mnNOn(v%SrJY83;>`83^oy(w(&r_8r@Ysfn&6_Cea5vkyi-whu?c zvFwBLJk$gCm*keNB=+G1d?jU>b0)tIvgAKO#=&zu&bvd&-_RLZLA6 z8{1@a1gx>u+t%1--7%hbMacoDckC~5XZ)$1A%A+u{t~zBs{Z)bIg4gz^$lMeP2e;4 zJuk*9kE8hv-i@jVV_W?uWdpR)m+d~@a@PExAg5qMDa__X_BFvef;JWy)f|(-QycwD z#2ne^$0cz${yK9}YvEGL*EG|8`C)EMOV=>JPw}#$R|^_E0dnL%9`S^XGH$%?6Xap_ zIFiykDkSIS^97v;1z0Uo)>wMYFBpQzBU({#LK9 z@M&KCKY=Zbpn(EGZjiAIeip zqc>kima@Z=%ROo=mns(KPw{q-_l5^bLe_rScWl2ZNggKlOF5mhUzX1=}e$of&yehqj+3tPM6yvWwV@AkMYh-@J|Eu2lf`<85bx39B0Y(1Ozyv3$- zEL6Yf7kRk{MC8=kk?Q_hQ!2fhxv0Hx6}GM2%EfiRyJrU$~L^+QWPx*5Mj1c)+)^K+kCXK6eZMhfY}y_ z5G+N}YrL~U_Sp$m3I(ZcA*H1#g10nuc%52cEbHQp5ToNNbJ#^OC@GqVO|jfy>17Jr z;7X8VycpCNMBMvpiF>VpwPoJ61a=v5*otdopJm>4^6vPjo){7T2gy$$gqC^hompUH z2|zGPKm^aMFEFY`ZQ}ZZ6cev6SQcfd+IWW|vK9OViq;oMq{;e%nVdm)I`|ID(|TAU zPfx)!d8$r@H-m+u^NK-(QWG@Vv&o*#_H40dt39XMa~e;-PXaofq-eFrDDO0T6$dp? zMX%v9Pp$SiUE+Dm#3KZ&J@gvyTrbXEg@V)>k`Ajq1ZU{SaUvePZ7q9KHW`+1ycs_m z#~G#r)6yl&S}1J%G=yysiTL5bv{CW1!uSz_1Jm@%Sq$eK*_tH~OR zz9C!#dQtRb&6$ecM$G~t$Qr#eS!2=XvnG=tM$^(VF+WnwD)K|7XWe{$NFGUk#CAPC z|LXngAylm=W~Lf{QL9ombVgxa)-iTjzO4l+nx9vuT|or4&c6CIMn0SemIK}GIh$uF zO2uFfPj@xZPhL;1#58Xfl|yYgmZIh+0q*C-pXyR-;nRx2+6nZeQXU#xaK&sSU7W>0 zgo?pBJTc?FyjEw%l{U=ye7%Ne@st_=m@;pEQD#sG%(z||GpjLI{cb<_+ z*HQ*|CBhA;Z}&w=Zc1;Lla}$Ezj#wJb1eJ&Q$6_scEoJ=ux!&ZDYI;W@r>_^t}pLSAazCWYzgEr2`vyp+lTbdm;e(LACkFluXm-eEURdAjb5ef1vJW% zb;TJ>y*I4pT5fOZOX$vP{4@2LbgSfr=9Ti*Stx<;w(_+Q6n617Sj8^7MdKR)TeQ`|Y`jJ>{bqDE?xg z_&7e^o}$N*+&uktC%4c_Ar`t{ui-wPve4%!e^h&OBs3r_v|gF)QK*R)dh7@PcLu#x zGpHgpIfKrIzXt|qz+j8C$7ot=8=Ugkq*M-3UWM-b42kF*5z&d-LXudbJFBtb1Ncj9 zc>>PLp=Vt?yYa2iZ@2du)JrQ=*# z&Vk)L{r8KJ5zMXC7pLQJ6{zY96E3IE>>}?@y3`jRmvptmuowuT`a~YVtSV9-Ih`xD=nsXaXEQ!ilWU2lPk)n zEXsGUDCJ7|e>7WExQ*R>v&G7rrDeSC4=Xn$($)jD=xSRIlhBvcGP2@uGj5C?a zY%wr!W8f&QLhXs_V??#^W7+liZ4KjH|3X=!5ka$wPLZ1{%>x-lrnBgh@-7rvCz-59 zksa0dpvbJrHbri6id-=%ICCkU{4=pn6ivsU;O(uhsWerUopEACsWLU+RLWplqv|d> zgL*2ZU@QI~L{--!Lj7Sqw2kwrW>3p z2D{?1%l`w4WA-b$s>{`Rebwhth|%5fl8jj~E+qxfe5=ekbD^nikH)PGuKAI*jYEx3 zS@n7l{s$z)=-W1G*3oX(g_^2kq4iWBYWzoc?6t-j-exOj297WSC*$=0fc|?ouS+5C6|noFpWEEMBkwZO zKM(5Hn`|sac$ysz0EpW4QR-a=3gR0({YykJQlC8l=jHg4le>D@2&adH$T z|9qw0Da6R0D~0|?nhQc&E;wb<`i}4{V65yD9n^?D?e5N``gk2xAP?+8|2W8b(b=C1 zpBd|dYQ?9nYz#(r~@M1p!&$ zgWvYXu?xzIhwZGG&w|$1`Iv?~_-_myCS364KMR3DrlI+^^7kQtc>(@YBG6bz@2HU8 z#b~&dt?qUv1dPZt5YfS>_VFAajM~`q#J_lWPZB5 zwIJVR$%s!Bng^H7T3hwGbD!4QR^f9s*k39D;i~9{eSCgYNe$zHLY&!1nI#Bd1wZ{lo;_jhdf>Qvs_-b z+L3_Z=32wNEBqJ{WQw_&rtYPOn7Czvyd$tP)vrL%`5LeLoA_M}z&ld2y!Y{FpH`XX zj<=g;=08I)s+?6R{6CzX2b`Tn_4jY?-E2?D-Mb|ugcR8DkVptc*j-uz(n6D>(xi(5 z!h?GSgy%lX3euZ&kZM4hGyz3G0TBdIiUk27h=^kOBO)qFS>NyP%v0v>ZX&$<**o(* zb7sz*IdkUB%$f4Ip&}JaigYCGJ@&Is6*W#Yp{3 za2Jo^8LE?VM>#l?hunB)9Zk6$pGm}|rzsbMbJWv3Xj5GQD&=BL{z%t5>D1Neu^Q2?ghf~vi-!w^5kWH#9UByVr%3U_7a;=5E%>*SSn7uZvK365^?&uEA z%SxcBkt;!We2${K5_A`X^HqXRph1d}0F~}yP5wyNdvzt~4$fBzx?Omsr`%(QA|0p8 zJ>i{JkjO|?%Nf{`Mf~~$+=3;uTZ`rzQD>|dM$#%6oKVfhr|8*JU^B!AE`zsV8yi?P z23H|jE^-RAv4%@&UDC~23{&Hd(`jTeF1SL1nuaKO@Qpl;x*=i?}V-2qK z@mbTmDUYot<8F7U%kkPYMs4}V)hju5I zdNuj?t!V%klA?E5z~BOE-3d;0Bjvbqs;LhLiLa^4Q_H|?xcE^b#wU}SqX8;L8*`WKL*WYOzD{jpl%YKU)sz30$ajM!0o`E9;Z)IG z-;ovwTBbn1?SCd!0U7tT{=%eH{eb4R9CW|U{x?Lp-k)%WR!kE^U&UeyBZ z;|BecREuYWzPze~C(!gY8*OMDU74xLt#Q$~SDslvx%D8esV(TXv@wzJl+E|C6rqr-c82K~Xx-<+U&i z)y6)o!Y0gHK&7-$kcTfKT=PlgxelG@70tH%)SPT+Pr~XO*gw?3p0GinfHnzZ&~+itEc}Yfa6M!sybO7jb@p<} zt(mK3oi)l@;!B8@P=+QpvAnEiRslj5YTSBNgZ{29!~!fZmjQmDQJ5}oyT`*(MA zDY!=-SYm}@kMPxoYk!N%8ewSD;tsE}pudocoN)mgr0G*>#14I?>PbvC2<#GbQcGqSDj?ah_OWsgHq^*M%7jPF$d zo0G`4E+QTA#mJTKMhg+qW#Bj2n-QPw=BYGyG`sCQi&aoHSVw$we6ylE;+u=XQ>9sYeQ+ivOD&{8~t%N zwWUSbs^=ibS4EIx2gHSX92b44@mInYi&%=epoJ4@iNrGF<2_1Dtx&O-gQ z^zSNET{GR>Jv@i`fb`S6)@$;&rOB7l2zrMF46Z=-VqxHRYQ0~{yCc%FSVtolS4>y? z;%~A%qjBiqJ%7uKqOvlI4c|(@ z6^uVrbXNqfU|gyq9MhsA2%sw%<$GGo%FyJr7FRHu)@shQRx<>aCstaPt!g+#X+lfB zZ>P0`5n;A(M>A?iOKbdaPD^VsxJI=6g#jDaEQ^-bVokm`qA^-p-7c#Hg`8Y>FXKYrRPxzldjx^4qdF6d?|K`SVm2GgHQsKys_HZN2Tyuky z%_NuAUb#KFh;xWTu&n`Cb@FAVC)NO!jlL6`--F81H4`UyT*ppOxD~<||G?-`gA8SP z>;DzYJ2DB_h5z00_gq@bQS|0UrucqK>0H4_8fRyP$n8y&%9D~$4k#vH1KLZIcYHM} zPY~_B&hBJcM>&4i7JudD{$h=@Y)+maHRHksD zJicf4GTEQv%wAJmc(1c_*;()_VWG#Bx;@hwuY&84Moemvh#y!9*(&p*1{25*A&3>x zOvkGwVAXkU)X#7&4X#d-sPwkZgc;7O7xj@2dAVHf2regfmYeJ>$}MlVBaL$;ud+tl z&b#V$D9uWxT=7skIjF9qy_zSkBpXQ9Qt}0bj6mz8mnO;8T88$;saFdB*_u3^bwNP0 zE+jNphV~m1{T)Sjb0q;yhp0@W`n>(eBz#v1oe@SrGs2WRoB^iOsqpUL^HgDK6ZcAO z#__D6XKv6?C?-uo7NauBo$l8JsH@yHFrRu0zFsd59;Xa}DW}vd>e}Fiq)68r{M^lK zZ>MyF*(jo7SgBfYC?08)To6 zmMu@u_bULhm8}=+b|XylSGLOCLkEzmUO?W<1w_D6t3-8rNb;U62X*n$vib0{vPya7 zlu<)s)LbzNvrNh;jR4E5lxIXX6f_JvH)5ZAq}9wAXFI&&km<02UNC~S4j+T8^29$K6s-D{zlT6Kb#?qF{-X`K)j7c-gGo3MMW@1d5 zRi5P;lV%VtIC&hR%Ci#Vl8c?bI-$3rSY@^+Z%z%DXD@FsCR?7V&~XQ2w?M^*7>rfs zI7#vHFVD#tmNv}uR@5{c21Y=dzEoC|mCWd~Ay#>{`niw?Y02k8H$yauZ)WJT6aKsS zzI|(bhE}ql9f0rK$)4f;0>Ter1e(U@wJ?Qw`3Vdh&LiBnd!^wQ67Ji*((vmDmtn`Z zd)>tl`#yf&+)Sh2F>|xWi_wn=FGi2zMvvu>Kh7T?=P|sV1{yA8ts=JpOkcQq;R;P{ zZ0_?DVllrz3PPCQzoB7#^&>Qb_-VuB+$VUBT`n%*Nd;J5o$)WTi0Z=l>s%B}Zsr!b z8^}8r6l2 zNg?sG29#?onOlsMGW-TTe0UStYR~64@$<=rii~;hIeE38;>x^tokj$wAs3uaBnb1K zypgVkbaJ6opt=QAI%1=6tQ*R=Ikvj!Oh^$`MoRw{6wFaM(l}p=BF|gG z;>Y;;Hh0|>ek-s$@HKb2ZGy9{r{TK4Sp4#J%t5sM!}xhRMvb5peAi`~+F`Kw*oHDU zu>3Vqa(j<#gq`4?X*%QuVCaw^krBdlxkRD5wi@m$0E&@wm+>463&(SD$C^wOEm8yr z@{j2JCJPO`!vim8p}xsP$T@(EyCM_6NJa;|DzEk;u5`eSlH^4qKN0{PAkUi+E7d3y zzboEI<3hD>8%}S_9H+fG`Ra=*r8y8E-9Ygi>$0<=I$qc`H{6&>t{W&OHkjX$n~Q+4 zR4gb)Dsob|b-`q32(?NuT(^hY3fB_Ir5Yn=qI;xq{r@Rv`d&uh z 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%0DExg*6@_bx0u$|ZB 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%0Dl3jXJY!75 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

    aR8p7guK-dl(b7R0U|nu)qe8Rr_`6!IYy^& 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ð-HK&N*Q5^OePrD$OuaWGQzkz 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

    01>!KCyy1_FDN(C{c|36ydSO^Y>C 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�QHv-t{cZY5>~Y;7vRfr-^T$9zHq4 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

    dm~T2^}6lAkzsbM7%4@FE5~KHQt~nH)9cF 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 z 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≪2) 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 Wjb;XC4I7_C(wnu