From 89413fe4b21d7b54bef7c2ac2c9ebc0d818b8f66 Mon Sep 17 00:00:00 2001 From: Mike Phares Date: Sat, 8 Feb 2025 11:44:28 -0700 Subject: [PATCH] Ready to test in WSL --- .editorconfig | 360 +++++++++++ .gitignore | 339 ++++++++++ .vscode/bash.md | 17 + .vscode/format-report.json | 1 + .vscode/launch.json | 33 + .vscode/mklink.md | 9 + .vscode/readme.md | 12 + .vscode/settings.json | 38 ++ .vscode/tasks.json | 176 ++++++ Dockerfile | 25 + OriginalToDeterministicHashCode.sln | 27 + docker-compose.yaml | 33 + global.json | 6 + .../original-to-deterministic-hash-code.http | 6 + .../Controllers/RenameController.cs | 19 + .../ServiceCollectionExtensions.cs | 15 + .../Models/AppSettings.cs | 64 ++ .../Models/Identifier.cs | 32 + .../Models/RenameSettings.cs | 38 ++ .../OriginalToDeterministicHashCode.csproj | 20 + .../Program.cs | 22 + .../Services/RenameService.cs | 582 ++++++++++++++++++ 22 files changed, 1874 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .vscode/bash.md create mode 100644 .vscode/format-report.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/mklink.md create mode 100644 .vscode/readme.md create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 Dockerfile create mode 100644 OriginalToDeterministicHashCode.sln create mode 100644 docker-compose.yaml create mode 100644 global.json create mode 100644 requests/original-to-deterministic-hash-code.http create mode 100644 src/OriginalToDeterministicHashCode/Controllers/RenameController.cs create mode 100644 src/OriginalToDeterministicHashCode/DependencyInjection/ServiceCollectionExtensions.cs create mode 100644 src/OriginalToDeterministicHashCode/Models/AppSettings.cs create mode 100644 src/OriginalToDeterministicHashCode/Models/Identifier.cs create mode 100644 src/OriginalToDeterministicHashCode/Models/RenameSettings.cs create mode 100644 src/OriginalToDeterministicHashCode/OriginalToDeterministicHashCode.csproj create mode 100644 src/OriginalToDeterministicHashCode/Program.cs create mode 100644 src/OriginalToDeterministicHashCode/Services/RenameService.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3eab4da --- /dev/null +++ b/.editorconfig @@ -0,0 +1,360 @@ +[*.md] +end_of_line = crlf +file_header_template = unset +indent_size = 2 +indent_style = space +insert_final_newline = false +root = true +tab_width = 2 +[*.csproj] +end_of_line = crlf +file_header_template = unset +indent_size = 2 +indent_style = space +insert_final_newline = false +root = true +tab_width = 2 +[*.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_qualified_reference = true:error +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_analyzer_diagnostic.category-Design.severity = error +dotnet_analyzer_diagnostic.category-Documentation.severity = error +dotnet_analyzer_diagnostic.category-Globalization.severity = none +dotnet_analyzer_diagnostic.category-Interoperability.severity = error +dotnet_analyzer_diagnostic.category-Maintainability.severity = error +dotnet_analyzer_diagnostic.category-Naming.severity = none +dotnet_analyzer_diagnostic.category-Performance.severity = none +dotnet_analyzer_diagnostic.category-Reliability.severity = error +dotnet_analyzer_diagnostic.category-Security.severity = error +dotnet_analyzer_diagnostic.category-SingleFile.severity = error +dotnet_analyzer_diagnostic.category-Style.severity = error +dotnet_analyzer_diagnostic.category-Usage.severity = error +dotnet_code_quality_unused_parameters = all +dotnet_code_quality_unused_parameters = non_public +dotnet_code_quality.CAXXXX.api_surface = private, internal +dotnet_diagnostic.CA1001.severity = error # CA1001: Types that own disposable fields should be disposable +dotnet_diagnostic.CA1001.severity = none # Question - CA1001: Types that own disposable fields should be disposable +dotnet_diagnostic.CA1051.severity = error # CA1051: Do not declare visible instance fields +dotnet_diagnostic.CA1051.severity = none # Question - CA1051: Do not declare visible instance fields +dotnet_diagnostic.CA1416.severity = none # Question - CA1416: This call site is reachable on all platforms. 'EventLogEntryType.Error' is only supported on: 'windows'. +dotnet_diagnostic.CA1510.severity = none # Question - CA1510: Use 'ArgumentNullException.ThrowIfNull' instead of explicitly throwing a new exception instance +dotnet_diagnostic.CA1511.severity = warning # CA1511: Use 'ArgumentException.ThrowIfNullOrEmpty' instead of explicitly throwing a new exception instance +dotnet_diagnostic.CA1513.severity = warning # Use 'ObjectDisposedException.ThrowIf' instead of explicitly throwing a new exception instance +dotnet_diagnostic.CA1825.severity = warning # CA1825: Avoid zero-length array allocations +dotnet_diagnostic.CA1829.severity = error # CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1829.severity = none # Question - CA1829: Use Length/Count property instead of Enumerable.Count method +dotnet_diagnostic.CA1834.severity = warning # CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.CA1860.severity = error # CA1860: Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance +dotnet_diagnostic.CA1860.severity = none # Question - CA1860: Avoid using 'Enumerable.Any()' extension method +dotnet_diagnostic.CA1862.severity = warning # CA1862: Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase' +dotnet_diagnostic.CA1862.severity = none # Question - CA1862: Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase' +dotnet_diagnostic.CA1869.severity = none # CA1869: Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. +dotnet_diagnostic.CA2200.severity = none # Question - CA2200: Re-throwing caught exception changes stack information +dotnet_diagnostic.CA2201.severity = none # CA2201: Exception type System.NullReferenceException is reserved by the runtime +dotnet_diagnostic.CA2208.severity = none # Question - CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2211.severity = none # Question - CA2211: Non-constant fields should not be visible +dotnet_diagnostic.CA2249.severity = none # Question - CA2249: Use 'string.Contains' instead of 'string.IndexOf' to improve readability +dotnet_diagnostic.CA2253.severity = none # Question - CA2253: Named placeholders should not be numeric values +dotnet_diagnostic.CA2254.severity = none # CA2254: The logging message template should not vary between calls to 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])' +dotnet_diagnostic.CS0103.severity = none # Question - CS0103: The name 'Functions' does not exist in the current context +dotnet_diagnostic.CS0168.severity = none # Question - CS0168: The variable 'ex' is declared but never used +dotnet_diagnostic.CS0219.severity = none # Question - CS0219: The variable 'result' is assigned but its value is never used +dotnet_diagnostic.CS0618.severity = none # Question - CS0618: Compiler Warning (level 2) +dotnet_diagnostic.CS0659.severity = none # Question - CS0659: Compiler Warning (level 3) +dotnet_diagnostic.CS8600.severity = none # Question - CS8600: Converting null literal or possible null value to non-nullable type +dotnet_diagnostic.CS8602.severity = none # Question - CS8602: Dereference of a possibly null reference. +dotnet_diagnostic.CS8603.severity = none # Question - CS8603: Possible null reference return +dotnet_diagnostic.CS8604.severity = none # Question - CS8604: Possible null reference argument for parameter. +dotnet_diagnostic.CS0612.severity = none # Question - CS0612: is obsolete +dotnet_diagnostic.CS8618.severity = none # Question - CS8618: Non-nullable variable must contain a non-null value when exiting constructor +dotnet_diagnostic.CS8625.severity = none # Question - CS8625: Cannot convert null literal to non-nullable reference type. +dotnet_diagnostic.CS8629.severity = none # Question - CS8629: Nullable value type may be null +dotnet_diagnostic.CS8765.severity = none # Question - CS8765: Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes) +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.IDE0004.severity = none # Question - IDE0004: Cast is redundant. +dotnet_diagnostic.IDE0003.severity = none # Question - IDE0003: this and Me preferences +dotnet_diagnostic.IDE0005.severity = error # Using directive is unnecessary +dotnet_diagnostic.IDE0005.severity = none # Question - IDE0005: Remove unnecessary using directives +dotnet_diagnostic.IDE0008.severity = none # Question - IDE0008: 'var' preferences +dotnet_diagnostic.IDE0010.severity = none # Add missing cases to switch statement (IDE0010) +dotnet_diagnostic.IDE0017.severity = none # Question - IDE0017: Use object initializers +dotnet_diagnostic.IDE0019.severity = none # Question - IDE0019: Use pattern matching to avoid 'as' followed by a 'null' check +dotnet_diagnostic.IDE0021.severity = none # Question - IDE0021: Use expression body for constructors +dotnet_diagnostic.IDE0022.severity = none # Question - IDE0022: Use expression body for methods +dotnet_diagnostic.IDE0025.severity = none # Question - IDE0025: Use expression body for properties +dotnet_diagnostic.IDE0027.severity = none # Question - IDE0027: Use expression body for accessor +dotnet_diagnostic.IDE0028.severity = error # IDE0028: Collection initialization can be simplified +dotnet_diagnostic.IDE0028.severity = none # Question - IDE0028: Use collection initializers or expressions +dotnet_diagnostic.IDE0031.severity = warning # Use null propagation (IDE0031) +dotnet_diagnostic.IDE0031.severity = none # Question - IDE0031: Use null propagation (IDE0031) +dotnet_diagnostic.IDE0032.severity = none # Question - IDE0032: Use auto-implemented property +dotnet_diagnostic.IDE0037.severity = none # Question - IDE0037: Member name can be simplified +dotnet_diagnostic.IDE0040.severity = none # Question - IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0041.severity = none # Question - IDE0041: Use 'is null' check +dotnet_diagnostic.IDE0044.severity = none # Question - IDE0044: Add readonly modifier +dotnet_diagnostic.IDE0047.severity = warning # IDE0047: Parentheses can be removed +dotnet_diagnostic.IDE0047.severity = none # Question - IDE0047: Parentheses preferences +dotnet_diagnostic.IDE0048.severity = none # Parentheses preferences (IDE0047 and IDE0048) +dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references (IDE0049) +dotnet_diagnostic.IDE0051.severity = error # Private member '' is unused [, ] +dotnet_diagnostic.IDE0051.severity = none # Question - IDE0051: Remove unused private member +dotnet_diagnostic.IDE0053.severity = none # Question - IDE0053: Use expression body for lambdas +dotnet_diagnostic.IDE0054.severity = none # Question - IDE0054: Use compound assignment +dotnet_diagnostic.IDE0055.severity = none # Question - IDE0055: Formatting rule +dotnet_diagnostic.IDE0057.severity = none # Question - IDE0057: Substring can be simplified +dotnet_diagnostic.IDE0058.severity = error # IDE0058: Expression value is never used +dotnet_diagnostic.IDE0058.severity = none # Question - IDE0058: Remove unnecessary expression value +dotnet_diagnostic.IDE0059.severity = none # Question - IDE0059: Remove unnecessary value assignment +dotnet_diagnostic.IDE0060.severity = error # IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0060.severity = none # Question - IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0063.severity = none # Question - IDE0063: Use simple 'using' statement +dotnet_diagnostic.IDE0066.severity = none # Question - IDE0066: Use switch expression +dotnet_diagnostic.IDE0074.severity = warning # IDE0074: Use compound assignment +dotnet_diagnostic.IDE0078.severity = none # Question - IDE0078: Use pattern matching +dotnet_diagnostic.IDE0100.severity = none # Question - IDE0100: Remove redundant equality +dotnet_diagnostic.IDE0090.severity = none # Question - IDE0090: Simplify new expression +dotnet_diagnostic.IDE0130.severity = none # Namespace does not match folder structure (IDE0130) +dotnet_diagnostic.IDE0161.severity = none # Question - IDE0161: Namespace declaration preferences +dotnet_diagnostic.IDE0270.severity = none # Question - IDE0270: Null check can be simplified +dotnet_diagnostic.IDE0270.severity = warning # IDE0270: Null check can be simplified +dotnet_diagnostic.IDE0290.severity = none # Use primary constructor [Distance]csharp(IDE0290) +dotnet_diagnostic.IDE0300.severity = error # IDE0300: Collection initialization can be simplified +dotnet_diagnostic.IDE0065.severity = none # Question - IDE0065: 'using' directive placement +dotnet_diagnostic.IDE0300.severity = none # Question - IDE0300: Collection initialization can be simplified +dotnet_diagnostic.IDE0301.severity = error #IDE0301: Collection initialization can be simplified +dotnet_diagnostic.IDE0305.severity = none # IDE0305: Collection initialization can be simplified +dotnet_diagnostic.IDE1006.severity = none # Question - IDE1006: Use collection expression for builder dotnet_style_prefer_collection_expression +dotnet_diagnostic.IDE2000.severity = error # IDE2000: Allow multiple blank lines +dotnet_diagnostic.IDE2000.severity = none # Question - IDE2000: Allow multiple blank lines +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..136928a --- /dev/null +++ b/.gitignore @@ -0,0 +1,339 @@ +## 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/ + +# 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/ + +.extensions-vscode +.extensions-vscode-oss +.extensions-vscode-insiders + +.vscode/.UserSecrets/secrets.json + +.vscode/jsonl/* +.vscode/.year-season-source +.vscode/.year-season-destination \ No newline at end of file diff --git a/.vscode/bash.md b/.vscode/bash.md new file mode 100644 index 0000000..fb99e50 --- /dev/null +++ b/.vscode/bash.md @@ -0,0 +1,17 @@ +# Bash + +```Powershell 1731637312952 = 638672341129520000 = Thu Nov 14 2024 19:21:52 GMT-0700 (Mountain Standard Time) +dotnet sln add (ls -r **/**.csproj) +``` + +```bash 1731641980552 = 638672387805520000 = Thu Nov 14 2024 20:39:40 GMT-0700 (Mountain Standard Time) +dotnet new global.json --roll-forward latestMinor --sdk-version 8.0.100 +``` + +```bash 1731642143081 = 638672389430810000 = Thu Nov 14 2024 20:42:22 GMT-0700 (Mountain Standard Time) +dotnet run --project src/OriginalToDeterministicHashCode +``` + +```bash 1731643960696 = 638672407606960000 = Thu Nov 14 2024 21:12:40 GMT-0700 (Mountain Standard Time) +docker compose up --build +``` diff --git a/.vscode/format-report.json b/.vscode/format-report.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/.vscode/format-report.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d2d6165 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,33 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/src/OriginalToDeterministicHashCode/bin/Debug/net8.0/OriginalToDeterministicHashCode.dll", + "args": [ + "s", + "test" + ], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + }, + { + "type": "node", + "request": "launch", + "name": "node Launch Current Opened File", + "program": "${file}" + } + ] +} \ No newline at end of file diff --git a/.vscode/mklink.md b/.vscode/mklink.md new file mode 100644 index 0000000..eafa521 --- /dev/null +++ b/.vscode/mklink.md @@ -0,0 +1,9 @@ +# mklink + +## original-to-deterministic-hash-code + +## OriginalToDeterministicHashCode + +```bash 1731634239041 = 638672310390410000 = Thu Nov 14 2024 18:30:38 GMT-0700 (Mountain Standard Time) +mklink /J "L:\Git\original-to-deterministic-hash-code\.vscode\.UserSecrets" "C:\Users\phares\AppData\Roaming\Microsoft\UserSecrets\2f63ace9-efe5-4b0a-9ebe-529309f33e3f" +``` diff --git a/.vscode/readme.md b/.vscode/readme.md new file mode 100644 index 0000000..101513a --- /dev/null +++ b/.vscode/readme.md @@ -0,0 +1,12 @@ +# Readme + +1. Controllers => Presentation logic +1. Services => application logic +1. Domain => domain logic (business) + +## Replace + +- KanbnToQuartz +- OriginalToDeterministicHashCode +- kanbn-to-quartz +- original-to-deterministic-hash-code diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..76815e2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,38 @@ +{ + "[markdown]": { + "editor.wordWrap": "off" + }, + "files.exclude": { + "**/.git": false, + "**/node_modules": true + }, + "files.watcherExclude": { + "**/node_modules": true + }, + "cSpell.words": [ + "accessibilities", + "Acks", + "aspnet", + "ASPNETCORE", + "CAXXXX", + "DENITED", + "Exif", + "gitea", + "Immich", + "Infineon", + "Kanbn", + "Npgsql", + "Phares", + "Thumbhash" + ], + "rest-client.environmentVariables": { + "$shared": { + "productId": "asdfasdf", + "reviewId": "asdfasdf" + }, + "dev": { + "host": "http://localhost:5003", + "token": "ey..dev" + } + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..8732e1d --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,176 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "User Secrets Init", + "command": "dotnet", + "type": "process", + "args": [ + "user-secrets", + "-p", + "${workspaceFolder}/src/OriginalToDeterministicHashCode/OriginalToDeterministicHashCode.csproj", + "init" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "User Secrets Set", + "command": "dotnet", + "type": "process", + "args": [ + "user-secrets", + "-p", + "${workspaceFolder}/src/OriginalToDeterministicHashCode/OriginalToDeterministicHashCode.csproj", + "set", + "_UserSecretsId", + "2f63ace9-efe5-4b0a-9ebe-529309f33e3f" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "Format", + "command": "dotnet", + "type": "process", + "args": [ + "format", + "--report", + ".vscode", + "--verbosity", + "detailed", + "--severity", + "warn" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "Format-Whitespaces", + "command": "dotnet", + "type": "process", + "args": [ + "format", + "whitespace" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/OriginalToDeterministicHashCode/OriginalToDeterministicHashCode.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/src/OriginalToDeterministicHashCode/OriginalToDeterministicHashCode.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/src/OriginalToDeterministicHashCode/OriginalToDeterministicHashCode.csproj" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "podmanLogin", + "command": "podman", + "type": "process", + "args": [ + "login", + "gitea.phares.duckdns.org:443" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "podmanBuild", + "command": "podman", + "type": "process", + "args": [ + "build", + "-t", + "original-to-deterministic-hash-code", + "." + ], + "problemMatcher": "$msCompile" + }, + { + "label": "podmanImageList", + "command": "podman", + "type": "process", + "args": [ + "image", + "ls" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "podmanRun", + "command": "podman", + "type": "process", + "args": [ + "run", + "-p", + "5001:5001", + "--name", + "original-to-deterministic-hash-code-api-001", + "a3de856b5731" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "podmanTag", + "command": "podman", + "type": "process", + "args": [ + "tag", + "a3de856b5731", + "gitea.phares.duckdns.org:443/phares3757/original-to-deterministic-hash-code:latest" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "podmanPush", + "command": "podman", + "type": "process", + "args": [ + "push", + "gitea.phares.duckdns.org:443/phares3757/original-to-deterministic-hash-code:latest" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "Publish AOT", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "-r", + "win-x64", + "-c", + "Release", + "-p:PublishAot=true", + "${workspaceFolder}/src/OriginalToDeterministicHashCode/OriginalToDeterministicHashCode.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..88e1a22 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +# Stage 1: Build Stage +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build + +WORKDIR /src + +# restore +COPY ["src/OriginalToDeterministicHashCode/OriginalToDeterministicHashCode.csproj", "OriginalToDeterministicHashCode/"] +RUN dotnet restore 'OriginalToDeterministicHashCode/OriginalToDeterministicHashCode.csproj' + +# build +COPY ["src/OriginalToDeterministicHashCode", "OriginalToDeterministicHashCode/"] +WORKDIR /src/OriginalToDeterministicHashCode +RUN dotnet build 'OriginalToDeterministicHashCode.csproj' -c Release -o /app/build + +# Stage 2: Publish Stage +FROM build AS publish +RUN dotnet publish 'OriginalToDeterministicHashCode.csproj' -c Release -o /app/publish + +# Stage 3: Run Stage +FROM mcr.microsoft.com/dotnet/aspnet:8.0 +ENV ASPNETCORE_HTTP_PORTS=5001 +EXPOSE 5001 +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT [ "dotnet", "OriginalToDeterministicHashCode.dll" ] diff --git a/OriginalToDeterministicHashCode.sln b/OriginalToDeterministicHashCode.sln new file mode 100644 index 0000000..a24f70d --- /dev/null +++ b/OriginalToDeterministicHashCode.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{726EA01C-9356-430F-B1AE-7F22BF7387E4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OriginalToDeterministicHashCode", "src\OriginalToDeterministicHashCode\OriginalToDeterministicHashCode.csproj", "{1CE5AD06-2C61-45DA-A07F-981CF40C2485}" +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 + {1CE5AD06-2C61-45DA-A07F-981CF40C2485}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CE5AD06-2C61-45DA-A07F-981CF40C2485}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CE5AD06-2C61-45DA-A07F-981CF40C2485}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CE5AD06-2C61-45DA-A07F-981CF40C2485}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1CE5AD06-2C61-45DA-A07F-981CF40C2485} = {726EA01C-9356-430F-B1AE-7F22BF7387E4} + EndGlobalSection +EndGlobal diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..4330ba8 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,33 @@ +services: + webapp: + container_name: original-to-deterministic-hash-code-api + build: + context: . + dockerfile: Dockerfile + ports: + - "5006:5001" + environment: + - ASPNETCORE_ENVIRONMENT=Production + + # db: + # container_name: original-to-deterministic-hash-code-db + # image: docker.io/postgres:latest + # ports: + # - "5432:5432" + # environment: + # POSTGRES_DB: immich_to_slideshow + # POSTGRES_USER: postgres + # POSTGRES_PASSWORD: strong_password + # volumes: + # - postgres_data:/var/lib/postgresql/data + +# volumes: +# postgres_data: + +# version: "3" +# services: +# original-to-deterministic-hash-code-webapp: +# image: original-to-deterministic-hash-code-webapp +# ports: +# - 5001:5001 +# networks: {} \ No newline at end of file diff --git a/global.json b/global.json new file mode 100644 index 0000000..744a6ea --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "rollForward": "latestMinor", + "version": "8.0.100" + } +} \ No newline at end of file diff --git a/requests/original-to-deterministic-hash-code.http b/requests/original-to-deterministic-hash-code.http new file mode 100644 index 0000000..9da6815 --- /dev/null +++ b/requests/original-to-deterministic-hash-code.http @@ -0,0 +1,6 @@ +@host = https://localhost:5003 + +GET {{host}}/rename/ +Accept: application/json + +### diff --git a/src/OriginalToDeterministicHashCode/Controllers/RenameController.cs b/src/OriginalToDeterministicHashCode/Controllers/RenameController.cs new file mode 100644 index 0000000..0be65ec --- /dev/null +++ b/src/OriginalToDeterministicHashCode/Controllers/RenameController.cs @@ -0,0 +1,19 @@ +using OriginalToDeterministicHashCode.Services; +using Microsoft.AspNetCore.Mvc; + +namespace OriginalToDeterministicHashCode.Controllers; + +[ApiController] +[Route("[controller]")] +public class RenameController(RenameService RenameService) : ControllerBase +{ + + private readonly RenameService _RenameService = RenameService; + + [HttpGet()] + public IActionResult Get() + { + _RenameService.RenameFiles(); + return Ok(); + } +} \ No newline at end of file diff --git a/src/OriginalToDeterministicHashCode/DependencyInjection/ServiceCollectionExtensions.cs b/src/OriginalToDeterministicHashCode/DependencyInjection/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..d19d37e --- /dev/null +++ b/src/OriginalToDeterministicHashCode/DependencyInjection/ServiceCollectionExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using OriginalToDeterministicHashCode.Services; + +namespace OriginalToDeterministicHashCode.DependencyInjection; + +public static class ServiceCollectionExtensions +{ + + public static IServiceCollection AddServices(this IServiceCollection services) + { + _ = services.AddScoped(); + return services; + } + +} \ No newline at end of file diff --git a/src/OriginalToDeterministicHashCode/Models/AppSettings.cs b/src/OriginalToDeterministicHashCode/Models/AppSettings.cs new file mode 100644 index 0000000..e471b8d --- /dev/null +++ b/src/OriginalToDeterministicHashCode/Models/AppSettings.cs @@ -0,0 +1,64 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using View_by_Distance.Shared.Models; + +namespace OriginalToDeterministicHashCode.Models; + +public record AppSettings(ResultSettings ResultSettings, + MetadataSettings MetadataSettings, + RenameSettings RenameSettings) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, AppSettingsSourceGenerationContext.Default.AppSettings); + return result; + } + + private static void Verify(AppSettings appSettings) + { + if (appSettings.RenameSettings.MaxDegreeOfParallelism > Environment.ProcessorCount) + throw new Exception("MaxDegreeOfParallelism must be =< Environment.ProcessorCount!"); + if (appSettings.RenameSettings.MaxDegreeOfParallelism > 1 && (appSettings.RenameSettings.InPlace || appSettings.RenameSettings.InPlaceMoveDirectory || appSettings.RenameSettings.InPlaceWithOriginalName)) + throw new NotSupportedException($"Change Settings: {nameof(appSettings.RenameSettings.InPlace)} or {nameof(appSettings.RenameSettings.InPlaceMoveDirectory)} or {nameof(appSettings.RenameSettings.MaxDegreeOfParallelism)}"); + if (appSettings.RenameSettings.InPlace && appSettings.RenameSettings.InPlaceMoveDirectory && appSettings.RenameSettings.InPlaceWithOriginalName) + throw new NotSupportedException($"Change Settings: {nameof(appSettings.RenameSettings.InPlace)} or {nameof(appSettings.RenameSettings.InPlaceMoveDirectory)} or {nameof(appSettings.RenameSettings.InPlaceWithOriginalName)}"); + } + + public static AppSettings Get(IConfigurationRoot configurationRoot) + { + AppSettings result; +#pragma warning disable IL3050, IL2026 + ResultSettings? resultSettings = configurationRoot.GetSection(nameof(ResultSettings)).Get(); + MetadataSettings? metadataSettings = configurationRoot.GetSection(nameof(MetadataSettings)).Get(); + RenameSettings? renameSettings = configurationRoot.GetSection(nameof(RenameSettings)).Get(); +#pragma warning restore IL3050, IL2026 + if (resultSettings is null || metadataSettings is null || renameSettings?.Company is null) + { + List paths = []; + foreach (IConfigurationProvider configurationProvider in configurationRoot.Providers) + { + if (configurationProvider is not Microsoft.Extensions.Configuration.Json.JsonConfigurationProvider jsonConfigurationProvider) + continue; + if (jsonConfigurationProvider.Source.FileProvider is not Microsoft.Extensions.FileProviders.PhysicalFileProvider physicalFileProvider) + continue; + paths.Add(physicalFileProvider.Root); + } + throw new NotSupportedException($"Not found!{Environment.NewLine}{string.Join(Environment.NewLine, paths.Distinct())}"); + } + result = new(resultSettings, metadataSettings, renameSettings); + Verify(result); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(AppSettings))] +internal partial class AppSettingsSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/src/OriginalToDeterministicHashCode/Models/Identifier.cs b/src/OriginalToDeterministicHashCode/Models/Identifier.cs new file mode 100644 index 0000000..a2d6102 --- /dev/null +++ b/src/OriginalToDeterministicHashCode/Models/Identifier.cs @@ -0,0 +1,32 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OriginalToDeterministicHashCode.Models; + +internal sealed record Identifier(string[] DirectoryNames, + bool? HasDateTimeOriginal, + int Id, + long Length, + string PaddedId, + long Ticks) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, IdentifierSourceGenerationContext.Default.Identifier); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Identifier))] +internal partial class IdentifierSourceGenerationContext : JsonSerializerContext +{ +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Identifier[]))] +internal partial class IdentifierCollectionSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/src/OriginalToDeterministicHashCode/Models/RenameSettings.cs b/src/OriginalToDeterministicHashCode/Models/RenameSettings.cs new file mode 100644 index 0000000..6e44517 --- /dev/null +++ b/src/OriginalToDeterministicHashCode/Models/RenameSettings.cs @@ -0,0 +1,38 @@ + +using System.Text.Json; +using System.Text.Json.Serialization; +using View_by_Distance.Shared.Models.Properties; + +namespace OriginalToDeterministicHashCode.Models; + +public record RenameSettings(string Company, + string DefaultMaker, + bool ForceNewId, + string[] IgnoreExtensions, + bool InPlace, + bool InPlaceMoveDirectory, + bool InPlaceWithOriginalName, + int MaxDegreeOfParallelism, + int MaxMilliSecondsPerCall, + bool OnlySaveIdentifiersToDisk, + string RelativePropertyCollectionFile, + bool RequireRootDirectoryExists, + string[] SidecarExtensions, + bool SkipIdFiles, + string[] ValidImageFormatExtensions, + string[] ValidVideoFormatExtensions) : IRenameSettings +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, RenameSettingsSourceGenerationContext.Default.RenameSettings); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(RenameSettings))] +internal partial class RenameSettingsSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/src/OriginalToDeterministicHashCode/OriginalToDeterministicHashCode.csproj b/src/OriginalToDeterministicHashCode/OriginalToDeterministicHashCode.csproj new file mode 100644 index 0000000..abebe2c --- /dev/null +++ b/src/OriginalToDeterministicHashCode/OriginalToDeterministicHashCode.csproj @@ -0,0 +1,20 @@ + + + enable + disable + net8.0 + win-x64;linux-x64 + 2f63ace9-efe5-4b0a-9ebe-529309f33e3f + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/OriginalToDeterministicHashCode/Program.cs b/src/OriginalToDeterministicHashCode/Program.cs new file mode 100644 index 0000000..72cc963 --- /dev/null +++ b/src/OriginalToDeterministicHashCode/Program.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using OriginalToDeterministicHashCode.DependencyInjection; +using OriginalToDeterministicHashCode.Models; + +WebApplicationBuilder webApplicationBuilder = WebApplication.CreateBuilder(args); +{ + _ = webApplicationBuilder.Services.AddServices(); + _ = webApplicationBuilder.Services.AddControllers(); + _ = webApplicationBuilder.Configuration.AddUserSecrets(); + AppSettings appSettings = AppSettings.Get(webApplicationBuilder.Configuration); + _ = webApplicationBuilder.Services.AddSingleton(_ => appSettings); +} +WebApplication webApplication = webApplicationBuilder.Build(); +{ + _ = webApplication.MapControllers(); +} +ILogger? logger = webApplication.Services.GetRequiredService>(); +logger.LogInformation("Starting Web Application"); +webApplication.Run(); diff --git a/src/OriginalToDeterministicHashCode/Services/RenameService.cs b/src/OriginalToDeterministicHashCode/Services/RenameService.cs new file mode 100644 index 0000000..c642e70 --- /dev/null +++ b/src/OriginalToDeterministicHashCode/Services/RenameService.cs @@ -0,0 +1,582 @@ +using CliWrap; +using Microsoft.Extensions.Logging; +using OriginalToDeterministicHashCode.Models; +using ShellProgressBar; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using View_by_Distance.Metadata.Models; +using View_by_Distance.Metadata.Models.Stateless; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; +using View_by_Distance.Shared.Models.Stateless; + +namespace OriginalToDeterministicHashCode.Services; + +public class RenameService : IRename, IDisposable +{ + + private sealed record ToDo(string? Directory, FilePath FilePath, string File, bool JsonFile); + + private sealed record RecordA(ExifDirectory ExifDirectory, bool FastForwardMovingPictureExpertsGroupUsed, FileInfo FileInfo, FilePath FilePath, ReadOnlyCollection SidecarFiles); + + private sealed record RecordB(DateTime DateTime, ExifDirectory ExifDirectory, bool FastForwardMovingPictureExpertsGroupUsed, FilePath FilePath, ReadOnlyCollection SidecarFiles, bool HasDateTimeOriginal, bool HasIgnoreKeyword, string JsonFile); + + private ProgressBar? _ProgressBar; + private readonly AppSettings _AppSettings; + private readonly ILogger _Logger; + + public RenameService(ILogger logger, AppSettings appSettings) + { + _Logger = logger; + _AppSettings = appSettings; + } + + void IRename.Tick() => + _ProgressBar?.Tick(); + + void IDisposable.Dispose() + { + _ProgressBar?.Dispose(); + GC.SuppressFinalize(this); + } + + ReadOnlyCollection IRename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(IRenameSettings renameSettings, FilePath filePath) + { + List results = []; + bool isValidVideoFormatExtensions = renameSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered); + if (isValidVideoFormatExtensions) + { + bool check; + try + { + CommandTask commandTask = Cli.Wrap("L:/Git/ffmpeg-2024-10-02-git-358fdf3083-full_build/bin/ffmpeg.exe") + .WithArguments(["-i", filePath.FullName, "-vf", "select=eq(n\\,0)", "-q:v", "1", $"{filePath.Name}-%4d.jpg"]) + .WithWorkingDirectory(filePath.DirectoryFullPath) + .ExecuteAsync(); + commandTask.Task.Wait(); + check = true; + } + catch (Exception) + { + check = false; + } + if (check) + { + results.AddRange(Directory.GetFiles(filePath.DirectoryFullPath, $"{filePath.Name}-*.jpg", SearchOption.TopDirectoryOnly)); + if (results.Count == 0) + throw new Exception(); + File.SetCreationTime(results[0], new(filePath.CreationTicks)); + File.SetLastWriteTime(results[0], new(filePath.LastWriteTicks)); + Thread.Sleep(100); + } + } + return results.AsReadOnly(); + } + +#pragma warning disable CA1416 + + DeterministicHashCode IRename.GetDeterministicHashCode(FilePath filePath) + { + DeterministicHashCode result; + int? id; + int? width; + int? height; + try + { + using Image image = Image.FromFile(filePath.FullName); + width = image.Width; + height = image.Height; + using Bitmap bitmap = new(image); + Rectangle rectangle = new(0, 0, image.Width, image.Height); + BitmapData bitmapData = bitmap.LockBits(rectangle, ImageLockMode.ReadOnly, bitmap.PixelFormat); + IntPtr intPtr = bitmapData.Scan0; + int length = bitmapData.Stride * bitmap.Height; + byte[] bytes = new byte[length]; + Marshal.Copy(intPtr, bytes, 0, length); + bitmap.UnlockBits(bitmapData); + id = IId.GetDeterministicHashCode(bytes); + } + catch (Exception) + { + id = null; + width = null; + height = null; + } + result = new(height, id, width); + return result; + } + +#pragma warning restore CA1416 + + private void NonParallelismAndInPlace(AppSettings appSettings, ReadOnlyCollection ids, ExifDirectory exifDirectory, FileInfo fileInfo, FilePath filePath, bool fastForwardMovingPictureExpertsGroupUsed, ReadOnlyCollection sidecarFiles) + { + if (exifDirectory.FilePath.Id is null) + throw new NotImplementedException(); + int i = 0; + ToDo toDo; + const string jpg = ".jpg"; + const string jpeg = ".jpeg"; + List toDoCollection = []; + DateTime? dateTime = IDate.GetDateTimeOriginal(exifDirectory); + ReadOnlyCollection keywords = IMetadata.GetKeywords(exifDirectory); + bool hasIgnoreKeyword = appSettings.MetadataSettings.IgnoreRulesKeyWords.Any(keywords.Contains); + string checkFileExtension = filePath.ExtensionLowered == jpeg ? jpg : filePath.ExtensionLowered; + bool hasDateTimeOriginal = dateTime is not null; + string paddedId = IId.GetPaddedId(appSettings.ResultSettings, appSettings.MetadataSettings, exifDirectory.FilePath.Id.Value, hasIgnoreKeyword, hasDateTimeOriginal, i); + string checkDirectory = appSettings.RenameSettings.InPlaceWithOriginalName ? Path.Combine(filePath.DirectoryFullPath, filePath.FileNameFirstSegment) : filePath.DirectoryFullPath; + string checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}"); + if (checkFile != filePath.FullName) + { + if (File.Exists(checkFile)) + { + checkFile = string.Concat(checkFile, ".del"); + if (File.Exists(checkFile)) + throw new NotImplementedException(); + } + toDo = new(checkDirectory, filePath, checkFile, JsonFile: false); + toDoCollection.Add(toDo); + if (sidecarFiles.Count != 0) + { + if (appSettings.RenameSettings.InPlace) + throw new NotSupportedException($"Must use {nameof(appSettings.RenameSettings.InPlaceWithOriginalName)} when sidecar file(s) are present!"); + dateTime ??= IDate.GetMinimum(exifDirectory); + RecordB recordB = new(dateTime.Value, exifDirectory, fastForwardMovingPictureExpertsGroupUsed, filePath, sidecarFiles, hasDateTimeOriginal, hasIgnoreKeyword, fileInfo.FullName); + toDoCollection.AddRange(GetSidecarFiles(appSettings, recordB, [], checkDirectory, paddedId)); + } + _ = RenameFilesInDirectories(appSettings.RenameSettings, new(toDoCollection)); + string jsonFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}.json"); + File.Move(fileInfo.FullName, jsonFile, overwrite: true); + if (appSettings.RenameSettings.InPlaceWithOriginalName && ids.Count > 0) + { + string contains = ids.Contains(exifDirectory.FilePath.Id.Value) ? "_ Exists _" : "_ New _"; + string idCheck = Path.Combine(checkDirectory, contains, fastForwardMovingPictureExpertsGroupUsed ? "Video" : "Image"); + if (!Directory.Exists(idCheck)) + _ = Directory.CreateDirectory(idCheck); + } + } + } + + private List GetRecordACollection(ILogger? logger, AppSettings appSettings, IRename rename, long ticks, ReadOnlyCollection ids, IEnumerable files, A_Metadata metadata) + { + List results = []; + int index = -1; + RecordA recordA; + FileInfo fileInfo; + FilePath filePath; + TimeSpan timeSpan; + string directoryName; + ExifDirectory exifDirectory; + List sidecarFiles; + DeterministicHashCode deterministicHashCode; + bool fastForwardMovingPictureExpertsGroupUsed; + FilePath? fastForwardMovingPictureExpertsGroupFilePath; + ReadOnlyCollection? fastForwardMovingPictureExpertsGroupFiles; + ReadOnlyDictionary> keyValuePairs = IMetadata.GetKeyValuePairs(files); + foreach (KeyValuePair> keyValuePair in keyValuePairs) + { + index += 1; + rename.Tick(); + if (keyValuePair.Value.Count > 1 && !appSettings.RenameSettings.ForceNewId) + { + if (appSettings.RenameSettings.InPlaceMoveDirectory) + continue; + throw new NotSupportedException($"When sidecar files are present {nameof(appSettings.RenameSettings.ForceNewId)} must be true!"); + } + if (keyValuePair.Value.Count > 2) + throw new NotSupportedException("Too many sidecar files!"); + foreach (FileHolder fileHolder in keyValuePair.Value) + { + timeSpan = new(DateTime.Now.Ticks - ticks); + if (appSettings.RenameSettings.MaxMilliSecondsPerCall > timeSpan.TotalMilliseconds) + break; + if (appSettings.RenameSettings.SidecarExtensions.Contains(fileHolder.ExtensionLowered)) + continue; + if (appSettings.RenameSettings.IgnoreExtensions.Contains(fileHolder.ExtensionLowered)) + continue; + filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index); + if (appSettings.RenameSettings.SkipIdFiles && filePath.Id is not null && (filePath.IsIntelligentIdFormat || filePath.SortOrder is not null)) + continue; + if (!appSettings.RenameSettings.ForceNewId && filePath.Id is not null) + { + fastForwardMovingPictureExpertsGroupFiles = null; + deterministicHashCode = new(null, filePath.Id, null); + directoryName = Path.GetFileName(filePath.DirectoryFullPath); + if (appSettings.RenameSettings.InPlaceWithOriginalName || (appSettings.RenameSettings.InPlace && directoryName.EndsWith(filePath.Id.Value.ToString()))) + continue; + } + else + { + fastForwardMovingPictureExpertsGroupFiles = rename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(appSettings.RenameSettings, filePath); + fastForwardMovingPictureExpertsGroupFilePath = fastForwardMovingPictureExpertsGroupFiles.Count == 0 ? null : FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, FileHolder.Get(fastForwardMovingPictureExpertsGroupFiles[0]), index); + deterministicHashCode = fastForwardMovingPictureExpertsGroupFilePath is null ? rename.GetDeterministicHashCode(filePath) : rename.GetDeterministicHashCode(fastForwardMovingPictureExpertsGroupFilePath); + } + sidecarFiles = []; + filePath = FilePath.Get(filePath, deterministicHashCode); + for (int i = 0; i < keyValuePair.Value.Count; i++) + { + if (keyValuePair.Value[i].ExtensionLowered == fileHolder.ExtensionLowered) + continue; + sidecarFiles.Add(keyValuePair.Value[i]); + } + try + { (fileInfo, exifDirectory) = metadata.GetMetadataCollection(appSettings.ResultSettings, appSettings.MetadataSettings, filePath); } + catch (Exception) + { + logger?.LogWarning("<{filePath}>", filePath.FullName); + continue; + } + fastForwardMovingPictureExpertsGroupUsed = fastForwardMovingPictureExpertsGroupFiles is not null && fastForwardMovingPictureExpertsGroupFiles.Count > 0; + if (fastForwardMovingPictureExpertsGroupUsed && fastForwardMovingPictureExpertsGroupFiles is not null) + { + foreach (string fastForwardMovingPictureExpertsGroupFile in fastForwardMovingPictureExpertsGroupFiles) + File.Delete(fastForwardMovingPictureExpertsGroupFile); + } + if (appSettings.RenameSettings.InPlace || appSettings.RenameSettings.InPlaceWithOriginalName) + NonParallelismAndInPlace(appSettings, ids, exifDirectory, fileInfo, filePath, fastForwardMovingPictureExpertsGroupUsed, new(sidecarFiles)); + if (!fastForwardMovingPictureExpertsGroupUsed && appSettings.RenameSettings.InPlaceMoveDirectory && appSettings.RenameSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered)) + fastForwardMovingPictureExpertsGroupUsed = true; + recordA = new(exifDirectory, fastForwardMovingPictureExpertsGroupUsed, fileInfo, filePath, new(sidecarFiles)); + results.Add(recordA); + } + } + return results; + } + + private static ReadOnlyCollection GetRecordBCollection(AppSettings appSettings, List recordACollection) + { + List results = []; + RecordB recordB; + DateTime? dateTime; + bool hasIgnoreKeyword; + bool hasDateTimeOriginal; + ReadOnlyCollection keywords; + foreach (RecordA recordA in recordACollection) + { + dateTime = IDate.GetDateTimeOriginal(recordA.ExifDirectory); + hasDateTimeOriginal = dateTime is not null; + dateTime ??= IDate.GetMinimum(recordA.ExifDirectory); + keywords = IMetadata.GetKeywords(recordA.ExifDirectory); + hasIgnoreKeyword = appSettings.MetadataSettings.IgnoreRulesKeyWords.Any(l => keywords.Contains(l)); + recordB = new(dateTime.Value, recordA.ExifDirectory, recordA.FastForwardMovingPictureExpertsGroupUsed, recordA.FilePath, recordA.SidecarFiles, hasDateTimeOriginal, hasIgnoreKeyword, recordA.FileInfo.FullName); + results.Add(recordB); + } + return results.AsReadOnly(); + } + + private ReadOnlyCollection GetRecordBCollection(ILogger? logger, AppSettings appSettings, IRename rename, long ticks, ReadOnlyCollection ids, DirectoryInfo directoryInfo) + { + ReadOnlyCollection results; + RecordA recordA; + List recordACollection = []; + A_Metadata metadata = new(appSettings.ResultSettings, appSettings.MetadataSettings); + int appSettingsMaxDegreeOfParallelism = appSettings.RenameSettings.MaxDegreeOfParallelism; + IEnumerable files = appSettingsMaxDegreeOfParallelism == 1 ? Directory.GetFiles(directoryInfo.FullName, "*", SearchOption.AllDirectories) : Directory.EnumerateFiles(directoryInfo.FullName, "*", SearchOption.AllDirectories); + int filesCount = appSettingsMaxDegreeOfParallelism == 1 ? files.Count() : 123000; + _ProgressBar = new(filesCount, "EnumerateFiles load", new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }); + if (appSettingsMaxDegreeOfParallelism == 1) + recordACollection.AddRange(GetRecordACollection(logger, appSettings, rename, ticks, ids, files, metadata)); + else + { + List distinct = []; + List metadataGroups = []; + ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = appSettingsMaxDegreeOfParallelism }; + files.AsParallel().ForAll(IMetadata.SetExifDirectoryCollection(rename, appSettings.ResultSettings, appSettings.MetadataSettings, appSettings.RenameSettings, metadata, distinct, metadataGroups)); + if (_ProgressBar.CurrentTick != recordACollection.Count) + throw new NotSupportedException(); + foreach (MetadataGroup metadataGroup in metadataGroups) + { + if (metadataGroup.FastForwardMovingPictureExpertsGroupUsed || !appSettings.RenameSettings.InPlaceMoveDirectory || !appSettings.RenameSettings.ValidVideoFormatExtensions.Contains(metadataGroup.FilePath.ExtensionLowered)) + recordA = new(metadataGroup.ExifDirectory, metadataGroup.FastForwardMovingPictureExpertsGroupUsed, metadataGroup.FileInfo, metadataGroup.FilePath, metadataGroup.SidecarFiles); + else + recordA = new(metadataGroup.ExifDirectory, FastForwardMovingPictureExpertsGroupUsed: true, metadataGroup.FileInfo, metadataGroup.FilePath, metadataGroup.SidecarFiles); + recordACollection.Add(recordA); + } + } + _ProgressBar.Dispose(); + results = GetRecordBCollection(appSettings, recordACollection); + return results; + } + + private static void VerifyIntMinValueLength(MetadataSettings metadataSettings, ReadOnlyCollection recordBCollection) + { + foreach (RecordB recordB in recordBCollection) + { + if (recordB.ExifDirectory.FilePath.Id is null) + continue; + if (metadataSettings.IntMinValueLength < recordB.ExifDirectory.FilePath.Id.Value.ToString().Length) + throw new NotSupportedException(); + } + } + + private static string GetTFW(RecordB record, bool? isWrongYear) => + string.Concat(record.HasDateTimeOriginal ? "T" : "F", isWrongYear is not null && isWrongYear.Value ? "W" : record.FastForwardMovingPictureExpertsGroupUsed ? "V" : "I"); + + private static string GetDirectoryName(string year, string tfw, string prefix, string? splat, int seasonValue, string seasonName, string makerSplit) => + splat is null ? $"{prefix}{year} {tfw}{year}.{seasonValue} {seasonName}{makerSplit}" : $"{prefix}{year} {tfw}{year}{splat}"; + + private static string? GetCheckDirectory(AppSettings appSettings, RecordB record, ReadOnlyCollection ids, bool multipleDirectoriesWithFiles, string paddedId) + { + string? result; + string year = record.DateTime.Year.ToString(); + string checkDirectoryName = Path.GetFileName(record.FilePath.DirectoryFullPath); + if (multipleDirectoriesWithFiles && !checkDirectoryName.Contains(year)) + result = null; + else + { + (bool? isWrongYear, string[] years) = IDate.IsWrongYear(record.FilePath, record.ExifDirectory); + if (appSettings.RenameSettings.InPlaceMoveDirectory && !record.FilePath.FileNameFirstSegment.Contains(paddedId)) + result = null; + else + { + string tfw = GetTFW(record, isWrongYear); + string? maker = IMetadata.GetMaker(record.ExifDirectory); + string rootDirectory = appSettings.ResultSettings.RootDirectory; + string[] segments = checkDirectoryName.Split(years, StringSplitOptions.None); + (int seasonValue, string seasonName) = IDate.GetSeason(record.DateTime.DayOfYear); + string? splat = checkDirectoryName.Length > 3 && checkDirectoryName[^3..][1] == '!' ? checkDirectoryName[^3..] : null; + string contains = record.ExifDirectory.FilePath.Id is null || ids.Contains(record.ExifDirectory.FilePath.Id.Value) ? "_ Exists _" : "_ New-Destination _"; + string makerSplit = string.IsNullOrEmpty(maker) ? string.IsNullOrEmpty(appSettings.RenameSettings.DefaultMaker) ? string.Empty : appSettings.RenameSettings.DefaultMaker : $" {maker.Split(' ')[0]}"; + string directoryName = GetDirectoryName(year, tfw, segments[0], splat, seasonValue, seasonName, makerSplit); + result = Path.GetFullPath(Path.Combine(rootDirectory, contains, directoryName)); + } + } + return result; + } + + private static List GetSidecarFiles(AppSettings appSettings, RecordB record, List distinct, string checkDirectory, string paddedId) + { + List results = []; + ToDo toDo; + string checkFile; + FilePath filePath; + string checkFileExtension; + foreach (FileHolder fileHolder in record.SidecarFiles) + { + checkFileExtension = fileHolder.ExtensionLowered; + filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index: null); + checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}"); + if (checkFile == filePath.FullName) + continue; + if (File.Exists(checkFile)) + { + checkFile = string.Concat(checkFile, ".del"); + if (File.Exists(checkFile)) + continue; + } + if (distinct.Contains(checkFile)) + continue; + distinct.Add(checkFile); + toDo = new(checkDirectory, filePath, checkFile, JsonFile: false); + results.Add(toDo); + } + return results; + } + + private static bool? GetDirectoryCheck(ResultSettings resultSettings) + { + bool? result = null; + IEnumerable files; + string[] directories = Directory.GetDirectories(resultSettings.RootDirectory, "*", SearchOption.TopDirectoryOnly); + foreach (string directory in directories) + { + files = Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories); + foreach (string _ in files) + { + if (result is null) + result = false; + else if (result.Value) + result = true; + break; + } + if (result is not null && result.Value) + break; + } + return result; + } + + private static ReadOnlyCollection GetToDoCollection(AppSettings appSettings, ReadOnlyCollection ids, ReadOnlyCollection recordBCollection) + { + List results = []; + ToDo toDo; + RecordB record; + string jsonFile; + string paddedId; + string checkFile; + FilePath filePath; + string directoryName; + FileHolder fileHolder; + string? checkDirectory; + const string jpg = ".jpg"; + string checkFileExtension; + List distinct = []; + const string jpeg = ".jpeg"; + string jsonFileSubDirectory; + bool? directoryCheck = GetDirectoryCheck(appSettings.ResultSettings); + VerifyIntMinValueLength(appSettings.MetadataSettings, recordBCollection); + bool multipleDirectoriesWithFiles = directoryCheck is not null && directoryCheck.Value; + ReadOnlyCollection sorted = (from l in recordBCollection orderby l.DateTime select l).ToArray().AsReadOnly(); + for (int i = 0; i < sorted.Count; i++) + { + record = sorted[i]; + if (record.ExifDirectory.FilePath.Id is null) + continue; + paddedId = IId.GetPaddedId(appSettings.ResultSettings, appSettings.MetadataSettings, record.ExifDirectory.FilePath.Id.Value, record.HasIgnoreKeyword, record.HasDateTimeOriginal, i); + checkDirectory = GetCheckDirectory(appSettings, record, ids, multipleDirectoriesWithFiles, paddedId); + if (string.IsNullOrEmpty(checkDirectory)) + continue; + checkFileExtension = record.FilePath.ExtensionLowered == jpeg ? jpg : record.FilePath.ExtensionLowered; + jsonFileSubDirectory = Path.GetDirectoryName(Path.GetDirectoryName(record.JsonFile)) ?? throw new Exception(); + checkFile = Path.Combine(checkDirectory, $"{paddedId}{checkFileExtension}"); + if (checkFile == record.FilePath.FullName) + continue; + if (File.Exists(checkFile)) + { + checkFile = string.Concat(checkFile, ".del"); + if (File.Exists(checkFile)) + continue; + } + (directoryName, _) = IPath.GetDirectoryNameAndIndex(appSettings.ResultSettings, record.ExifDirectory.FilePath.Id.Value); + jsonFile = Path.Combine(jsonFileSubDirectory, directoryName, $"{record.ExifDirectory.FilePath.Id.Value}{checkFileExtension}.json"); + if (record.JsonFile != jsonFile) + { + fileHolder = FileHolder.Get(record.JsonFile); + filePath = FilePath.Get(appSettings.ResultSettings, appSettings.MetadataSettings, fileHolder, index: null); + toDo = new(null, filePath, jsonFile, JsonFile: true); + results.Add(toDo); + } + if (distinct.Contains(checkFile)) + continue; + distinct.Add(checkFile); + toDo = new(checkDirectory, record.FilePath, checkFile, JsonFile: false); + results.Add(toDo); + if (record.SidecarFiles.Count == 0) + continue; + results.AddRange(GetSidecarFiles(appSettings, record, distinct, checkDirectory, paddedId)); + } + return results.AsReadOnly(); + } + + private static void VerifyDirectories(ReadOnlyCollection toDoCollection) + { + List distinct = []; + foreach (ToDo toDo in toDoCollection) + { + if (toDo.Directory is null || distinct.Contains(toDo.Directory)) + continue; + if (!Directory.Exists(toDo.Directory)) + _ = Directory.CreateDirectory(toDo.Directory); + distinct.Add(toDo.Directory); + } + } + + private ReadOnlyCollection RenameFilesInDirectories(RenameSettings renameSettings, ReadOnlyCollection toDoCollection) + { + List results = []; + VerifyDirectories(toDoCollection); + bool useProgressBar = !renameSettings.InPlace && !renameSettings.InPlaceWithOriginalName; + if (useProgressBar) + _ProgressBar = new(toDoCollection.Count, "Move Files", new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }); + foreach (ToDo toDo in toDoCollection) + { + if (useProgressBar) + _ProgressBar?.Tick(); + if (toDo.JsonFile) + { + if (File.Exists(toDo.File)) + File.Delete(toDo.File); + try + { File.Move(toDo.FilePath.FullName, toDo.File); } + catch (Exception) + { continue; } + } + else if (toDo.Directory is null) + throw new NotSupportedException(); + else + { + if (File.Exists(toDo.File)) + File.Delete(toDo.File); + try + { File.Move(toDo.FilePath.FullName, toDo.File); } + catch (Exception) + { continue; } + results.Add($"{toDo.FilePath.FullName}\t{toDo.File}"); + } + } + if (useProgressBar) + _ProgressBar?.Dispose(); + return results.AsReadOnly(); + } + + private static void SaveIdentifiersToDisk(long ticks, AppSettings appSettings, ReadOnlyCollection recordBCollection) + { + string paddedId; + Identifier identifier; + List identifiers = []; + string aMetadataCollectionDirectory = IResult.GetResultsDateGroupDirectory(appSettings.ResultSettings, nameof(A_Metadata), appSettings.ResultSettings.ResultCollection); + foreach (RecordB record in recordBCollection) + { + if (record.ExifDirectory.FilePath.Id is null) + continue; + paddedId = IId.GetPaddedId(appSettings.ResultSettings, appSettings.MetadataSettings, record.ExifDirectory.FilePath.Id.Value, record.HasIgnoreKeyword, record.HasDateTimeOriginal, index: null); + identifier = new([], record.HasDateTimeOriginal, record.ExifDirectory.FilePath.Id.Value, record.FilePath.Length, paddedId, record.DateTime.Ticks); + identifiers.Add(identifier); + } + string json = JsonSerializer.Serialize(identifiers.OrderBy(l => l.PaddedId).ToArray(), IdentifierCollectionSourceGenerationContext.Default.IdentifierArray); + _ = IPath.WriteAllText(Path.Combine(aMetadataCollectionDirectory, $"{ticks}.json"), json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null); + } + + private static ReadOnlyCollection GetIds(RenameSettings renameSettings) + { + ReadOnlyCollection results; + string? propertyCollectionFile = string.IsNullOrEmpty(renameSettings.RelativePropertyCollectionFile) ? null : renameSettings.RelativePropertyCollectionFile; + string? json = !File.Exists(propertyCollectionFile) ? null : File.ReadAllText(propertyCollectionFile); + Identifier[]? identifiers = json is null ? null : JsonSerializer.Deserialize(json, IdentifierCollectionSourceGenerationContext.Default.IdentifierArray); + if (identifiers is null && !string.IsNullOrEmpty(renameSettings.RelativePropertyCollectionFile)) + throw new Exception($"Invalid {nameof(renameSettings.RelativePropertyCollectionFile)}"); + results = identifiers is null ? new([]) : new((from l in identifiers select l.Id).ToArray()); + return results; + } + + private void RenameWork(ILogger? logger, AppSettings appSettings, IRename rename, long ticks) + { + ReadOnlyCollection ids = GetIds(appSettings.RenameSettings); + _ = IPath.DeleteEmptyDirectories(appSettings.ResultSettings.RootDirectory); + DirectoryInfo directoryInfo = new(Path.GetFullPath(appSettings.ResultSettings.RootDirectory)); + logger?.LogInformation("{Ticks} {RootDirectory}", ticks, directoryInfo.FullName); + ReadOnlyCollection recordBCollection = GetRecordBCollection(logger, appSettings, rename, ticks, ids, directoryInfo); + SaveIdentifiersToDisk(ticks, appSettings, recordBCollection); + if (appSettings.RenameSettings.InPlace || appSettings.RenameSettings.InPlaceWithOriginalName) + { + if (recordBCollection.Count > 0) + recordBCollection = new([]); + string aMetadataSingletonDirectory = IResult.GetResultsGroupDirectory(appSettings.ResultSettings, nameof(A_Metadata)); + _ = IPath.DeleteEmptyDirectories(aMetadataSingletonDirectory); + } + if (!appSettings.RenameSettings.OnlySaveIdentifiersToDisk) + { + ReadOnlyCollection toDoCollection = GetToDoCollection(appSettings, ids, recordBCollection); + ReadOnlyCollection lines = RenameFilesInDirectories(appSettings.RenameSettings, toDoCollection); + if (lines.Count != 0) + { + File.WriteAllLines($"D:/Tmp/Phares/{DateTime.Now.Ticks}.tsv", lines); + _ = IPath.DeleteEmptyDirectories(directoryInfo.FullName); + } + } + } + + public void RenameFiles() + { + IRename rename = this; + long ticks = DateTime.Now.Ticks; + RenameWork(_Logger, _AppSettings, rename, ticks); + } +} \ No newline at end of file