Ready to test in WSL

This commit is contained in:
Mike Phares 2025-02-08 11:44:28 -07:00
commit 89413fe4b2
22 changed files with 1874 additions and 0 deletions

360
.editorconfig Normal file
View File

@ -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

339
.gitignore vendored Normal file
View File

@ -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

17
.vscode/bash.md vendored Normal file
View File

@ -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
```

1
.vscode/format-report.json vendored Normal file
View File

@ -0,0 +1 @@
[]

33
.vscode/launch.json vendored Normal file
View File

@ -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}"
}
]
}

9
.vscode/mklink.md vendored Normal file
View File

@ -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"
```

12
.vscode/readme.md vendored Normal file
View File

@ -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

38
.vscode/settings.json vendored Normal file
View File

@ -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"
}
}
}

176
.vscode/tasks.json vendored Normal file
View File

@ -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"
}
]
}

25
Dockerfile Normal file
View File

@ -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" ]

View File

@ -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

33
docker-compose.yaml Normal file
View File

@ -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: {}

6
global.json Normal file
View File

@ -0,0 +1,6 @@
{
"sdk": {
"rollForward": "latestMinor",
"version": "8.0.100"
}
}

View File

@ -0,0 +1,6 @@
@host = https://localhost:5003
GET {{host}}/rename/
Accept: application/json
###

View File

@ -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();
}
}

View File

@ -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<RenameService>();
return services;
}
}

View File

@ -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<ResultSettings>();
MetadataSettings? metadataSettings = configurationRoot.GetSection(nameof(MetadataSettings)).Get<MetadataSettings>();
RenameSettings? renameSettings = configurationRoot.GetSection(nameof(RenameSettings)).Get<RenameSettings>();
#pragma warning restore IL3050, IL2026
if (resultSettings is null || metadataSettings is null || renameSettings?.Company is null)
{
List<string> 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
{
}

View File

@ -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
{
}

View File

@ -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
{
}

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
<UserSecretsId>2f63ace9-efe5-4b0a-9ebe-529309f33e3f</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CliWrap" Version="3.7.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="ShellProgressBar" Version="5.2.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Phares.AA.Shared" Version="8.0.112" />
<PackageReference Include="Phares.AA.Metadata " Version="8.0.112" />
</ItemGroup>
</Project>

View File

@ -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<Program>();
AppSettings appSettings = AppSettings.Get(webApplicationBuilder.Configuration);
_ = webApplicationBuilder.Services.AddSingleton(_ => appSettings);
}
WebApplication webApplication = webApplicationBuilder.Build();
{
_ = webApplication.MapControllers();
}
ILogger<Program>? logger = webApplication.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Starting Web Application");
webApplication.Run();

View File

@ -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<FileHolder> SidecarFiles);
private sealed record RecordB(DateTime DateTime, ExifDirectory ExifDirectory, bool FastForwardMovingPictureExpertsGroupUsed, FilePath FilePath, ReadOnlyCollection<FileHolder> SidecarFiles, bool HasDateTimeOriginal, bool HasIgnoreKeyword, string JsonFile);
private ProgressBar? _ProgressBar;
private readonly AppSettings _AppSettings;
private readonly ILogger<RenameService> _Logger;
public RenameService(ILogger<RenameService> logger, AppSettings appSettings)
{
_Logger = logger;
_AppSettings = appSettings;
}
void IRename.Tick() =>
_ProgressBar?.Tick();
void IDisposable.Dispose()
{
_ProgressBar?.Dispose();
GC.SuppressFinalize(this);
}
ReadOnlyCollection<string> IRename.ConvertAndGetFastForwardMovingPictureExpertsGroupFiles(IRenameSettings renameSettings, FilePath filePath)
{
List<string> results = [];
bool isValidVideoFormatExtensions = renameSettings.ValidVideoFormatExtensions.Contains(filePath.ExtensionLowered);
if (isValidVideoFormatExtensions)
{
bool check;
try
{
CommandTask<CommandResult> 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<int> ids, ExifDirectory exifDirectory, FileInfo fileInfo, FilePath filePath, bool fastForwardMovingPictureExpertsGroupUsed, ReadOnlyCollection<FileHolder> sidecarFiles)
{
if (exifDirectory.FilePath.Id is null)
throw new NotImplementedException();
int i = 0;
ToDo toDo;
const string jpg = ".jpg";
const string jpeg = ".jpeg";
List<ToDo> toDoCollection = [];
DateTime? dateTime = IDate.GetDateTimeOriginal(exifDirectory);
ReadOnlyCollection<string> 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<RecordA> GetRecordACollection(ILogger<RenameService>? logger, AppSettings appSettings, IRename rename, long ticks, ReadOnlyCollection<int> ids, IEnumerable<string> files, A_Metadata metadata)
{
List<RecordA> results = [];
int index = -1;
RecordA recordA;
FileInfo fileInfo;
FilePath filePath;
TimeSpan timeSpan;
string directoryName;
ExifDirectory exifDirectory;
List<FileHolder> sidecarFiles;
DeterministicHashCode deterministicHashCode;
bool fastForwardMovingPictureExpertsGroupUsed;
FilePath? fastForwardMovingPictureExpertsGroupFilePath;
ReadOnlyCollection<string>? fastForwardMovingPictureExpertsGroupFiles;
ReadOnlyDictionary<string, List<FileHolder>> keyValuePairs = IMetadata.GetKeyValuePairs(files);
foreach (KeyValuePair<string, List<FileHolder>> 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<RecordB> GetRecordBCollection(AppSettings appSettings, List<RecordA> recordACollection)
{
List<RecordB> results = [];
RecordB recordB;
DateTime? dateTime;
bool hasIgnoreKeyword;
bool hasDateTimeOriginal;
ReadOnlyCollection<string> 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<RecordB> GetRecordBCollection(ILogger<RenameService>? logger, AppSettings appSettings, IRename rename, long ticks, ReadOnlyCollection<int> ids, DirectoryInfo directoryInfo)
{
ReadOnlyCollection<RecordB> results;
RecordA recordA;
List<RecordA> recordACollection = [];
A_Metadata metadata = new(appSettings.ResultSettings, appSettings.MetadataSettings);
int appSettingsMaxDegreeOfParallelism = appSettings.RenameSettings.MaxDegreeOfParallelism;
IEnumerable<string> 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<string> distinct = [];
List<MetadataGroup> 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<RecordB> 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<int> 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<ToDo> GetSidecarFiles(AppSettings appSettings, RecordB record, List<string> distinct, string checkDirectory, string paddedId)
{
List<ToDo> 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<string> 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<ToDo> GetToDoCollection(AppSettings appSettings, ReadOnlyCollection<int> ids, ReadOnlyCollection<RecordB> recordBCollection)
{
List<ToDo> 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<string> 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<RecordB> 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<ToDo> toDoCollection)
{
List<string> 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<string> RenameFilesInDirectories(RenameSettings renameSettings, ReadOnlyCollection<ToDo> toDoCollection)
{
List<string> 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<RecordB> recordBCollection)
{
string paddedId;
Identifier identifier;
List<Identifier> 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<int> GetIds(RenameSettings renameSettings)
{
ReadOnlyCollection<int> 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<RenameService>? logger, AppSettings appSettings, IRename rename, long ticks)
{
ReadOnlyCollection<int> 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<RecordB> 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<ToDo> toDoCollection = GetToDoCollection(appSettings, ids, recordBCollection);
ReadOnlyCollection<string> 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);
}
}