commit 1f5deedc7323783cb1d17c7a6958563a1401e357 Author: Mike Phares Date: Thu Jun 1 12:48:01 2023 -0700 Initial Commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6a59641 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,247 @@ +[*.cs] +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true +csharp_prefer_braces = false +csharp_prefer_simple_default_expression = true:warning +csharp_prefer_simple_using_statement = true:warning +csharp_prefer_static_local_function = true:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true +csharp_style_conditional_delegate_call = true +csharp_style_deconstructed_variable_declaration = false +csharp_style_expression_bodied_accessors = when_on_single_line:warning +csharp_style_expression_bodied_constructors = when_on_single_line:warning +csharp_style_expression_bodied_indexers = when_on_single_line:warning +csharp_style_expression_bodied_lambdas = when_on_single_line:warning +csharp_style_expression_bodied_local_functions = when_on_single_line:warning +csharp_style_expression_bodied_methods = when_on_single_line:warning +csharp_style_expression_bodied_operators = when_on_single_line:warning +csharp_style_expression_bodied_properties = when_on_single_line:warning +csharp_style_implicit_object_creation_when_type_is_apparent = true:warning +csharp_style_inlined_variable_declaration = false +csharp_style_namespace_declarations = file_scoped:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_not_pattern = true:warning +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_pattern_matching = true:warning +csharp_style_prefer_range_operator = true:warning +csharp_style_prefer_switch_expression = true:warning +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable:warning +csharp_style_unused_value_expression_statement_preference = discard_variable:warning +csharp_style_var_elsewhere = false:warning +csharp_style_var_for_built_in_types = false:warning +csharp_style_var_when_type_is_apparent = false:warning +csharp_using_directive_placement = outside_namespace +dotnet_code_quality_unused_parameters = all +dotnet_code_quality_unused_parameters = non_public # IDE0060: Remove unused parameter +dotnet_code_quality.CAXXXX.api_surface = private, internal +dotnet_diagnostic.CA1825.severity = warning # CA1823: Avoid zero-length array allocations +dotnet_diagnostic.CA1829.severity = warning # CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1834.severity = warning # CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.IDE0001.severity = warning # IDE0001: Simplify name +dotnet_diagnostic.IDE0002.severity = warning # Simplify (member access) - System.Version.Equals("1", "2"); Version.Equals("1", "2"); +dotnet_diagnostic.IDE0004.severity = warning # IDE0004: Cast is redundant. +dotnet_diagnostic.IDE0005.severity = warning # Using directive is unnecessary +dotnet_diagnostic.IDE0031.severity = warning # Use null propagation (IDE0031) +dotnet_diagnostic.IDE0047.severity = warning # IDE0047: Parentheses can be removed +dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references (IDE0049) +dotnet_diagnostic.IDE0060.severity = warning # IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0270.severity = warning # IDE0270: Null check can be simplified +dotnet_naming_rule.abstract_method_should_be_pascal_case.severity = warning +dotnet_naming_rule.abstract_method_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.abstract_method_should_be_pascal_case.symbols = abstract_method +dotnet_naming_rule.class_should_be_pascal_case.severity = warning +dotnet_naming_rule.class_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.class_should_be_pascal_case.symbols = class +dotnet_naming_rule.delegate_should_be_pascal_case.severity = warning +dotnet_naming_rule.delegate_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.delegate_should_be_pascal_case.symbols = delegate +dotnet_naming_rule.enum_should_be_pascal_case.severity = warning +dotnet_naming_rule.enum_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.enum_should_be_pascal_case.symbols = enum +dotnet_naming_rule.event_should_be_pascal_case.severity = warning +dotnet_naming_rule.event_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.event_should_be_pascal_case.symbols = event +dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.method_should_be_pascal_case.severity = warning +dotnet_naming_rule.method_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.method_should_be_pascal_case.symbols = method +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.private_method_should_be_pascal_case.severity = warning +dotnet_naming_rule.private_method_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.private_method_should_be_pascal_case.symbols = private_method +dotnet_naming_rule.private_or_internal_field_should_be_private_of_internal_field.severity = warning +dotnet_naming_rule.private_or_internal_field_should_be_private_of_internal_field.style = private_of_internal_field +dotnet_naming_rule.private_or_internal_field_should_be_private_of_internal_field.symbols = private_or_internal_field +dotnet_naming_rule.private_or_internal_static_field_should_be_private_of_internal_field.severity = warning +dotnet_naming_rule.private_or_internal_static_field_should_be_private_of_internal_field.style = private_of_internal_field +dotnet_naming_rule.private_or_internal_static_field_should_be_private_of_internal_field.symbols = private_or_internal_static_field +dotnet_naming_rule.property_should_be_pascal_case.severity = warning +dotnet_naming_rule.property_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.property_should_be_pascal_case.symbols = property +dotnet_naming_rule.public_or_protected_field_should_be_private_of_internal_field.severity = warning +dotnet_naming_rule.public_or_protected_field_should_be_private_of_internal_field.style = private_of_internal_field +dotnet_naming_rule.public_or_protected_field_should_be_private_of_internal_field.symbols = public_or_protected_field +dotnet_naming_rule.static_field_should_be_pascal_case.severity = warning +dotnet_naming_rule.static_field_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.static_field_should_be_pascal_case.symbols = static_field +dotnet_naming_rule.static_method_should_be_pascal_case.severity = warning +dotnet_naming_rule.static_method_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.static_method_should_be_pascal_case.symbols = static_method +dotnet_naming_rule.struct_should_be_pascal_case.severity = warning +dotnet_naming_rule.struct_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.struct_should_be_pascal_case.symbols = struct +dotnet_naming_rule.types_should_be_pascal_case.severity = warning +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_style.begins_with_i.capitalization = pascal_case +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.private_of_internal_field.capitalization = pascal_case +dotnet_naming_style.private_of_internal_field.required_prefix = _ +dotnet_naming_style.private_of_internal_field.required_suffix = +dotnet_naming_style.private_of_internal_field.word_separator = +dotnet_naming_symbols.abstract_method.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.abstract_method.applicable_kinds = method +dotnet_naming_symbols.abstract_method.required_modifiers = abstract +dotnet_naming_symbols.class.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.class.applicable_kinds = class +dotnet_naming_symbols.class.required_modifiers = +dotnet_naming_symbols.delegate.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.delegate.applicable_kinds = delegate +dotnet_naming_symbols.delegate.required_modifiers = +dotnet_naming_symbols.enum.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.enum.applicable_kinds = enum +dotnet_naming_symbols.enum.required_modifiers = +dotnet_naming_symbols.event.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.event.applicable_kinds = event +dotnet_naming_symbols.event.required_modifiers = +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.required_modifiers = +dotnet_naming_symbols.method.applicable_accessibilities = public +dotnet_naming_symbols.method.applicable_kinds = method +dotnet_naming_symbols.method.required_modifiers = +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.private_method.applicable_accessibilities = private +dotnet_naming_symbols.private_method.applicable_kinds = method +dotnet_naming_symbols.private_method.required_modifiers = +dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_field.required_modifiers = +dotnet_naming_symbols.private_or_internal_static_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_static_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_static_field.required_modifiers = static +dotnet_naming_symbols.property.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.property.applicable_kinds = property +dotnet_naming_symbols.property.required_modifiers = +dotnet_naming_symbols.public_or_protected_field.applicable_accessibilities = public, protected +dotnet_naming_symbols.public_or_protected_field.applicable_kinds = field +dotnet_naming_symbols.public_or_protected_field.required_modifiers = +dotnet_naming_symbols.static_field.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.static_field.applicable_kinds = field +dotnet_naming_symbols.static_field.required_modifiers = static +dotnet_naming_symbols.static_method.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.static_method.applicable_kinds = method +dotnet_naming_symbols.static_method.required_modifiers = static +dotnet_naming_symbols.struct.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.struct.applicable_kinds = struct +dotnet_naming_symbols.struct.required_modifiers = +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.required_modifiers = +dotnet_remove_unnecessary_suppression_exclusions = 0 +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +dotnet_style_allow_multiple_blank_lines_experimental = false:warning +dotnet_style_allow_statement_immediately_after_block_experimental = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true:warning +dotnet_style_object_initializer = true:warning +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_compound_assignment = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = false +dotnet_style_prefer_conditional_expression_over_return = false +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning +dotnet_style_prefer_simplified_boolean_expressions = true:warning +dotnet_style_prefer_simplified_interpolation = true +dotnet_style_qualification_for_event = false:error +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false:error +dotnet_style_qualification_for_property = false:error +dotnet_style_readonly_field = true:warning +dotnet_style_require_accessibility_modifiers = for_non_interface_members +end_of_line = crlf +file_header_template = unset +indent_size = 4 +indent_style = space +insert_final_newline = false +root = true +tab_width = 4 +# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1822 +# https://github.com/dotnet/aspnetcore/blob/main/.editorconfig +# https://github.com/dotnet/project-system/blob/main/.editorconfig \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e5ca0f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,343 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# 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/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# 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 + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +# **/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.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 + +# Visual Studio Trace Files +*.e2e + +# 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 + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# 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 +# Note: 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 +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable 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 +*.appx + +# 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 +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# 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 +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# 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 + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +## +## Visual Studio Code +## +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/thunder-tests/ + +# Libman.json +/wwwroot/lib/* \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..26d463a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,43 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (web) - Server", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "buildServer", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/Server/bin/Debug/net7.0/Barcode.Host.Server.dll", + "args": [], + "cwd": "${workspaceFolder}/Server", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)", + "uriFormat": "%s/swagger/index.html" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Server/Views" + } + }, + { + "name": ".NET Core Attach - Server", + "type": "coreclr", + "request": "attach", + "preLaunchTask": "watchServer", + "processName": "Barcode.Host.Server.exe" + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8d9e274 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "cSpell.enabled": false, + "files.exclude": { + "**/.git": false + }, + "thunder-client.saveToWorkspace": true, + "thunder-client.workspaceRelativePath": ".vscode", + "coverage-gutters.coverageBaseDir": "../.vscode/TestResults/*" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..05606b3 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,170 @@ +{ + "version": "2.0.0", + "options": { + "env": { + "serverUserSecretsId": "02dce973-df1d-4325-962a-ed549af8d4c5" + } + }, + "tasks": [ + { + "label": "userSecretsInit", + "command": "dotnet", + "type": "process", + "args": [ + "user-secrets", + "-p", + "${workspaceFolder}/Server/Barcode.Host.Server.csproj", + "init" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "userSecretsSet", + "command": "dotnet", + "type": "process", + "args": [ + "user-secrets", + "-p", + "${workspaceFolder}/Server/Barcode.Host.Server.csproj", + "set", + "asdf", + "123" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "userSecretsMkLink", + "command": "cmd", + "type": "shell", + "args": [ + "/c", + "mklink", + "/J", + ".vscode\\UserSecrets", + "${userHome}\\AppData\\Roaming\\Microsoft\\UserSecrets\\$env:serverUserSecretsId" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "buildServer", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/Server/Barcode.Host.Server.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publishServer", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/Server/Barcode.Host.Server.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "testDebug", + "command": "dotnet", + "type": "process", + "args": [ + "test", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "testRelease", + "command": "dotnet", + "type": "process", + "args": [ + "test", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "-c", + "Release" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "format", + "command": "dotnet", + "type": "process", + "args": [ + "format", + "--report", + ".vscode", + "--verbosity", + "detailed", + "--severity", + "warn" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "old-watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/Server/Barcode.Host.Server.csproj" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watchServer", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "--launch-profile", + "http", + "run", + "--project", + "${workspaceFolder}/Server/Barcode.Host.Server.csproj", + "--verbose" + ], + "isBackground": true, + "dependsOn": [ + "build" + ], + "problemMatcher": { + "fileLocation": "relative", + "pattern": { + "regexp": "^([^\\s].*)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$", + "file": 1, + "location": 2, + "severity": 3, + "code": 4, + "message": 5 + }, + "background": { + "activeOnStart": true, + "beginsPattern": "^.*Shutdown requested.*", + "endsPattern": "^.*Application started.*" + } + } + }, + { + "label": "PowerShell Force Clean", + "type": "shell", + "command": "& Get-ChildItem . -include bin,obj -Recurse | foreach ($_) { remove-item $_.fullname -Force -Recurse }", + "problemMatcher": [] + }, + { + "label": "PowerShell Clean TestResults", + "type": "shell", + "command": "& Get-ChildItem . -include TestResults -Recurse | foreach ($_) { remove-item $_.fullname -Force -Recurse }", + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/Barcode-Host.sln b/Barcode-Host.sln new file mode 100644 index 0000000..662ced0 --- /dev/null +++ b/Barcode-Host.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Barcode.Host.Server.csproj", "{8D12E061-2AE3-4008-859A-1B5BEA41D553}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "Shared\Barcode.Host.Shared.csproj", "{CBFA9393-988A-4D35-9B7F-BF4ABD355712}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server.Tests", "Server.Tests\Barcode.Host.Server.Tests.csproj", "{505C7449-F5BC-4A7A-8A89-4863C0258697}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8D12E061-2AE3-4008-859A-1B5BEA41D553}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D12E061-2AE3-4008-859A-1B5BEA41D553}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D12E061-2AE3-4008-859A-1B5BEA41D553}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D12E061-2AE3-4008-859A-1B5BEA41D553}.Release|Any CPU.Build.0 = Release|Any CPU + {CBFA9393-988A-4D35-9B7F-BF4ABD355712}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CBFA9393-988A-4D35-9B7F-BF4ABD355712}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CBFA9393-988A-4D35-9B7F-BF4ABD355712}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CBFA9393-988A-4D35-9B7F-BF4ABD355712}.Release|Any CPU.Build.0 = Release|Any CPU + {505C7449-F5BC-4A7A-8A89-4863C0258697}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {505C7449-F5BC-4A7A-8A89-4863C0258697}.Debug|Any CPU.Build.0 = Debug|Any CPU + {505C7449-F5BC-4A7A-8A89-4863C0258697}.Release|Any CPU.ActiveCfg = Release|Any CPU + {505C7449-F5BC-4A7A-8A89-4863C0258697}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Server.Tests/Barcode.Host.Server.Tests.csproj b/Server.Tests/Barcode.Host.Server.Tests.csproj new file mode 100644 index 0000000..a27f474 --- /dev/null +++ b/Server.Tests/Barcode.Host.Server.Tests.csproj @@ -0,0 +1,53 @@ + + + net7.0 + enable + enable + + + trx + XPlat Code Coverage + ../.vscode/TestResults + + + true + true + true + + + Windows + + + OSX + + + Linux + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + \ No newline at end of file diff --git a/Server.Tests/Class1.cs b/Server.Tests/Class1.cs new file mode 100644 index 0000000..e4ca7ff --- /dev/null +++ b/Server.Tests/Class1.cs @@ -0,0 +1,5 @@ +namespace Server.Tests; +public class Class1 +{ + +} \ No newline at end of file diff --git a/Server/Barcode.Host.Server.csproj b/Server/Barcode.Host.Server.csproj new file mode 100644 index 0000000..140bdf2 --- /dev/null +++ b/Server/Barcode.Host.Server.csproj @@ -0,0 +1,39 @@ + + + net7.0 + enable + enable + Exe + win-x64;linux-x64 + 02dce973-df1d-4325-962a-ed549af8d4c5 + + + true + true + true + + + Windows + + + OSX + + + Linux + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Server/Models/AppSettings.cs b/Server/Models/AppSettings.cs new file mode 100644 index 0000000..168ca46 --- /dev/null +++ b/Server/Models/AppSettings.cs @@ -0,0 +1,33 @@ +using System.Text.Json; + +namespace Barcode.Host.Server.Models; + +public record AppSettings(string ApiExportPath, + string ApiLoggingContentTypes, + string ApiLoggingPathPrefixes, + string ApiLogPath, + string ApiUrl, + string AttachmentPath, + string BuildNumber, + string Company, + string ConnectionString, + string GitCommitSeven, + string InboundApiAllowedIPList, + bool IsDevelopment, + bool IsStaging, + string MockRoot, + string MonAResource, + string MonASite, + string OI2SqlConnectionString, + string OIExportPath, + string URLs, + string WorkingDirectoryName) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); + return result; + } + +} \ No newline at end of file diff --git a/Server/Models/Binder/AppSettings.cs b/Server/Models/Binder/AppSettings.cs new file mode 100644 index 0000000..9011f3b --- /dev/null +++ b/Server/Models/Binder/AppSettings.cs @@ -0,0 +1,118 @@ +using Microsoft.Extensions.Configuration; +using System.ComponentModel.DataAnnotations; +using System.Text.Json; + +namespace Barcode.Host.Server.Models.Binder; + +public class AppSettings +{ + +#nullable disable + + [Display(Name = "Api Export Path"), Required] public string ApiExportPath { get; set; } + [Display(Name = "Api Logging Content Types"), Required] public string ApiLoggingContentTypes { get; set; } + [Display(Name = "Api Logging Path Prefixes"), Required] public string ApiLoggingPathPrefixes { get; set; } + [Display(Name = "Api Log Path"), Required] public string ApiLogPath { get; set; } + [Display(Name = "Api URL"), Required] public string ApiUrl { get; set; } + [Display(Name = "Attachment Path"), Required] public string AttachmentPath { get; set; } + [Display(Name = "Build Number"), Required] public string BuildNumber { get; set; } + [Display(Name = "Company"), Required] public string Company { get; set; } + [Display(Name = "Connection String"), Required] public string ConnectionString { get; set; } + [Display(Name = "Git Commit Seven"), Required] public string GitCommitSeven { get; set; } + [Display(Name = "Inbound Api Allowed IP List"), Required] public string InboundApiAllowedIPList { get; set; } + [Display(Name = "Is Development"), Required] public bool? IsDevelopment { get; set; } + [Display(Name = "Is Staging"), Required] public bool? IsStaging { get; set; } + [Display(Name = "Mock Root"), Required] public string MockRoot { get; set; } + [Display(Name = "MonA Resource"), Required] public string MonAResource { get; set; } + [Display(Name = "MonA Site"), Required] public string MonASite { get; set; } + [Display(Name = "Oi 2 Sql Connection String"), Required] public string Oi2SqlConnectionString { get; set; } + [Display(Name = "OI Export Path"), Required] public string OIExportPath { get; set; } + [Display(Name = "URLs"), Required] public string URLs { get; set; } + [Display(Name = "Working Directory Name"), Required] public string WorkingDirectoryName { get; set; } + +#nullable restore + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, new JsonSerializerOptions() { WriteIndented = true }); + return result; + } + + private static Models.AppSettings Get(AppSettings? appSettings) + { + Models.AppSettings result; + if (appSettings is null) + throw new NullReferenceException(nameof(appSettings)); + if (appSettings.ApiExportPath is null) + throw new NullReferenceException(nameof(ApiExportPath)); + if (appSettings.ApiLoggingContentTypes is null) + throw new NullReferenceException(nameof(ApiLoggingContentTypes)); + if (appSettings.ApiLoggingPathPrefixes is null) + throw new NullReferenceException(nameof(ApiLoggingPathPrefixes)); + if (appSettings.ApiLogPath is null) + throw new NullReferenceException(nameof(ApiLogPath)); + if (appSettings.ApiUrl is null) + throw new NullReferenceException(nameof(ApiUrl)); + if (appSettings.AttachmentPath is null) + throw new NullReferenceException(nameof(AttachmentPath)); + if (appSettings.BuildNumber is null) + throw new NullReferenceException(nameof(BuildNumber)); + if (appSettings.Company is null) + throw new NullReferenceException(nameof(Company)); + if (appSettings.ConnectionString is null) + throw new NullReferenceException(nameof(ConnectionString)); + if (appSettings.GitCommitSeven is null) + throw new NullReferenceException(nameof(GitCommitSeven)); + if (appSettings.InboundApiAllowedIPList is null) + throw new NullReferenceException(nameof(InboundApiAllowedIPList)); + if (appSettings.IsDevelopment is null) + throw new NullReferenceException(nameof(IsDevelopment)); + if (appSettings.IsStaging is null) + throw new NullReferenceException(nameof(IsStaging)); + if (appSettings.MockRoot is null) + throw new NullReferenceException(nameof(MockRoot)); + if (appSettings.MonAResource is null) + throw new NullReferenceException(nameof(MonAResource)); + if (appSettings.MonASite is null) + throw new NullReferenceException(nameof(MonASite)); + if (appSettings.Oi2SqlConnectionString is null) + throw new NullReferenceException(nameof(Oi2SqlConnectionString)); + if (appSettings.OIExportPath is null) + throw new NullReferenceException(nameof(OIExportPath)); + if (appSettings.URLs is null) + throw new NullReferenceException(nameof(URLs)); + if (appSettings.WorkingDirectoryName is null) + throw new NullReferenceException(nameof(WorkingDirectoryName)); + result = new( + appSettings.ApiExportPath, + appSettings.ApiLoggingContentTypes, + appSettings.ApiLoggingPathPrefixes, + appSettings.ApiLogPath, + appSettings.ApiUrl, + appSettings.AttachmentPath, + appSettings.BuildNumber, + appSettings.Company, + appSettings.ConnectionString, + appSettings.GitCommitSeven, + appSettings.InboundApiAllowedIPList, + appSettings.IsDevelopment.Value, + appSettings.IsStaging.Value, + appSettings.MockRoot, + appSettings.MonAResource, + appSettings.MonASite, + appSettings.Oi2SqlConnectionString, + appSettings.OIExportPath, + appSettings.URLs, + appSettings.WorkingDirectoryName); + return result; + } + + public static Models.AppSettings Get(IConfigurationRoot configurationRoot) + { + Models.AppSettings result; + AppSettings? appSettings = configurationRoot.Get(); + result = Get(appSettings); + return result; + } + +} \ No newline at end of file diff --git a/Server/Program.cs b/Server/Program.cs new file mode 100644 index 0000000..04b3ded --- /dev/null +++ b/Server/Program.cs @@ -0,0 +1,106 @@ +using Barcode.Host.Server.Models; +using Barcode.Host.Shared.Models; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Serilog; +using System.Reflection; + +namespace Barcode.Host.Server; + +public class Program +{ + + private static (string, WebApplicationOptions) Get(string[] args) + { + Assembly assembly = Assembly.GetExecutingAssembly(); + string? assemblyName = assembly.GetName()?.Name; + if (string.IsNullOrEmpty(assemblyName)) + throw new Exception(); + string baseAssemblyName = assemblyName.Split('.')[0]; + string webRootPath = Path.Combine(AppContext.BaseDirectory.Split(baseAssemblyName)[0], baseAssemblyName, "wwwroot"); + if (!Directory.Exists(webRootPath)) + webRootPath = string.Empty; + WebApplicationOptions webApplicationOptions = new() + { + Args = args, + ContentRootPath = AppContext.BaseDirectory, + WebRootPath = webRootPath + }; + return new(assemblyName, webApplicationOptions); + } + + public static int Main(string[] args) + { + LoggerConfiguration loggerConfiguration = new(); + (string assemblyName, WebApplicationOptions _) = Get(args); + WebApplicationBuilder webApplicationBuilder = WebApplication.CreateBuilder(args); + _ = webApplicationBuilder.Configuration.AddUserSecrets(); + AppSettings appSettings = Models.Binder.AppSettings.Get(webApplicationBuilder.Configuration); + if (string.IsNullOrEmpty(appSettings.WorkingDirectoryName)) + throw new Exception("Working directory name must have a value!"); + string workingDirectory = IWorkingDirectory.GetWorkingDirectory(assemblyName, appSettings.WorkingDirectoryName); + Environment.SetEnvironmentVariable(nameof(workingDirectory), workingDirectory); + _ = ConfigurationLoggerConfigurationExtensions.Configuration(loggerConfiguration.ReadFrom, webApplicationBuilder.Configuration); + _ = SerilogHostBuilderExtensions.UseSerilog(webApplicationBuilder.Host); + Log.Logger = loggerConfiguration.CreateLogger(); + ILogger log = Log.ForContext(); + try + { + _ = webApplicationBuilder.Services.AddMemoryCache(); + _ = webApplicationBuilder.Services.Configure(options => options.SuppressModelStateInvalidFilter = true); + _ = webApplicationBuilder.Services.AddControllersWithViews(); + _ = webApplicationBuilder.Services.AddDistributedMemoryCache(); + + _ = webApplicationBuilder.Services.AddSingleton(_ => appSettings); + + _ = webApplicationBuilder.Services.AddSwaggerGen(); + _ = webApplicationBuilder.Services.AddSession(sessionOptions => + { + sessionOptions.IdleTimeout = TimeSpan.FromSeconds(2000); + sessionOptions.Cookie.HttpOnly = true; + sessionOptions.Cookie.IsEssential = true; + } + ); + WebApplication webApplication = webApplicationBuilder.Build(); + _ = webApplication.UseCors(corsPolicyBuilder => corsPolicyBuilder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); + if (!webApplicationBuilder.Environment.IsDevelopment()) + { + _ = webApplication.UseExceptionHandler("/Error"); + _ = webApplication.UseHttpsRedirection(); + _ = webApplication.UseHsts(); + } + else + { + if (string.IsNullOrEmpty(appSettings.URLs)) + { + Environment.ExitCode = -1; + webApplication.Lifetime.StopApplication(); + } + _ = webApplication.UseSwagger(); + _ = webApplication.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Server V1")); + } + _ = webApplication.Lifetime.ApplicationStopped.Register(Log.CloseAndFlush); + _ = SerilogApplicationBuilderExtensions.UseSerilogRequestLogging(webApplication); + _ = webApplication.UseFileServer(enableDirectoryBrowsing: true); + _ = webApplication.UseStaticFiles(); + _ = webApplication.UseSession(); + _ = webApplication.MapControllers(); + log.Information("Starting Web Application"); + webApplication.Run(); + return 0; + } + catch (Exception ex) + { + log.Fatal(ex, "Host terminated unexpectedly"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + +} \ No newline at end of file diff --git a/Server/Properties/launchSettings.json b/Server/Properties/launchSettings.json new file mode 100644 index 0000000..96ca44f --- /dev/null +++ b/Server/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5126", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7130;http://localhost:5126", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https-prod": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7130;http://localhost:5126", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Production" + } + } + } +} diff --git a/Server/appsettings.Development.json b/Server/appsettings.Development.json new file mode 100644 index 0000000..67028c2 --- /dev/null +++ b/Server/appsettings.Development.json @@ -0,0 +1,47 @@ +{ + "ApiExportPath": "\\\\messdv002.na.infineon.com\\Candela", + "ApiUrl": "~/api", + "ConnectionString": "Data Source=MESSAD1001\\TEST1,59583;Integrated Security=True;Initial Catalog=Metrology;", + "IsDevelopment": true, + "MockRoot": "", + "MonAResource": "OI_Metrology_Viewer_IFX", + "Oi2SqlConnectionString": "Data Source=MESSAD1001\\TEST1,59583;Initial Catalog=LSL2SQL;Persist Security Info=True;User ID=srpadmin;Password=0okm9ijn;", + "Serilog": { + "Using": [ + "Serilog.Sinks.Console", + "Serilog.Sinks.File" + ], + "MinimumLevel": "Debug", + "WriteTo": [ + { + "Name": "Debug", + "Args": { + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}.{MethodName}) ({InstanceId}) ({RemoteIpAddress}) {Message}{NewLine}{Exception}" + } + }, + { + "Name": "Console", + "Args": { + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}.{MethodName}) ({InstanceId}) ({RemoteIpAddress}) {Message}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "%workingDirectory% - Log/log-.txt", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}.{MethodName}) ({InstanceId}) ({RemoteIpAddress}) {Message}{NewLine}{Exception}", + "rollingInterval": "Hour" + } + } + ], + "Enrich": [ + "FromLogContext", + "WithMachineName", + "WithThreadId" + ], + "Properties": { + "Application": "Sample" + } + }, + "URLs": "https://localhost:7130;http://localhost:5126" +} \ No newline at end of file diff --git a/Server/appsettings.json b/Server/appsettings.json new file mode 100644 index 0000000..7d2ecc8 --- /dev/null +++ b/Server/appsettings.json @@ -0,0 +1,68 @@ +{ + "AllowedHosts": "*", + "ApiExportPath": "\\\\messv02ecc1.ec.local\\EC_Metrology_Si", + "ApiLoggingContentTypes": "application/json", + "ApiLoggingPathPrefixes": "/api/inbound", + "ApiUrl": "~/api", + "ApiLogPath": "D:\\Metrology\\MetrologyAPILogs", + "AttachmentPath": "\\\\messv02ecc1.ec.local\\EC_Metrology_Si\\MetrologyAttachments", + "BuildNumber": "1", + "Company": "Infineon Technologies Americas Corp.", + "ConnectionString": "Data Source=messv01ec.ec.local\\PROD1,53959;Integrated Security=True;Initial Catalog=Metrology;", + "GitCommitSeven": "1234567", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Log4netProvider": "Debug", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "InboundApiAllowedIPList": "", + "IsDevelopment": false, + "IsStaging": false, + "MockRoot": "", + "MonAResource": "OI_Metrology_Viewer_EC", + "MonASite": "auc", + "Oi2SqlConnectionString": "Data Source=messv01ec.ec.local\\PROD1,53959;Initial Catalog=LSL2SQL;Persist Security Info=True;User ID=srpadmin;Password=0okm9ijn;", + "OIExportPath": "\\\\openinsight-db-srv.na.infineon.com\\apps\\Metrology\\Data", + "Serilog": { + "Using": [ + "Serilog.Sinks.Console", + "Serilog.Sinks.File" + ], + "MinimumLevel": "Information", + "WriteTo": [ + { + "Name": "Debug", + "Args": { + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}.{MethodName}) ({InstanceId}) ({RemoteIpAddress}) {Message}{NewLine}{Exception}" + } + }, + { + "Name": "Console", + "Args": { + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}.{MethodName}) ({InstanceId}) ({RemoteIpAddress}) {Message}{NewLine}{Exception}" + } + }, + { + "Name": "File", + "Args": { + "path": "%workingDirectory% - Log/log-.txt", + "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level}] ({SourceContext}.{MethodName}) ({InstanceId}) ({RemoteIpAddress}) {Message}{NewLine}{Exception}", + "rollingInterval": "Hour" + } + } + ], + "Enrich": [ + "FromLogContext", + "WithMachineName", + "WithThreadId" + ], + "Properties": { + "Application": "Sample" + } + }, + "URLs": "http://localhost:5002;", + "WorkingDirectoryName": "IFXApps" +} \ No newline at end of file diff --git a/Shared/Barcode.Host.Shared.csproj b/Shared/Barcode.Host.Shared.csproj new file mode 100644 index 0000000..1c87be8 --- /dev/null +++ b/Shared/Barcode.Host.Shared.csproj @@ -0,0 +1,28 @@ + + + net7.0 + enable + enable + + + true + true + true + + + Windows + + + OSX + + + Linux + + + + + + + + + \ No newline at end of file diff --git a/Shared/KeyboardMouse/Abstract/IAggregateInputReader.cs b/Shared/KeyboardMouse/Abstract/IAggregateInputReader.cs new file mode 100644 index 0000000..cf20ae6 --- /dev/null +++ b/Shared/KeyboardMouse/Abstract/IAggregateInputReader.cs @@ -0,0 +1,6 @@ +namespace Barcode.Host.Shared.KeyboardMouse.Abstract; + +public interface IAggregateInputReader +{ + event InputReader.RaiseKeyPress OnKeyPress; +} \ No newline at end of file diff --git a/Shared/KeyboardMouse/AggregateInputReader.cs b/Shared/KeyboardMouse/AggregateInputReader.cs new file mode 100644 index 0000000..204c863 --- /dev/null +++ b/Shared/KeyboardMouse/AggregateInputReader.cs @@ -0,0 +1,72 @@ +using Barcode.Host.Shared.KeyboardMouse.Abstract; +using Microsoft.Extensions.Logging; + +namespace Barcode.Host.Shared.KeyboardMouse; + +public class AggregateInputReader : IDisposable, IAggregateInputReader +{ + private readonly ILogger _InputReaderLogger; + + private Dictionary? _Readers = new(); + + public event InputReader.RaiseKeyPress? OnKeyPress; + + public AggregateInputReader(ILogger inputReaderLogger) + { + _InputReaderLogger = inputReaderLogger; + + System.Timers.Timer timer = new() + { + Interval = 10 * 1000, + Enabled = true + }; + timer.Elapsed += (_, _) => Scan(); + timer.Start(); + } + + private void ReaderOnOnKeyPress(KeyPressEvent e) => OnKeyPress?.Invoke(e); + + private void Scan() + { + string[] files = Directory.GetFiles("/dev/input/", "event*"); + + foreach (string file in files) + { + if (_Readers is not null && _Readers.ContainsKey(file)) + { + continue; + } + + InputReader reader = new(file, _InputReaderLogger); + + reader.OnKeyPress += ReaderOnOnKeyPress; + + _Readers?.Add(file, reader); + } + + IEnumerable? deadReaders = _Readers?.Values.Where(r => r.Faulted); + if (deadReaders is not null) + { + foreach (InputReader? dr in deadReaders) + { + _ = _Readers?.Remove(dr.Path); + dr.OnKeyPress -= ReaderOnOnKeyPress; + dr.Dispose(); + } + } + } + + public void Dispose() + { + if (_Readers is not null) + { + foreach (InputReader d in _Readers.Values) + { + d.OnKeyPress -= ReaderOnOnKeyPress; + d.Dispose(); + } + } + + _Readers = null; + } +} \ No newline at end of file diff --git a/Shared/KeyboardMouse/DeviceReader.cs b/Shared/KeyboardMouse/DeviceReader.cs new file mode 100644 index 0000000..8f97752 --- /dev/null +++ b/Shared/KeyboardMouse/DeviceReader.cs @@ -0,0 +1,82 @@ +namespace Barcode.Host.Shared.KeyboardMouse; + +public static class DeviceReader +{ + public static IEnumerable Get(string path = "/proc/bus/input/devices") + { + List devices = new(); + + using FileStream filestream = new(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using StreamReader reader = new(filestream); + + LinuxDevice linuxDevice = new(); + while (!reader.EndOfStream) + { + string? line = reader.ReadLine(); + + if (string.IsNullOrWhiteSpace(line)) + { + if (!string.IsNullOrWhiteSpace(linuxDevice.Name)) + { + devices.Add(linuxDevice); + linuxDevice = new LinuxDevice(); + } + + continue; + } + + if (line.StartsWith("I")) + ApplyIdentifier(line, linuxDevice); + + else if (line.StartsWith("N")) + linuxDevice.Name = line.Substring(9, line.Length - 9 - 1); + + else if (line.StartsWith("P")) + linuxDevice.PhysicalPath = line[8..]; + + else if (line.StartsWith("S")) + linuxDevice.SysFsPath = line[9..]; + + else if (line.StartsWith("U")) + linuxDevice.UniqueIdentificationCode = line[8..]; + + else if (line.StartsWith("H")) + linuxDevice.Handlers = line[12..] + .Split(" ") + .Where(h => !string.IsNullOrWhiteSpace(h)) + .ToList(); + + else if (line.StartsWith("B")) + linuxDevice.Bitmaps.Add(line[3..]); + } + + return devices; + } + + private static void ApplyIdentifier(string line, LinuxDevice linuxDevice) + { + string[] values = line[3..] + .Split(" "); + + foreach (string v in values) + { + string[] kvp = v.Split("="); + + switch (kvp[0]) + { + case "Bus": + linuxDevice.Identifier.Bus = kvp[1]; + break; + case "Vendor": + linuxDevice.Identifier.Vendor = kvp[1]; + break; + case "Product": + linuxDevice.Identifier.Product = kvp[1]; + break; + case "Version": + linuxDevice.Identifier.Version = kvp[1]; + break; + } + } + } +} \ No newline at end of file diff --git a/Shared/KeyboardMouse/EventCode.cs b/Shared/KeyboardMouse/EventCode.cs new file mode 100644 index 0000000..27a3067 --- /dev/null +++ b/Shared/KeyboardMouse/EventCode.cs @@ -0,0 +1,277 @@ +namespace Barcode.Host.Shared.KeyboardMouse; + +/// +/// Mapping for this can be found here: https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h +/// +public enum EventCode +{ + Reserved = 0, + Esc = 1, + Num1 = 2, + Num2 = 3, + Num3 = 4, + Num4 = 5, + Num5 = 6, + Num6 = 7, + Num7 = 8, + Num8 = 9, + Num9 = 10, + Num0 = 11, + Minus = 12, + Equal = 13, + Backspace = 14, + Tab = 15, + Q = 16, + W = 17, + E = 18, + R = 19, + T = 20, + Y = 21, + U = 22, + I = 23, + O = 24, + P = 25, + LeftBrace = 26, + RightBrace = 27, + Enter = 28, + LeftCtrl = 29, + A = 30, + S = 31, + D = 32, + F = 33, + G = 34, + H = 35, + J = 36, + K = 37, + L = 38, + Semicolon = 39, + Apostrophe = 40, + Grave = 41, + LeftShift = 42, + Backslash = 43, + Z = 44, + X = 45, + C = 46, + V = 47, + B = 48, + N = 49, + M = 50, + Comma = 51, + Dot = 52, + Slash = 53, + RightShift = 54, + KpAsterisk = 55, + LeftAlt = 56, + Space = 57, + Capslock = 58, + F1 = 59, + Pf2 = 60, + F3 = 61, + F4 = 62, + F5 = 63, + F6 = 64, + F7 = 65, + F8 = 66, + Pf9 = 67, + F10 = 68, + Numlock = 69, + ScrollLock = 70, + Kp7 = 71, + Kp8 = 72, + Kp9 = 73, + PkpMinus = 74, + Kp4 = 75, + Kp5 = 76, + Kp6 = 77, + KpPlus = 78, + Kp1 = 79, + Kp2 = 80, + Kp3 = 81, + Kp0 = 82, + KpDot = 83, + + Zenkakuhankaku = 85, + //102ND = 86, + F11 = 87, + F12 = 88, + Ro = 89, + Katakana = 90, + Hiragana = 91, + Henkan = 92, + Katakanahiragana = 93, + Muhenkan = 94, + KpJpComma = 95, + KpEnter = 96, + RightCtrl = 97, + KpSlash = 98, + SysRq = 99, + RightAlt = 100, + LineFeed = 101, + Home = 102, + Up = 103, + Pageup = 104, + Left = 105, + Right = 106, + End = 107, + Down = 108, + Pagedown = 109, + Insert = 110, + Delete = 111, + Macro = 112, + Mute = 113, + VolumeDown = 114, + VolumeUp = 115, + Power = 116, // SC System Power Down + KpEqual = 117, + KpPlusMinus = 118, + Pause = 119, + Scale = 120, // AL Compiz Scale (Expose) + + KpComma = 121, + Hangeul = 122, + Hanja = 123, + Yen = 124, + LeftMeta = 125, + RightMeta = 126, + Compose = 127, + + Stop = 128, // AC Stop + Again = 129, + Props = 130, // AC Properties + Undo = 131, // AC Undo + Front = 132, + Copy = 133, // AC Copy + Open = 134, // AC Open + Paste = 135, // AC Paste + Find = 136, // AC Search + Cut = 137, // AC Cut + Help = 138, // AL Integrated Help Center + Menu = 139, // Menu (show menu) + Calc = 140, // AL Calculator + Setup = 141, + Sleep = 142, // SC System Sleep + Wakeup = 143, // System Wake Up + File = 144, // AL Local Machine Browser + Sendfile = 145, + DeleteFile = 146, + Xfer = 147, + Prog1 = 148, + Prog2 = 149, + Www = 150, // AL Internet Browser + MsDos = 151, + Coffee = 152, // AL Terminal Lock/Screensaver + RotateDisplay = 153, // Display orientation for e.g. tablets + CycleWindows = 154, + Mail = 155, + Bookmarks = 156, // AC Bookmarks + Computer = 157, + Back = 158, // AC Back + Forward = 159, // AC Forward + CloseCd = 160, + EjectCd = 161, + EjectCloseCd = 162, + NextSong = 163, + PlayPause = 164, + PreviousSong = 165, + StopCd = 166, + Record = 167, + Rewind = 168, + Phone = 169, // Media Select Telephone + Iso = 170, + Config = 171, // AL Consumer Control Configuration + Homepage = 172, // AC Home + Refresh = 173, // AC Refresh + Exit = 174, // AC Exit + Move = 175, + Edit = 176, + ScrollUp = 177, + ScrollDown = 178, + KpLeftParen = 179, + KpRightParen = 180, + New = 181, // AC New + Redo = 182, // AC Redo/Repeat + + F13 = 183, + F14 = 184, + F15 = 185, + F16 = 186, + F17 = 187, + F18 = 188, + F19 = 189, + F20 = 190, + F21 = 191, + F22 = 192, + F23 = 193, + F24 = 194, + + PlayCd = 200, + PauseCd = 201, + Prog3 = 202, + Prog4 = 203, + Dashboard = 204, // AL Dashboard + Suspend = 205, + Close = 206, // AC Close + Play = 207, + FastForward = 208, + BassBoost = 209, + Print = 210, // AC Print + Hp = 211, + Camera = 212, + Sound = 213, + Question = 214, + Email = 215, + Chat = 216, + Search = 217, + Connect = 218, + Finance = 219, // AL Checkbook/Finance + Sport = 220, + Shop = 221, + AltErase = 222, + Cancel = 223, // AC Cancel + BrightnessDown = 224, + BrightnessUp = 225, + Media = 226, + + SwitchVideoMode = 227, // Cycle between available video outputs (Monitor/LCD/TV-out/etc) + KbdIllumToggle = 228, + KbdIllumDown = 229, + KbdIllumUp = 230, + + Send = 231, // AC Send + Reply = 232, // AC Reply + ForwardMail = 233, // AC Forward Msg + Save = 234, // AC Save + Documents = 235, + + Battery = 236, + + Bluetooth = 237, + Wlan = 238, + Uwb = 239, + + Unknown = 240, + + VideoNext = 241, // drive next video source + VideoPrev = 242, // drive previous video source + BrightnessCycle = 243, // brightness up, after max is min + BrightnessAuto = 244, // Set Auto Brightness: manual brightness control is off, rely on ambient + DisplayOff = 245, // display device to off state + + Wwan = 246, // Wireless WAN (LTE, UMTS, GSM, etc.) + RfKill = 247, // Key that controls all radios + + MicMute = 248, // Mute / unmute the microphone + LeftMouse = 272, + RightMouse = 273, + MiddleMouse = 274, + MouseBack = 275, + MouseForward = 276, + + ToolFinger = 325, + ToolQuintTap = 328, + Touch = 330, + ToolDoubleTap = 333, + ToolTripleTap = 334, + ToolQuadTap = 335, + Mic = 582 +} \ No newline at end of file diff --git a/Shared/KeyboardMouse/EventType.cs b/Shared/KeyboardMouse/EventType.cs new file mode 100644 index 0000000..6823ef1 --- /dev/null +++ b/Shared/KeyboardMouse/EventType.cs @@ -0,0 +1,64 @@ +namespace Barcode.Host.Shared.KeyboardMouse; + +public enum EventType +{ + /// + /// Used as markers to separate events. Events may be separated in time or in space, such as with the multitouch protocol. + /// + EV_SYN, + + /// + /// Used to describe state changes of keyboards, buttons, or other key-like devices. + /// + EV_KEY, + + /// + /// Used to describe relative axis value changes, e.g. moving the mouse 5 units to the left. + /// + EV_REL, + + /// + /// Used to describe absolute axis value changes, e.g. describing the coordinates of a touch on a touchscreen. + /// + EV_ABS, + + /// + /// Used to describe miscellaneous input data that do not fit into other types. + /// + EV_MSC, + + /// + /// Used to describe binary state input switches. + /// + EV_SW, + + /// + /// Used to turn LEDs on devices on and off. + /// + EV_LED, + + /// + /// Used to output sound to devices. + /// + EV_SND, + + /// + /// Used for autorepeating devices. + /// + EV_REP, + + /// + /// Used to send force feedback commands to an input device. + /// + EV_FF, + + /// + /// A special type for power button and switch input. + /// + EV_PWR, + + /// + /// Used to receive force feedback device status. + /// + EV_FF_STATUS, +} \ No newline at end of file diff --git a/Shared/KeyboardMouse/InputReader.cs b/Shared/KeyboardMouse/InputReader.cs new file mode 100644 index 0000000..02b475a --- /dev/null +++ b/Shared/KeyboardMouse/InputReader.cs @@ -0,0 +1,166 @@ +using Microsoft.Extensions.Logging; + +namespace Barcode.Host.Shared.KeyboardMouse; + +public class InputReader : IDisposable +{ + private readonly ILogger _Logger; + private const int _BufferLength = 24; + + private static readonly int _PiOffset; + + private readonly byte[] _Buffer = new byte[_BufferLength]; + + private FileStream? _Stream; + private bool _Disposing; + + public delegate void RaiseKeyPress(KeyPressEvent e); + + public delegate void RaiseMouseMove(MouseMoveEvent e); + + public event RaiseKeyPress? OnKeyPress; + + public event RaiseMouseMove? OnMouseMove; + + public string Path { get; } + + public bool Faulted { get; private set; } + + static InputReader() + { + if (RunningOnRaspberryPi()) + { + _PiOffset = -8; + } + } + + public InputReader( + string path, + ILogger logger) + { + _Logger = logger; + + Path = path; + + try + { + _Stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + } + catch (UnauthorizedAccessException ex) + { + _Logger.LogError(ex, "Current user doesn't have permissions to access input data. Add user to input group to correct this error"); + Faulted = true; + } + catch (IOException ex) + { + _Logger.LogWarning(ex, $"Error occurred while trying to build stream for {path}"); + Faulted = true; + } + + _ = Task.Run(Run); + } + + private void Run() + { + while (true) + { + if (_Disposing) + break; + + try + { + if (!Faulted && _Stream is not null) + { + _ = _Stream.Read(_Buffer, 0, _BufferLength); + } + } + catch (IOException ex) + { + _Logger.LogInformation(ex, $"Error occured while trying to read from the stream for {Path}"); + Faulted = true; + } + + EventType type = GetEventType(); + short code = GetCode(); + int value = GetValue(); + + switch (type) + { + case EventType.EV_KEY: + HandleKeyPressEvent(code, value); + break; + case EventType.EV_REL: + MouseAxis axis = (MouseAxis)code; + MouseMoveEvent e = new(axis, value); + OnMouseMove?.Invoke(e); + break; + } + } + } + + private int GetValue() + { + byte[] valueBits = new[] + { + _Buffer[20 + _PiOffset], + _Buffer[21 + _PiOffset], + _Buffer[22 + _PiOffset], + _Buffer[23 + _PiOffset] + }; + + int value = BitConverter.ToInt32(valueBits, 0); + + return value; + } + + private short GetCode() + { + byte[] codeBits = new[] + { + _Buffer[18 + _PiOffset], + _Buffer[19 + _PiOffset] + }; + + short code = BitConverter.ToInt16(codeBits, 0); + + return code; + } + + private EventType GetEventType() + { + byte[] typeBits = new[] + { + _Buffer[16 + _PiOffset], + _Buffer[17 + _PiOffset] + }; + + short type = BitConverter.ToInt16(typeBits, 0); + + EventType eventType = (EventType)type; + + return eventType; + } + + private void HandleKeyPressEvent(short code, int value) + { + EventCode c = (EventCode)code; + KeyState s = (KeyState)value; + KeyPressEvent e = new(c, s); + OnKeyPress?.Invoke(e); + } + + public void Dispose() + { + _Disposing = true; + _Stream?.Dispose(); + _Stream = null; + } + + private static bool RunningOnRaspberryPi() + { + string path = "/proc/cpuinfo"; + string[] text = File.ReadAllLines(path); + bool runningOnPi = text.Any(l => l.Contains("Raspberry Pi")); + return runningOnPi; + } +} \ No newline at end of file diff --git a/Shared/KeyboardMouse/KeyPressEvent.cs b/Shared/KeyboardMouse/KeyPressEvent.cs new file mode 100644 index 0000000..b0303fa --- /dev/null +++ b/Shared/KeyboardMouse/KeyPressEvent.cs @@ -0,0 +1,14 @@ +namespace Barcode.Host.Shared.KeyboardMouse; + +public readonly struct KeyPressEvent +{ + public KeyPressEvent(EventCode code, KeyState state) + { + Code = code; + State = state; + } + + public EventCode Code { get; } + + public KeyState State { get; } +} \ No newline at end of file diff --git a/Shared/KeyboardMouse/KeyState.cs b/Shared/KeyboardMouse/KeyState.cs new file mode 100644 index 0000000..2758618 --- /dev/null +++ b/Shared/KeyboardMouse/KeyState.cs @@ -0,0 +1,8 @@ +namespace Barcode.Host.Shared.KeyboardMouse; + +public enum KeyState +{ + KeyUp, + KeyDown, + KeyHold +} \ No newline at end of file diff --git a/Shared/KeyboardMouse/LinuxDevice.cs b/Shared/KeyboardMouse/LinuxDevice.cs new file mode 100644 index 0000000..c6321a7 --- /dev/null +++ b/Shared/KeyboardMouse/LinuxDevice.cs @@ -0,0 +1,18 @@ +namespace Barcode.Host.Shared.KeyboardMouse; + +public class LinuxDevice +{ + public LinuxDeviceIdentifier Identifier { get; set; } = new(); + + public string? Name { get; set; } + + public string? PhysicalPath { get; set; } + + public string? SysFsPath { get; set; } + + public string? UniqueIdentificationCode { get; set; } + + public List Handlers { get; set; } = new(); + + public List Bitmaps { get; set; } = new(); +} \ No newline at end of file diff --git a/Shared/KeyboardMouse/LinuxDeviceIdentifier.cs b/Shared/KeyboardMouse/LinuxDeviceIdentifier.cs new file mode 100644 index 0000000..3adafb3 --- /dev/null +++ b/Shared/KeyboardMouse/LinuxDeviceIdentifier.cs @@ -0,0 +1,12 @@ +namespace Barcode.Host.Shared.KeyboardMouse; + +public class LinuxDeviceIdentifier +{ + public string? Bus { get; set; } + + public string? Vendor { get; set; } + + public string? Product { get; set; } + + public string? Version { get; set; } +} \ No newline at end of file diff --git a/Shared/KeyboardMouse/MouseAxis.cs b/Shared/KeyboardMouse/MouseAxis.cs new file mode 100644 index 0000000..b1cdf88 --- /dev/null +++ b/Shared/KeyboardMouse/MouseAxis.cs @@ -0,0 +1,7 @@ +namespace Barcode.Host.Shared.KeyboardMouse; + +public enum MouseAxis +{ + X, + Y +} \ No newline at end of file diff --git a/Shared/KeyboardMouse/MouseMoveEvent.cs b/Shared/KeyboardMouse/MouseMoveEvent.cs new file mode 100644 index 0000000..789b409 --- /dev/null +++ b/Shared/KeyboardMouse/MouseMoveEvent.cs @@ -0,0 +1,14 @@ +namespace Barcode.Host.Shared.KeyboardMouse; + +public readonly struct MouseMoveEvent +{ + public MouseMoveEvent(MouseAxis axis, int amount) + { + Axis = axis; + Amount = amount; + } + + public MouseAxis Axis { get; } + + public int Amount { get; } +} \ No newline at end of file diff --git a/Shared/Linux/GroupManager.cs b/Shared/Linux/GroupManager.cs new file mode 100644 index 0000000..6b87767 --- /dev/null +++ b/Shared/Linux/GroupManager.cs @@ -0,0 +1,44 @@ +using CliWrap; +using CliWrap.Buffered; + +namespace Barcode.Host.Shared.Linux; + +public class LinuxGroupManager : ILinuxGroupManager +{ + public async Task IsInInputGroup() + { + BufferedCommandResult result = await Cli.Wrap("id") + .ExecuteBufferedAsync(); + + string output = result.StandardOutput; + + const StringSplitOptions options = StringSplitOptions.RemoveEmptyEntries; + bool inInputGroup = output.Split(new[] { ' ' }, options) + .First(p => p.StartsWith("groups")) + .Remove(0, "groups".Length) + .Split(',', options) + .Any(p => p.Contains("input")); + + return inInputGroup; + } + + public async Task AddUserToInputGroup(string password) + { + using CancellationTokenSource cts = new(); + cts.CancelAfter(TimeSpan.FromSeconds(10)); + + _ = await Cli.Wrap("bash") + .WithArguments($"-c \"echo '{password}' | sudo -S gpasswd -a $USER input") + .ExecuteBufferedAsync(cts.Token); + } + + public async Task RebootSystem(string password) + { + using CancellationTokenSource cts = new(); + cts.CancelAfter(TimeSpan.FromSeconds(10)); + + _ = await Cli.Wrap("bash") + .WithArguments($"-c \"echo '{password}' | sudo -S reboot\"") + .ExecuteBufferedAsync(cts.Token); + } +} \ No newline at end of file diff --git a/Shared/Linux/IGroupManager.cs b/Shared/Linux/IGroupManager.cs new file mode 100644 index 0000000..b38620a --- /dev/null +++ b/Shared/Linux/IGroupManager.cs @@ -0,0 +1,10 @@ +namespace Barcode.Host.Shared.Linux; + +public interface ILinuxGroupManager +{ + Task IsInInputGroup(); + + Task AddUserToInputGroup(string password); + + Task RebootSystem(string password); +} \ No newline at end of file diff --git a/Shared/Models/IWorkingDirectory.cs b/Shared/Models/IWorkingDirectory.cs new file mode 100644 index 0000000..6213a0d --- /dev/null +++ b/Shared/Models/IWorkingDirectory.cs @@ -0,0 +1,9 @@ +namespace Barcode.Host.Shared.Models; + +public interface IWorkingDirectory +{ + + static string GetWorkingDirectory(string? executingAssemblyName, string subDirectoryName) => + WorkingDirectory.GetWorkingDirectory(executingAssemblyName, subDirectoryName); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/IAppSettingsController.cs b/Shared/Models/Stateless/IAppSettingsController.cs new file mode 100644 index 0000000..ea3ff20 --- /dev/null +++ b/Shared/Models/Stateless/IAppSettingsController.cs @@ -0,0 +1,15 @@ +namespace Barcode.Host.Shared.Models.Stateless; + +public interface IAppSettingsController +{ + + enum Action : int + { + App = 0, + DevOps = 1 + } + + static string GetRouteName() => nameof(IAppSettingsController)[1..^10]; + T GetAppSettings(); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/IAppSettingsRepository.cs b/Shared/Models/Stateless/IAppSettingsRepository.cs new file mode 100644 index 0000000..a1082c6 --- /dev/null +++ b/Shared/Models/Stateless/IAppSettingsRepository.cs @@ -0,0 +1,10 @@ +namespace Barcode.Host.Shared.Models.Stateless; + +public interface IAppSettingsRepository +{ + + T GetAppSettings(); + string GetBuildNumberAndGitCommitSeven(); + void VerifyConnectionStrings(); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/IMethodName.cs b/Shared/Models/Stateless/IMethodName.cs new file mode 100644 index 0000000..a2e572b --- /dev/null +++ b/Shared/Models/Stateless/IMethodName.cs @@ -0,0 +1,10 @@ +using System.Runtime.CompilerServices; + +namespace Barcode.Host.Shared.Models.Stateless; + +public interface IMethodName +{ + + static string? GetActualAsyncMethodName([CallerMemberName] string? name = null) => name; + +} \ No newline at end of file diff --git a/Shared/Models/WorkingDirectory.cs b/Shared/Models/WorkingDirectory.cs new file mode 100644 index 0000000..287358a --- /dev/null +++ b/Shared/Models/WorkingDirectory.cs @@ -0,0 +1,50 @@ +namespace Barcode.Host.Shared.Models; + +internal abstract class WorkingDirectory +{ + + internal static string GetWorkingDirectory(string? executingAssemblyName, string subDirectoryName) + { + string result = string.Empty; + if (executingAssemblyName is null) + throw new Exception(); + string traceFile; + List directories = new(); + Environment.SpecialFolder[] specialFolders = new Environment.SpecialFolder[] + { + Environment.SpecialFolder.LocalApplicationData, + Environment.SpecialFolder.ApplicationData, + Environment.SpecialFolder.History, + Environment.SpecialFolder.CommonApplicationData, + Environment.SpecialFolder.InternetCache + }; + foreach (Environment.SpecialFolder specialFolder in specialFolders) + directories.Add(Path.Combine(Environment.GetFolderPath(specialFolder), subDirectoryName, executingAssemblyName)); + foreach (string directory in directories) + { + for (int i = 1; i < 3; i++) + { + if (i == 1) + result = directory; + else + result = string.Concat("D", directory[1..]); + try + { + if (!Directory.Exists(result)) + _ = Directory.CreateDirectory(result); + traceFile = string.Concat(result, @"\", DateTime.Now.Ticks, ".txt"); + File.WriteAllText(traceFile, traceFile); + File.Delete(traceFile); + break; + } + catch (Exception) { result = string.Empty; } + } + if (!string.IsNullOrEmpty(result)) + break; + } + if (string.IsNullOrEmpty(result)) + throw new Exception("Unable to set working directory!"); + return result; + } + +} \ No newline at end of file