commit f7573e95e4e53212c88b44a0f72c27b4888e0337 Author: Mike Phares Date: Sun Oct 22 11:25:08 2023 -0700 AOT Builds diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..92ebca3 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "deadcsharp": { + "version": "2.0.0", + "commands": [ + "dead-csharp" + ] + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3dfc1d0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,263 @@ +[*.md] +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_code_quality_unused_parameters = all +dotnet_code_quality_unused_parameters = non_public # IDE0060: Remove unused parameter +dotnet_code_quality.CAXXXX.api_surface = private, internal +dotnet_diagnostic.CA1825.severity = warning # CA1823: Avoid zero-length array allocations +dotnet_diagnostic.CA1829.severity = warning # CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1834.severity = warning # CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.CA1860.severity = error # CA1860: Prefer comparing 'Count' to 0 rather than using 'Any()', both for clarity and for performance +dotnet_diagnostic.CA1869.severity = none # CA1869: Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. +dotnet_diagnostic.CA2254.severity = none # CA2254: The logging message template should not vary between calls to 'LoggerExtensions.LogInformation(ILogger, string?, params object?[])' +dotnet_diagnostic.IDE0001.severity = warning # IDE0001: Simplify name +dotnet_diagnostic.IDE0002.severity = warning # Simplify (member access) - System.Version.Equals("1", "2"); Version.Equals("1", "2"); +dotnet_diagnostic.IDE0004.severity = warning # IDE0004: Cast is redundant. +dotnet_diagnostic.IDE0005.severity = warning # Using directive is unnecessary +dotnet_diagnostic.IDE0028.severity = error # IDE0028: Collection initialization can be simplified +dotnet_diagnostic.IDE0031.severity = warning # Use null propagation (IDE0031) +dotnet_diagnostic.IDE0047.severity = warning # IDE0047: Parentheses can be removed +dotnet_diagnostic.IDE0049.severity = warning # Use language keywords instead of framework type names for type references (IDE0049) +dotnet_diagnostic.IDE0060.severity = warning # IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0290.severity = none # Use primary constructor [Distance]csharp(IDE0290) +dotnet_diagnostic.IDE0300.severity = error # 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_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..5449c42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,470 @@ +## 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 +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# 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 +nunit-*.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/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.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 + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# 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 +# NuGet Symbol Packages +*.snupkg +# 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 +*.appxbundle +*.appxupload + +# 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 +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# 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/ + +# CodeRush personal settings +.cr/personal + +# 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/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# JetBrains Rider +.idea/ +*.sln.iml + +## +## Visual Studio Code +## +!.vscode/extensions.json +!.vscode/launch.json +!.vscode/settings.json +!.vscode/tasks.json +!Instance/.vscode/appsettings.example.json +!Rename/.vscode/appsettings.example.json +.vscode/helper/** +.vscode/Docker/** +Shared/.vscode/ged +shared/.vscode/helper/** + +#VSCode Settings => mklink /J "VSCode Settings" "C:\Users\phares\AppData\Roaming\Code\User" +globalStorage/ +*workspaceStorage/ + +#Auto Gen Code from Code-Generator +*.gen + +[Ll]ib/ + +Shared/.kanbn diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f24b15b --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ + +**/.vscode/* + +[Bb]in/ +[Oo]bj/ + +*.json + +*.zip \ No newline at end of file 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..0d64020 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,566 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + }, + { + "name": "Compare", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Compare/bin/Debug/net8.0/win-x64/Compare.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Copy-Distinct", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Copy-Distinct/bin/Debug/net8.0/win-x64/Copy-Distinct.dll", + "args": [], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Duplicate-Search", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Duplicate-Search/bin/Debug/net8.0/win-x64/Duplicate-Search.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Date-Group", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Date-Group/bin/Debug/net8.0/win-x64/Date-Group.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Delete-By-Distinct", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Delete-By-Distinct/bin/Debug/net8.0/win-x64/Delete-By-Distinct.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Delete-By-Relative", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Delete-By-Relative/bin/Debug/net8.0/win-x64/Delete-By-Relative.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Drag-Drop", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Drag-Drop/bin/Debug/net8.0-windows/win-x64/Drag-Drop.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Drag-Drop-Explorer", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Drag-Drop-Explorer/bin/Debug/net8.0-windows/win-x64/Drag-Drop-Explorer.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Drag-Drop-Move", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Drag-Drop-Move/bin/Debug/net8.0-windows/win-x64/Drag-Drop-Move.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Drag-Drop-Set-Property-Item", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Drag-Drop-Set-Property-Item/bin/Debug/net8.0-windows/win-x64/Drag-Drop-Set-Property-Item.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Instance", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Instance/bin/Debug/net8.0/win-x64/Instance.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Mirror-Length", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Mirror-Length/bin/Debug/net8.0/win-x64/Mirror-Length.dll", + "args": [], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Metadata-Query", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Metadata-Query/bin/Debug/net8.0/win-x64/Metadata-Query.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Move-By-Id", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Move-By-Id/bin/Debug/net8.0/win-x64/Move-By-Id.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Not-Copy-Copy", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Not-Copy-Copy/bin/Debug/net8.0/win-x64/Not-Copy-Copy.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Offset-Date-Time-Original", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Offset-Date-Time-Original/bin/Debug/net8.0/win-x64/Offset-Date-Time-Original.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "PrepareForOld", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/PrepareForOld/bin/Debug/net8.0/win-x64/PrepareForOld.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Person", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Person/bin/Debug/net8.0/Person.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Rename", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Rename/bin/Debug/net8.0/win-x64/AA.Rename.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + }, + { + "name": "Set-Created-Date", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/Set-Created-Date/bin/Debug/net8.0/win-x64/Set-Created-Date.dll", + "args": [ + "s" + ], + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "stopAtEntry": false, + "requireExactSource": false + } + ] +} +// https://www.amazon.com/photos/ +// "AmazonUsername": "phares36@gmail.com", +// "AmazonPassword": "JlPhgtv@63", +// Keep creating the .json file the same way +// However load all .json files at the beginning +// When looping through all files and file not in collection path&name or date is different from collection value +// Get id in normal fashion +// If id is in collection update collection to new path/name +// If not save and add to collection +// Nicéphore Niépce in 1826 or 182 +// https://dotnet.microsoft.com/en-us/download +// https://git-scm.com/ +// https://code.visualstudio.com/ +// https://github.com/mikepharesjr/VS-Code-Settings +// https://github.com/davisking/dlib-models +// https://github.com/mikepharesjr/View-by-Distance-MKLink-Console +// https://184.103.9.214/login?folder=/home/vscode/Notes&to= +// Notes at 9/18/2022 10:29 PM +// (637987913910140924) 9/14/2022 10:29 PM - 113 *.jpg && 109 *.json +// (637989361172096980) 9/16/2022 02:41 PM - 094 *.jpg && 026 *.json +// All including (637991052364021796) 9/18/2022 1:40 PM - 17435 *.jpg && 16237 *.json = 93.128763980499% with int match only +// List of people with current person key formatted in tab form +// Exclusion list for Robert +// Person birthday allow # +// Host face directory on Ubuntu +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/49158951_2264915190186558_112485584324263936_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=84a396&_nc_ohc=Q78znguHVeMAX88OKg9&_nc_ht=scontent-lax3-2.xx&oh=00_AT_gJFk9xytYuXp9p5smDm0mMDXzsPnFS_EfKEbiL3KYqg&oe=63523F37 +// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/290625560_10217841255422109_3297482250419620718_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=730e14&_nc_ohc=O3ybqwDLK9AAX_V0FGP&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT-9rtBghXU7lAGkOy4KPHbV9Kyj8DGm-BGtUabWgQJbOg&oe=63335209 +// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/243093116_10216723829287154_8982585928017225043_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=730e14&_nc_ohc=TW6UDC8v9egAX-_YcWf&_nc_ht=scontent-lax3-1.xx&oh=00_AT8rE2JOfkJINSsA5imGVBDbXNDvxx1_uZn0sLM-6ITDUw&oe=633238A9 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/281249_1450445399251_3598212_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=ihKT3xtvWXoAX-kiat-&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT9cmbHu00fc0-aR-k6MP7T_cja6geiAkobbvU4pdfG6KQ&oe=63527F21 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/62557_157063120990498_3918482_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=de6eea&_nc_ohc=lV8bmtAes-0AX8Uk9pG&_nc_ht=scontent-lax3-1.xx&oh=00_AT-ieBL8wIZ4gQmRWAvTWDu9wBzAsPV5s4c82MvOs6oU5g&oe=6355846B +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/166577_1634651034600_6777460_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=nEZ0RR0cqn4AX-wA-VH&_nc_ht=scontent-lax3-1.xx&oh=00_AT_ig2HUMDQl1fhXY0Inm0QWC3i2__rPUAWYF2Wqx-pkew&oe=635290D6 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/97107804_10223189072017432_5088375698351980544_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=t0rqlgAAd_0AX-ANM1H&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT_hqG2piNkASRB5z94qeY8q7hy90ku9Dka4enL4wsAnTg&oe=6354CD8A +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/121491463_10159438417238072_1207001349879930424_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=rZTiRaFTrCkAX_L4sVS&_nc_ht=scontent-lax3-2.xx&oh=00_AT_uKD2jeNUTwlyN5r6UXRK40aNNgRNCZrrJ9B-WW6tlag&oe=63522946 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/96370874_10158521458082578_8748970895894118400_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=qvIn4gTjvo0AX-dHnuq&_nc_ht=scontent-lax3-1.xx&oh=00_AT8R-D8AsjWKMlUxK-AimPNzxuNT06zQCS43ESdf1LEeoQ&oe=63562F54 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/96681856_10157495047223435_2857053180632498176_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=UwF0qUGoG9QAX-S1LcI&_nc_ht=scontent-lax3-2.xx&oh=00_AT-5j0EiZB6HOOc2jGqGOll2zFoNTdDLKLfJztq0HYWagg&oe=6355A2A9 +// https://scontent-lax3-1.xx.fbcdn.net/v/t31.18172-8/26850365_1810492538961485_3240264679723665015_o.jpg?_nc_cat=110&ccb=1-7&_nc_sid=0debeb&_nc_ohc=WrJYsgu1eS4AX8kIsyM&_nc_ht=scontent-lax3-1.xx&oh=00_AT-fzFN40kYFjF2-uYL6KcgKPxgFkDZ5BcyIUVFdCTf2tg&oe=635485B7 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/67735718_10219770387799941_2254439277247070208_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=730e14&_nc_ohc=MD8haDYY7uIAX_dwTY4&_nc_ht=scontent-lax3-1.xx&oh=00_AT_zhJpjbWomrihWiSq35KFkmpBfEgbXCSNM68icaABxoA&oe=6355871D +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/59983603_2215207855225638_2271200674882519040_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=-2zmPWdcy9IAX8KXJ_t&_nc_ht=scontent-lax3-2.xx&oh=00_AT_7N0yufGym-cuQfeMK6iceBNI7eEyaScRm3m3_4njTIQ&oe=6354C2B6 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/204501292_10222856568897395_8588510782161393042_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=2gLdnkn19swAX_9mAG1&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT_ha5ncAI7ykhbgFNT1_4NWCvPGFWQLgdq6q20nZSIVQA&oe=63537D09 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/65945099_10217135244827869_3400689284598988800_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=NHnhR9_7UTkAX8wpbxK&_nc_ht=scontent-lax3-1.xx&oh=00_AT_-Q-zUeDoD11B9TY_n_MWEimMiXCokB_5TojplmU6_Eg&oe=6355A2B8 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/22055_581856018827_4131263_n.jpg?_nc_cat=107&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=zsqIe2xzpf0AX8lcyLV&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT_DzvUir_30cauAjp8eaSlYKVADdanzWo1NAQK7_b3M-w&oe=63548E83 +// https://scontent-lax3-1.xx.fbcdn.net/v/t31.18172-8/14525108_10202312892739292_8313619716862825541_o.jpg?_nc_cat=102&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=lZ4MpT6i_MIAX-3d-nV&_nc_ht=scontent-lax3-1.xx&oh=00_AT_9JoZdMdlNV71PdrkU7AmvFkkwqllEg9sVTvcGyzoESw&oe=6353993A +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/104325415_860091804515928_5960365004832843376_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=174925&_nc_ohc=y1AW-ld80OcAX_927wW&_nc_oc=AQluU8Pwuqxs9xBVYhQJ9ThWOh5vLIKvQdOQnCtZpN2-j-6d9PRMHWHR5aaFBkcJicA&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT8vQwiLvz7cJU61FkIPLtcc8j8tlL2tC6zRlH1i20VUmQ&oe=6356EACE +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/43565411_10217177505092918_6185540976904241152_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=730e14&_nc_ohc=yBls9BCXhdwAX_doX-2&_nc_ht=scontent-lax3-1.xx&oh=00_AT-PYyffphSxOeGM_3aC4oHSA4U9cEjm2OrappZ_jh6qIg&oe=6355895C +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/48422692_10157144542022625_3324340889383337984_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=h_gtXGa5nSAAX-CB2E1&_nc_ht=scontent-lax3-1.xx&oh=00_AT8x7vZA5VN27hfoMPcQ39So-1COUnuNjDj4iz8d-cw6wQ&oe=6354B370 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/150153899_10208532293942419_1808101907825622971_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=3NZWKCpJ0VYAX911uCo&_nc_ht=scontent-lax3-2.xx&oh=00_AT_NhKwagHEsPuQVSERZNQ2_6MOIG2UKG5r32c0fIPaeQA&oe=63552624 +// https://scontent-lax3-2.xx.fbcdn.net/v/t31.18172-8/13641143_10209202266801205_7525162492297688727_o.jpg?_nc_cat=106&ccb=1-7&_nc_sid=174925&_nc_ohc=AETs7fkfLacAX-q9cnO&_nc_ht=scontent-lax3-2.xx&oh=00_AT89DOYAP9GbNvEgmlI0z9VK9Z8uJttFQyuO-tfe5OGbFQ&oe=6355CF5D +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/299875_2023116112081_1500929171_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=bsY9uckSAQwAX-xxGaU&_nc_ht=scontent-lax3-2.xx&oh=00_AT8OStQmDU_7NLNrYLeNfoULcBA-onU1V3Z4bn35-ipzCQ&oe=63555339 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/1544444_10203587296855592_4576811169900508598_n.jpg?_nc_cat=100&ccb=1-7&_nc_sid=730e14&_nc_ohc=pYUGNkoYGPcAX8X2gtw&_nc_ht=scontent-lax3-2.xx&oh=00_AT_thzQZyWPWDA8t1acXqEzifLMXxcPTUOgqWqmeuk3SfQ&oe=63566033 +// https://scontent-lax3-2.xx.fbcdn.net/v/t31.18172-8/10887668_10205175511939476_7644367668075304275_o.jpg?_nc_cat=100&ccb=1-7&_nc_sid=730e14&_nc_ohc=2T_utQvqkXYAX94o-WV&_nc_ht=scontent-lax3-2.xx&oh=00_AT8aSCQ4JVgRULd1RxRJ8DYqPtB9EyGY-May2QwwqKSqjQ&oe=6356B098 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/78675669_10102984650797787_175454688261439488_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=7Plv_RW77xoAX9PiH9G&_nc_ht=scontent-lax3-2.xx&oh=00_AT-npzVNkTzAdYgQ4D1ltfyl8llC_xqOHJ9Mi8Vmh2khlw&oe=63571251 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/59569741_10214142910059142_9219208491263066112_n.jpg?_nc_cat=100&ccb=1-7&_nc_sid=730e14&_nc_ohc=F7GF2uCFljcAX8Skffr&_nc_ht=scontent-lax3-2.xx&oh=00_AT9e3f8x6hSmAcxQ5JDUvDPFEg0wcecKw175Qfj_WMwADQ&oe=635488AA +// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/290559432_10209959340897099_842876274165104077_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=Vgxc93JsPBEAX8zrKaq&_nc_ht=scontent-lax3-2.xx&oh=00_AT-pPGd4o-t7kSeY4XKOTDeOzfg6wXOMBdY6rldQyrKQmw&oe=6335398E +// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/308587069_10210153867920153_6202104893656263114_n.jpg?stp=cp6_dst-jpg&_nc_cat=104&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=SKB2FSdXFQEAX_v1t5g&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT_E1y9uQ27O5_FAyAeRJ0XotEyBEXytkM2iDCGvldSxLw&oe=633549B9 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/67309987_10214298629206292_6820175905885782016_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=730e14&_nc_ohc=BTQEZF_dwRwAX-cxNEm&_nc_ht=scontent-lax3-2.xx&oh=00_AT8_z-JOBPmskVsJ0S9bOYgcYngpVrMjhVrfFk3r06Wj8Q&oe=635473AE +// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/263710427_1324163581376203_7502850514450466967_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=e3f864&_nc_ohc=2KB9VISs3y0AX-PiYUd&_nc_ht=scontent-lax3-1.xx&oh=00_AT_WaHBBSAR2-1dDEdpufugFf4eBttyGfIIflCwvCu_76g&oe=6334DBC3 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/67181620_114720179834180_3651229536021905408_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=CYF8Qyy506MAX-GBXV_&_nc_ht=scontent-lax3-2.xx&oh=00_AT_dFZiY42lloSfdiqNgSBlDpS9eGIEu85hhG5KIQD7DwA&oe=6354B320 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/68383564_10217654576270105_5209606295652401152_n.jpg?_nc_cat=109&ccb=1-7&_nc_sid=730e14&_nc_ohc=phqcunpNs5EAX-d33kB&_nc_ht=scontent-lax3-1.xx&oh=00_AT9n5kFQnIPSXUzNAMb19Wpue1YA-bHFpSCE-_tOXD8Gnw&oe=635615C2 +// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/285466259_7423531907720135_1229107015631332741_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=z85uLRC706sAX_H3_Ht&_nc_ht=scontent-lax3-2.xx&oh=00_AT-_wJpeYhqcdivdF4SbBO3B6M082qGD3qdGAPDmVP6nAA&oe=6334B2ED +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/32919393_10214483292069982_3789509822047584256_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=oJzHahluIDYAX-5-UGl&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT9e7dHUoseqwf6qF39NHEnPzal6NzQgyCwio6SvzUZTUQ&oe=63575682 +// https://scontent-lax3-1.xx.fbcdn.net/v/t31.18172-8/22256636_1931521070438917_6414682443902494017_o.jpg?_nc_cat=102&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=bf99qSpRJOQAX_stzg5&_nc_ht=scontent-lax3-1.xx&oh=00_AT-eoWkn2Gaqj8BGasgNOycRzf3AiQE2GqHtKC28_-vFug&oe=63586C84 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/600876_10151927436713567_876993105_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=dcNCC6RNOooAX985ENZ&_nc_ht=scontent-lax3-1.xx&oh=00_AT-9Q999B_sWiS-yPP48gVdrheH0bY-DAiOKmkExbnW5Rw&oe=6355ED8A +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/35235266_10213962597433782_6655457381733892096_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=uxfDliBhPHUAX_JXJNU&_nc_ht=scontent-lax3-1.xx&oh=00_AT9zGc-8QqaAHItoDqGzKAMEYOU0fEQz_ld1R_CGxPokug&oe=63565840 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/161932468_10159007128985149_3980438021322511452_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=6dCTPFwvFPcAX8ogdv8&_nc_ht=scontent-lax3-1.xx&oh=00_AT8pspicXFXgj-MOKzkTyp_ZpuboruYwyM-MMNwro_m9JA&oe=63564482 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/91250196_10215323899906785_1298078522899693568_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=730e14&_nc_ohc=CSXhKF_1hqsAX9qlbvP&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT-JZSnju_3XqWV6w9rSSUmuo2oIaCig5pn7JmrvFbBfGw&oe=6354B339 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/428510_490436967638912_1438526428_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=ba80b0&_nc_ohc=s0ZgvdtWwOsAX87AD-I&_nc_ht=scontent-lax3-1.xx&oh=00_AT-gv7QKuJhEjRPbmQr5hLG163-EQXcx6FJ8aHCcPTfEXw&oe=63563591 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/50312617_10161338488075048_5525340242208358400_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=NcQcK_z6t9kAX9-On7t&_nc_ht=scontent-lax3-2.xx&oh=00_AT_WYrFNqGwzuSssGPBRmhpqGlyf1k2ulbdKakrs4xAHBg&oe=635832DB +// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/247966826_10200232179385908_1907223790607385605_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=Xn0j5rxLaCQAX8OD7k4&_nc_ht=scontent-lax3-2.xx&oh=00_AT_bVVWAuaGYpIcBOCbAtn_xyS2Rqu--uKs06-FKOfjuzg&oe=633583B8 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/57840024_10161651547530207_1744748790901899264_n.jpg?_nc_cat=107&ccb=1-7&_nc_sid=730e14&_nc_ohc=TKg3rTiXkYUAX-V8NO5&_nc_ht=scontent-lax3-2.xx&oh=00_AT-Mepp0iN8-mHhyKRHj-R5yBOu2wZGtQeApkSoZTzM3mg&oe=6356A1D2 +// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/269876615_612483726673361_2182050585964176784_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=174925&_nc_ohc=9QJt0X_FDlEAX_Okew3&_nc_ht=scontent-lax3-2.xx&oh=00_AT_Yq8OflcYUA39JsmM__UxFYi8YASqsrRYx-T1Qn2NPSw&oe=6336391B +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/126332086_10224303272989365_2242003924983097751_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=zpX0nadZ9XMAX8m1Kk1&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT9c_dq-TLiNxgeqmp9Pwnqp9xjUIG3_ijxmiAW4MPYBKw&oe=63578DD6 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/74624149_10215957575592842_7757392320252608512_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=22WGGAPeTdAAX87greQ&_nc_ht=scontent-lax3-2.xx&oh=00_AT9J2qV9zBMEPuqsibA-87pjTBg3e3hVwHb211GO7lJicQ&oe=6355F050 +// https://scontent-lax3-1.xx.fbcdn.net/v/t31.18172-8/10649011_812346452173040_8035951943727654673_o.jpg?_nc_cat=104&ccb=1-7&_nc_sid=84a396&_nc_ohc=aDP-7N5XOgMAX9eflUn&_nc_ht=scontent-lax3-1.xx&oh=00_AT_mCAWB-OTJ32F1W90m-bPWu9GmjvFr-j1VdAQmkdNtXg&oe=63565628 +// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/289655024_10228170790084748_4126077972169573791_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=NeX9XD5pcwMAX9UBHLe&_nc_ht=scontent-lax3-2.xx&oh=00_AT-Qh77ATMIGL6JnKHr9rIim3Ui_UU7XH_S02S7-iu21qg&oe=633700F0 +// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/266998682_10227151119593623_456420668145746211_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=vjNb4pBYn5YAX_gWfIx&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT_rv-lqoI2u7Y-FMU8G8Vfxo6QyA-tTEIMKRpRx9TxZPg&oe=63353CCC +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/40093120_10217172129645111_2951374557987995648_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=X1j0ykpT26oAX8kDzfQ&_nc_ht=scontent-lax3-1.xx&oh=00_AT-jst6eXb7F4NsSJRj4jOQvKqQOiiMP4K5iT8xq2t4dTg&oe=63588214 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/31870362_10216285257993874_8330563136197230592_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=84a396&_nc_ohc=8kJ5pbwsKDMAX9hIVFA&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT9vRjGUZdT7KasJ4Ndti2LHfrPaTcahl4ipT9BmdRZJeg&oe=6356737F +// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/230074448_10103880528811228_6909448863476427135_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=0debeb&_nc_ohc=cUpp1Y_DO8MAX92tN_t&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT-K2_yEERUmvvjkjUKf7zwaV2Ofq1fHIQAtEkCt7I2Bhw&oe=63352B95 +// https://scontent-lax3-2.xx.fbcdn.net/v/t31.18172-8/13502600_10210212049485607_95598559411189791_o.jpg?_nc_cat=101&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=AEojM0a2X5cAX8kmHfZ&_nc_ht=scontent-lax3-2.xx&oh=00_AT-ZX7vIfwazh_zcDpFCLxwLgp90gERyBG2CtGxIClEnYA&oe=6355A9C7 +// https://scontent-lax3-2.xx.fbcdn.net/v/t31.18172-8/14195939_1314130471931770_9026437168267009241_o.jpg?_nc_cat=106&ccb=1-7&_nc_sid=730e14&_nc_ohc=f8owaBX7kMQAX8ioj3m&_nc_ht=scontent-lax3-2.xx&oh=00_AT8N8GR4GXmKa3w_dP952fl1_bx_s7CTz8qFtxaBK91S2w&oe=6355DBA1 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/67428022_1287438048070254_7628190366730027008_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=b9115d&_nc_ohc=wO9dTzPDXjcAX85VZFY&_nc_ht=scontent-lax3-1.xx&oh=00_AT9ZDKZuwlVncwGAKb6fkty_AaqnBNmKj46n2L3V1Y_V5w&oe=63589442 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/95959209_10221111135660559_6413640771529867264_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=730e14&_nc_ohc=tN-1cmyDpEsAX-GecHc&_nc_ht=scontent-lax3-2.xx&oh=00_AT9w87N6Gq277785G3f6iF5IhEtBtJ7FTZwxkK5dipNqOw&oe=63575F14 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/207486_1959635958499_5198863_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=2c4854&_nc_ohc=Z2KnsIT4a60AX9wPnkk&_nc_ht=scontent-lax3-2.xx&oh=00_AT8Vxh2vW53SBZ4sH9cgZEofWKOUnpf8fpMjQRjZGCR6oQ&oe=6355FB36 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/37219804_10213591073105306_9108104030982242304_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=pngJAV_sIesAX8zSJG-&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT8UXpCCDSQ8uOPtSrdzcty0tSpO6TCEKCL-K7q-BMb5CA&oe=6357898C +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/427539_3441473049618_710901137_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=3DPj0_PEbSsAX9ryGz1&_nc_ht=scontent-lax3-2.xx&oh=00_AT9t82wuMvW2xdHA8E1VokNBzIhHtgBbpH8fvrIxE_0rPQ&oe=6357DCF0 +// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/241548211_237343028191419_6995991527624737723_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=nsC7CBQxaO8AX-De2g8&_nc_ht=scontent-lax3-1.xx&oh=00_AT_V2gvedewiVuhPIozJyq4ccKm6WoxBkXe0Ocm1fTZ3wA&oe=63365FAA +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/10253934_10152334525164461_3158331570278601806_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=ba80b0&_nc_ohc=0A0Haq5W-IgAX8Y28oA&_nc_ht=scontent-lax3-1.xx&oh=00_AT-3tqlxtQj1n0D5t1jzVfQi22q3OMyQQrBcw2ZBrmYGaA&oe=6356ACAB +// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/308817118_10218508885627876_6441766117990882318_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=730e14&_nc_ohc=1kMU8trJLw4AX_R76M2&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT9k53CcIY7KnW3CjRR4bbE8wjuAUVVi6D1an-kcbOaF2g&oe=63363C9E +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/41038997_10156771390897533_9196241072043327488_n.jpg?_nc_cat=110&ccb=1-7&_nc_sid=174925&_nc_ohc=CK2PqvoqhVsAX9xGRSK&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT8qHnIW7OsQMRb7htRwC7byyqcK4X5qAkdz8IM4caO_iA&oe=6357DA17 +// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/279316302_4583494791751734_5208076832025426501_n.jpg?_nc_cat=109&ccb=1-7&_nc_sid=e3f864&_nc_ohc=Mt2sOGHVzbMAX-ghCmr&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT_xdFzERKvZfApmuZ19H39uWsTz0vrJR7fivTuaLGiE1g&oe=63372D6C +// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/213757548_10222718701187533_9216326465615113019_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=bMlCbM1DFccAX-UEjOE&_nc_oc=AQlwSf-Xrfqx-ldXQ8cz-gImrOZnTfVoNReVucU0i3C--RLTsfldVzKT8ij7sn7Jn3E&_nc_ht=scontent-lax3-2.xx&oh=00_AT-YAQ9scfYOoaPy5Kzb6yWcpe4t5MNTfE1pAP-RD3oBAA&oe=63379E32 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/1005197_172610252919953_1797035311_n.jpg?_nc_cat=102&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=9wV_ZoGuDfQAX9ZQWRM&_nc_ht=scontent-lax3-1.xx&oh=00_AT_M3_rgENbCEjmouX8Ar_N_s-P5Tke0TvPzza0Mwx90dg&oe=635764FD +// https://scontent-lax3-1.xx.fbcdn.net/v/t31.18172-8/24130218_2001836833160114_7527164989319060274_o.jpg?_nc_cat=104&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=ZxM5zjih3tYAX8IsXx9&_nc_ht=scontent-lax3-1.xx&oh=00_AT-G2-I-OXpcoq9TKqn_YT3gObzj8GMavpJe-KH7xj_5Lg&oe=6357091E +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/11745695_10206235519080812_1953505210138379535_n.jpg?_nc_cat=109&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=RDqIQjOVB0AAX8Q90_Z&_nc_ht=scontent-lax3-1.xx&oh=00_AT_FogaOVWhgLcesEzaidYx--sKeBVCD6xDg2IWJdvcafA&oe=6356095A +// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/280118285_2094899397354077_5352291217200307805_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=YQDlNI2xTrIAX-curq5&_nc_ht=scontent-lax3-2.xx&oh=00_AT8SuwF2W7tioXfAfCCun6zQsrIUjArOSr0ZlRb3F_Ej2Q&oe=63378827 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/13233144_10101471840461827_9039193337970955304_n.jpg?_nc_cat=107&ccb=1-7&_nc_sid=cdbe9c&_nc_ohc=aiRRaotqsnYAX-slwLd&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT_CxgSXV-0uNiiz_jLV_s3cPAk2vUyoro5MVsXsVyiEcg&oe=6358E40F +// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/305309404_10220461241674046_1323434462733520520_n.jpg?_nc_cat=108&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=NajFsslaLvcAX8-Kuz_&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT89eIRz0st3qjPnhURiW9W7wXbaOV0usVykQiKRL_TRhg&oe=6337A145 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/32481784_10216908834904287_1754862128918953984_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=8mU2Cf_iAFwAX80s1xj&_nc_ht=scontent-lax3-2.xx&oh=00_AT8SXOmaqoNTZvBik5j4hE7YEenshkhPggwNgfLdIjIn4g&oe=6355E4D5 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/22814020_10211731412395324_9169598565701382472_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=UTLq0qx2PQwAX-Y9rIg&_nc_ht=scontent-lax3-2.xx&oh=00_AT_T9Bqd0LwRhuUkbMuUHQcCxcB7I3TAumrw7BR-UuEe3Q&oe=63563325 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/94927050_10220780432954871_7311541287197343744_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=qJofGIany9YAX_15Uhx&_nc_ht=scontent-lax3-1.xx&oh=00_AT_K9V-fQ-IOdVE3BtUQCtFvljScg3xdbjWK8LWh0fs3EQ&oe=6355F4D6 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/47685020_2061588197234140_3115001451777097728_n.jpg?_nc_cat=103&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=g2_slmBopOEAX-kvLeT&_nc_ht=scontent-lax3-2.xx&oh=00_AT9CXSXrom34MSI2zgqXx7qdPKAn8fhKG8dBqGE3l1c_Nw&oe=63567CA6 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.6435-9/39221047_10156127305716865_6824896341232058368_n.jpg?_nc_cat=106&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=osdlvseA5vkAX9zsbYp&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT96aJo9mM_AXgkaQau0AYQ6GgMk3m02z4fpuT1FwYvXtw&oe=6357B649 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/11037634_10204752644508765_3331659968007967889_n.jpg?_nc_cat=107&ccb=1-7&_nc_sid=730e14&_nc_ohc=mjDY6IkATEoAX_WhE62&_nc_ht=scontent-lax3-2.xx&oh=00_AT8gl0GngVzWAynqGAC8zrgZUqAEgahMmzE5X41KpPnYxQ&oe=635825BA +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/36371667_10213980051274137_6162910755566911488_n.jpg?_nc_cat=102&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=-j6ofEojPiIAX-yLrVV&_nc_ht=scontent-lax3-1.xx&oh=00_AT9ZvpazeIfHclhHr24SguNiu0m4WMAj2k5jm2I40JIv5w&oe=635612A1 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/13938350_626048368466_6216870049337020892_n.jpg?_nc_cat=111&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=AbgIJkB2bH4AX-TuAoA&_nc_ht=scontent-lax3-2.xx&oh=00_AT-o0wi5Cz5vl1WDgcDm2BneTIBTuR-__qxxmR-3Fgyg_w&oe=63579473 +// https://scontent-lax3-1.xx.fbcdn.net/v/t39.30808-6/306356815_5223195104456342_2659949811667496237_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=730e14&_nc_ohc=NQdsObWTav0AX9EbW12&_nc_ht=scontent-lax3-1.xx&oh=00_AT8v-jsilYOUGKvQdlt7PXQmbdJRfZD0-GdycokWq-aNwg&oe=63365BAB +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/44500772_10217404839095591_8903105516822069248_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=RqwiyNoil04AX_Y4FGb&_nc_ht=scontent-lax3-1.xx&oh=00_AT85t0AjyzMXSj_UcUhKqWf7ZXdQOFAy12aFdeOO9cjBYg&oe=6356ED81 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/23659202_10212567297792300_7982751930141302131_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=ad2b24&_nc_ohc=9vxjTRupUuAAX8fJ3Qa&_nc_ht=scontent-lax3-2.xx&oh=00_AT8wFIaUMmI2_kLSejxewgzzMK6g0ngEJVfhEQukM0zQdQ&oe=63596B35 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.6435-9/92554157_10158297246718523_986365937043111936_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=lWhPQlbtKVoAX-4D9Ke&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-1.xx&oh=00_AT_P1suJEkfnHlSL3rPoDoYW2exSg_gQyN--HoylQbt_JQ&oe=63567442 +// https://scontent-lax3-2.xx.fbcdn.net/v/t39.30808-6/280689051_10209840036394561_8737214584492733274_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=iMAAHqNekCgAX8fvEAm&_nc_ht=scontent-lax3-2.xx&oh=00_AT8b7_ODsDN7lNgTKr7JZiUE65R7WWYxENl1vOUGeobpBw&oe=6336E678 +// https://scontent-lax3-1.xx.fbcdn.net/v/t1.18169-9/25110_107749419250772_1842086_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=09cbfe&_nc_ohc=xPyRCvImlaoAX9Z_UTs&_nc_ht=scontent-lax3-1.xx&oh=00_AT85xffhHDJlhzyFTBvWs8j3Gd4GyDCte_Pnd7hNM006sw&oe=635880C2 +// https://scontent-lax3-2.xx.fbcdn.net/v/t1.18169-9/10259940_10153561109653458_6837681277740526675_n.jpg?_nc_cat=100&ccb=1-7&_nc_sid=8bfeb9&_nc_ohc=T1v9Wfqz8rUAX8y6bxq&tn=D0unuoVdv--xjhpM&_nc_ht=scontent-lax3-2.xx&oh=00_AT9Y9j5s19n0EzgO_dZMom8tAweFYIrJLOEsrsr4HAWHkQ&oe=63588CAE +// https://scontent-lax3-1.xx.fbcdn.net/v/t31.18172-8/17621858_10154253751886426_3939148233753829194_o.jpg?_nc_cat=104&ccb=1-7&_nc_sid=ad2b24&_nc_ohc=yMOqJQQBV80AX8Xlzr-&_nc_ht=scontent-lax3-1.xx&oh=00_AT_z0oeHJ0fqKhVA4WdgGE2cZSgL0bIKNXzPsY0zeLipAg&oe=6356AF4C +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// FileSizeChanged +// LastWriteTimeChanged +// LastWriteTimeChanged +// LastWriteTimeChanged +// LastWriteTimeChanged +// LastWriteTimeChanged +// LastWriteTimeChanged +// LastWriteTimeChanged +// LastWriteTimeChanged +// LastWriteTimeChanged +// LastWriteTimeChanged +// LastWriteTimeChanged +// - Device Videos 2_0_0_3 - Current - Kristy Google +// - Device Videos 2_0_0_3 - Current - Mike Google +// - Device Videos 2_0_0_3 - Current - Tracy +// Chester & Linores ?Pearson? +// Distance face files date time? +// If not Hog and Original image size resize face +// New json file with known locations at original image size +// Add person to metadata when saving to SaveFilteredOriginalImagesFromJLinksForOutputResolutions maybe SaveMappedForOutputResolutions +// GetFaceLocations +// Stream stream; +// FileType fileType; +// const string abd = ".abd"; +// List jsonGroupFiles = GetFileCollection(jsonGroupFileCollection); +// foreach (string jsonGroupFile in jsonGroupFiles) +// File.Move(jsonGroupFile, string.Concat(jsonGroupFile, abd)); +// stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read); +// fileType = FileTypeDetector.DetectFileType(stream); +// stream.Close(); +// stream.Dispose(); +// if (fileType == FileType.Unknown) +// continue; +// if (!isUniqueFileName || string.IsNullOrEmpty(resultAllInOneKey)) +// { +// if (!isUniqueFileName && !string.IsNullOrEmpty(resultAllInOneKey)) +// File.Move(Path.Combine(resultAllInOneKey, fileName), Path.Combine(resultAllInOneKey, $"{fileName}.del")); +// resultAllInOneKey = string.Concat(jsonGroupDirectory, directoryName[length..]); +// } +// if (!jsonGroupFileCollection.TryGetValue(resultAllInOneKey, out keyValuePairs) || !keyValuePairs.TryGetValue(fileName, out collection)) +// fileAndJsonGroupFiles.Add(new FileAndJsonGroupFile(file, isUniqueFileName, Path.Combine(resultAllInOneKey, fileName), false)); +// else +// { +// foreach (string jsonGroupFile in collection) +// // File.Move(string.Concat(jsonGroupFile, abd), jsonGroupFile); +// fileAndJsonGroupFiles.Add(new FileAndJsonGroupFile(file, isUniqueFileName, jsonGroupFile, true)); +// } +// C:\Program Files\ImageMagick-7.1.0-Q16-HDRI>identify -format '%Q' "D:\7-Question\- - - Images\Mike Goolgle Photos 2014\-1507651551.jpg" +// '98' +// C:\Program Files\ImageMagick-7.1.0-Q16-HDRI>identify -format '%Q' "D:\7) Question\- - - Images\Mike Goolgle Photos 2014\197616258.jpg" +// '90' +// dotnet dead-csharp --inputs "**/*.cs" --excludes "**/obj/**" "**/I*.cs" "**/UnitTest*.cs" +// " - Shortcut (2)" +// _ Manual Copy Successful +// # Don't know full birthday +// ] Match +// ^ Know birthday +// _ Import of Partial info +// ` Want to find +// ~ Approxomite age +// + Approxomite age and have at least one image +// XPAuthor = 40093 | [3-1] ("Picasa") +// Artist = 315 | [182-3] ("Picasa", "unknown", " ") +// Kristy, Mike, Tracy, Mackenzie, Julie, Patrick, ... +// XPKeywords = 40094 | Vulgar !9, Blurry !4, Duplicate !3, Irrelevant !2, Review !1, Slideshow [0] +// XPSubject = 40095 | Family Pictures, Scanned [2-1] (" ") +// XPComment = 40092 | [9-2] ("\uFFFD", " ") +// Rating = 18246 | [52-1] ("0") +// UserComment = 37510 | [3570-^^^] +// XPTitle = 40091 | [24-24] ("Logan and his Doggie", ...) +// ImageDescription = 270 | [2135-5^] 4+37+82+1354+647+11 ("Camera 360", "OLYMPUS DIGITAL CAMERA ", "SAMSUNG ", " ", " ") +// int artist = (int)IExif.Tags.Artist; +// int fileSource = (int)IExif.Tags.FileSource; +// int userComment = (int)IExif.Tags.UserComment; +// 0x9c9b XPTitle int8u IFD0 (tags 0x9c9b-0x9c9f are used by Windows Explorer; special characters in these values are converted to UTF-8 by default, or Windows Latin1 with the -L option. XPTitle is ignored by Windows Explorer if ImageDescription exists) +// 0x9c9c XPComment int8u IFD0 +// 0x9c9d XPAuthor int8u IFD0 (ignored by Windows Explorer if Artist exists) +// 0x9c9e XPKeywords int8u IFD0 +// 0x9c9f XPSubject int8u IFD0 +// 0xa300 FileSource undef ExifIFD 1 = Film Scanner +// 2 = Reflection Print Scanner +// 3 = Digital Camera +// "\x03\x00\x00\x00" = Sigma Digital Camera +// 0x9286 UserComment undef ExifIFD +// 0x013b Artist string IFD0 (becomes a list-type tag when the MWG module is loaded) diff --git a/.vscode/mklink.md b/.vscode/mklink.md new file mode 100644 index 0000000..066d3fa --- /dev/null +++ b/.vscode/mklink.md @@ -0,0 +1,15 @@ +--- +type: "note" +created: "2023-10-20T03:58:11.564Z" +updated: "2023-10-20T03:59:01.765Z" +--- + +# mklink + +```bash +mklink /J "L:\Git\AA\Shared\.kanbn" "D:\5-Other-Small\Kanban\View-by-Distance" +``` + +```bash +mklink /J "L:\Git\AA\Shared\.kanbn" "D:\5-Other-Small\Kanban\View-by-Distance" +``` diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..754cdcc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,61 @@ +{ + "cSpell.words": [ + "appsettings", + "Argb", + "ASPNETCORE", + "Barrick", + "bcdfghjklmnpqrstvwxyz", + "Bday", + "Beichler", + "Bohdi", + "Cobertura", + "cref", + "CUDA", + "Dlib", + "DSCN", + "Exif", + "eyeα", + "Getα", + "Greyscale", + "Hasher", + "Hmmss", + "Hmmssfff", + "jfif", + "JOSN", + "mmod", + "Nicéphore", + "Niépce", + "nosj", + "paramref", + "permille", + "permyriad", + "Phares", + "Phgtv", + "Photoshop", + "RDHC", + "recognise", + "Rects", + "resnet", + "Serilog", + "Subfile", + "Subfiles", + "Syncthing", + "Unmanaged", + "unrecognised", + "Upsample", + "Vericruz" + ], + "files.watcherExclude": { + "**/node_modules": true + }, + "cSpell.enabled": true, + "files.exclude": { + "**/.git": false, + "**/node_modules": true + }, + "coverage-gutters.coverageBaseDir": "./.vscode/ReportGenerator/Cobertura/*", + "extensions.ignoreRecommendations": true, + "[markdown]": { + "editor.wordWrap": "off" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..af07f66 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,75 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "User Secrets Init", + "command": "dotnet", + "type": "process", + "args": [ + "user-secrets", + "-p", + "${workspaceFolder}/Rename/AA.Rename.csproj", + "init" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "User Secrets Set", + "command": "dotnet", + "type": "process", + "args": [ + "user-secrets", + "-p", + "${workspaceFolder}/Rename/AA.Rename.csproj", + "set", + "_Application", + "Rename" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "Format", + "command": "dotnet", + "type": "process", + "args": [ + "format", + "--report", + ".vscode", + "--verbosity", + "detailed", + "--severity", + "warn" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/AA.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "Publish AOT", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "-r", + "win-x64", + "-c", + "Release", + "-p:PublishAot=true", + "${workspaceFolder}/Rename/AA.Rename.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/AA.sln b/AA.sln new file mode 100644 index 0000000..9799ffd --- /dev/null +++ b/AA.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AA.Shared", "Shared\AA.Shared.csproj", "{74E6778C-FCBE-4959-AFED-7C528BC5F855}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AA.Metadata", "Metadata\AA.Metadata.csproj", "{9E1FE0DA-8A9C-49B4-82BC-67F68D42F49C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AA.Rename", "Rename\AA.Rename.csproj", "{E80BF7A6-C892-426D-829C-910BD4700E69}" +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 + {74E6778C-FCBE-4959-AFED-7C528BC5F855}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74E6778C-FCBE-4959-AFED-7C528BC5F855}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74E6778C-FCBE-4959-AFED-7C528BC5F855}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74E6778C-FCBE-4959-AFED-7C528BC5F855}.Release|Any CPU.Build.0 = Release|Any CPU + {9E1FE0DA-8A9C-49B4-82BC-67F68D42F49C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E1FE0DA-8A9C-49B4-82BC-67F68D42F49C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E1FE0DA-8A9C-49B4-82BC-67F68D42F49C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E1FE0DA-8A9C-49B4-82BC-67F68D42F49C}.Release|Any CPU.Build.0 = Release|Any CPU + {E80BF7A6-C892-426D-829C-910BD4700E69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E80BF7A6-C892-426D-829C-910BD4700E69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E80BF7A6-C892-426D-829C-910BD4700E69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E80BF7A6-C892-426D-829C-910BD4700E69}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Metadata/AA.Metadata.csproj b/Metadata/AA.Metadata.csproj new file mode 100644 index 0000000..fce4661 --- /dev/null +++ b/Metadata/AA.Metadata.csproj @@ -0,0 +1,43 @@ + + + enable + enable + library + win-x64 + net8.0 + + + Phares.AA.Metadata + false + 8.0.101.1 + Mike Phares + Phares + true + snupkg + + + true + true + true + + + Windows + + + OSX + + + Linux + + + + + + + + + + + + + \ No newline at end of file diff --git a/Metadata/Models/A_Metadata.cs b/Metadata/Models/A_Metadata.cs new file mode 100644 index 0000000..023f133 --- /dev/null +++ b/Metadata/Models/A_Metadata.cs @@ -0,0 +1,98 @@ +using MetadataExtractor; +using System.Text.Json; +using View_by_Distance.Metadata.Models.Stateless; +using View_by_Distance.Metadata.Models.Stateless.Methods; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Properties; + +namespace View_by_Distance.Metadata.Models; + +public class A_Metadata +{ + + private readonly IAAConfiguration _AAConfiguration; + private readonly bool _PropertiesChangedForMetadata; + private readonly bool _ForceMetadataLastWriteTimeToCreationTime; + private readonly IReadOnlyDictionary _FileGroups; + + public A_Metadata(IAAConfiguration aAConfiguration, bool forceMetadataLastWriteTimeToCreationTime, bool propertiesChangedForMetadata) + { + _AAConfiguration = aAConfiguration; + _PropertiesChangedForMetadata = propertiesChangedForMetadata; + _ForceMetadataLastWriteTimeToCreationTime = forceMetadataLastWriteTimeToCreationTime; + string bResultsFullGroupDirectory = Shared.Models.Stateless.Methods.IResult.GetResultsFullGroupDirectory(_AAConfiguration, + nameof(A_Metadata), + string.Empty, + includeResizeGroup: false, + includeModel: false, + includePredictorModel: false); + _FileGroups = Shared.Models.Stateless.Methods.IPath.GetKeyValuePairs(aAConfiguration, bResultsFullGroupDirectory, [aAConfiguration.ResultSingleton]); + } + + public ExifDirectory GetMetadataCollection(string file) + { + ExifDirectory? results; + string fileName = Path.GetFileName(file); + string fileExtensionLowered = Path.GetExtension(file).ToLower(); + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); + (_, int directoryIndex) = Shared.Models.Stateless.Methods.IPath.GetDirectoryNameAndIndex(_AAConfiguration.ResultAllInOneSubdirectoryLength, fileName); + FileInfo fileInfo = new(Path.Combine(_FileGroups[_AAConfiguration.ResultSingleton][directoryIndex], $"{fileNameWithoutExtension}{fileExtensionLowered}.json")); + if (_ForceMetadataLastWriteTimeToCreationTime && !fileInfo.Exists && File.Exists(Path.ChangeExtension(fileInfo.FullName, ".delete"))) + { + File.Move(Path.ChangeExtension(fileInfo.FullName, ".delete"), fileInfo.FullName); + fileInfo.Refresh(); + } + if (_ForceMetadataLastWriteTimeToCreationTime && fileInfo.Exists && fileInfo.LastWriteTime != fileInfo.CreationTime) + { + File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime); + fileInfo.Refresh(); + } + if (_PropertiesChangedForMetadata) + results = null; + else if (!fileInfo.Exists) + results = null; + else if (!fileInfo.FullName.EndsWith(".json") && !fileInfo.FullName.EndsWith(".old")) + throw new ArgumentException("must be a *.json file"); + else + { + string json = File.ReadAllText(fileInfo.FullName); + try + { + results = JsonSerializer.Deserialize(json, ExifDirectorySourceGenerationContext.Default.ExifDirectory); + if (results is null) + throw new Exception(); + } + catch (Exception) + { + results = null; + } + } + if (results is null) + { + System.Drawing.Size? size; + try + { size = Dimensions.GetDimensions(file); } + catch (Exception) { size = null; } + IReadOnlyList directories = ImageMetadataReader.ReadMetadata(file); + results = Exif.Covert(file, fileInfo, size, directories); + string json = JsonSerializer.Serialize(results, ExifDirectorySourceGenerationContext.Default.ExifDirectory); + if (Shared.Models.Stateless.Methods.IPath.WriteAllText(fileInfo.FullName, json, updateDateWhenMatches: false, compareBeforeWrite: true, updateToWhenMatches: null) && _ForceMetadataLastWriteTimeToCreationTime) + { + File.SetLastWriteTime(fileInfo.FullName, fileInfo.CreationTime); + fileInfo.Refresh(); + } + } + return results; + } + + public static Action GetResultCollection(A_Metadata metadata, List exifDirectories, Action tick) + { + return file => + { + tick.Invoke(); + lock (exifDirectories) + exifDirectories.Add(metadata.GetMetadataCollection(file)); + }; + } + +} \ No newline at end of file diff --git a/Metadata/Models/Binder/.editorconfig b/Metadata/Models/Binder/.editorconfig new file mode 100644 index 0000000..1c444cd --- /dev/null +++ b/Metadata/Models/Binder/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +csharp_preserve_single_line_statements = true \ No newline at end of file diff --git a/Metadata/Models/Binder/Configuration.cs b/Metadata/Models/Binder/Configuration.cs new file mode 100644 index 0000000..bd061c9 --- /dev/null +++ b/Metadata/Models/Binder/Configuration.cs @@ -0,0 +1,119 @@ +using Microsoft.Extensions.Configuration; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Metadata.Models.Binder; + +public class Configuration +{ + + public string? DateGroup { get; set; } + public string? FileNameDirectorySeparator { get; set; } + public bool? ForcePropertyLastWriteTimeToCreationTime { get; set; } + public string[]? IgnoreExtensions { get; set; } + public string[]? IgnoreRulesKeyWords { get; set; } + public int? MaxImagesInDirectoryForTopLevelFirstPass { get; set; } + public string? ModelName { init; get; } + public int? NumberOfJitters { init; get; } + public int? NumberOfTimesToUpsample { init; get; } + public int? Offset { init; get; } + public string? Pattern { get; set; } + public string? PersonBirthdayFormat { get; set; } + public bool? PopulatePropertyId { get; set; } + public string? PredictorModelName { get; set; } + public bool? PropertiesChangedForProperty { get; set; } + public string[]? PropertyContentCollectionFiles { get; set; } + public string? ResultAllInOne { get; set; } + public int? ResultAllInOneSubdirectoryLength { get; set; } + public string? ResultCollection { get; set; } + public string? ResultContent { get; set; } + public string? ResultSingleton { get; set; } + public string? RootDirectory { get; set; } + public string[]? ValidImageFormatExtensions { get; set; } + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, BinderMetadataConfigurationSourceGenerationContext.Default.Configuration); + return result; + } + + private static MetadataConfiguration Get(Configuration? configuration) + { + MetadataConfiguration result; + if (configuration is null) throw new NullReferenceException(nameof(configuration)); + if (configuration.DateGroup is null) throw new NullReferenceException(nameof(configuration.DateGroup)); + if (configuration.FileNameDirectorySeparator is null) throw new NullReferenceException(nameof(configuration.FileNameDirectorySeparator)); + if (configuration.ForcePropertyLastWriteTimeToCreationTime is null) throw new NullReferenceException(nameof(configuration.ForcePropertyLastWriteTimeToCreationTime)); + if (configuration.IgnoreExtensions is null) throw new NullReferenceException(nameof(configuration.IgnoreExtensions)); + if (configuration.IgnoreRulesKeyWords is null) throw new NullReferenceException(nameof(configuration.IgnoreRulesKeyWords)); + if (configuration.MaxImagesInDirectoryForTopLevelFirstPass is null) throw new NullReferenceException(nameof(configuration.MaxImagesInDirectoryForTopLevelFirstPass)); + // if (configuration.ModelName is null) throw new NullReferenceException(nameof(configuration.ModelName)); + // if (configuration.NumberOfJitters is null) throw new NullReferenceException(nameof(configuration.NumberOfJitters)); + // if (configuration.NumberOfTimesToUpsample is null) throw new NullReferenceException(nameof(configuration.NumberOfTimesToUpsample)); + if (configuration.Offset is null) throw new NullReferenceException(nameof(configuration.Offset)); ; + if (configuration.Pattern is null) throw new NullReferenceException(nameof(configuration.Pattern)); + if (configuration.PersonBirthdayFormat is null) throw new NullReferenceException(nameof(configuration.PersonBirthdayFormat)); + if (configuration.PopulatePropertyId is null) throw new NullReferenceException(nameof(configuration.PopulatePropertyId)); + // if (configuration.PredictorModelName is null) throw new NullReferenceException(nameof(configuration.PredictorModelName)); + if (configuration.PropertiesChangedForProperty is null) throw new NullReferenceException(nameof(configuration.PropertiesChangedForProperty)); + // if (configuration.PropertyContentCollectionFiles is null) throw new NullReferenceException(nameof(configuration.PropertyContentCollectionFiles)); + if (configuration.ResultAllInOne is null) throw new NullReferenceException(nameof(configuration.ResultAllInOne)); + if (configuration.ResultAllInOneSubdirectoryLength is null) throw new NullReferenceException(nameof(configuration.ResultAllInOneSubdirectoryLength)); + if (configuration.ResultCollection is null) throw new NullReferenceException(nameof(configuration.ResultCollection)); + if (configuration.ResultContent is null) throw new NullReferenceException(nameof(configuration.ResultContent)); + if (configuration.ResultSingleton is null) throw new NullReferenceException(nameof(configuration.ResultSingleton)); + if (configuration.RootDirectory is null) throw new NullReferenceException(nameof(configuration.RootDirectory)); + if (configuration.ValidImageFormatExtensions is null) throw new NullReferenceException(nameof(configuration.ValidImageFormatExtensions)); + result = new(configuration.DateGroup, + configuration.FileNameDirectorySeparator, + configuration.ForcePropertyLastWriteTimeToCreationTime.Value, + configuration.IgnoreExtensions, + configuration.IgnoreRulesKeyWords, + configuration.MaxImagesInDirectoryForTopLevelFirstPass.Value, + configuration.ModelName, + configuration.NumberOfJitters, + configuration.NumberOfTimesToUpsample, + configuration.Offset.Value, + configuration.Pattern, + configuration.PersonBirthdayFormat, + configuration.PopulatePropertyId.Value, + configuration.PredictorModelName, + configuration.PropertiesChangedForProperty.Value, + configuration.PropertyContentCollectionFiles ?? [], + configuration.ResultAllInOne, + configuration.ResultAllInOneSubdirectoryLength.Value, + configuration.ResultCollection, + configuration.ResultContent, + configuration.ResultSingleton, + Path.GetFullPath(configuration.RootDirectory), + configuration.ValidImageFormatExtensions); + return result; + } + + public static MetadataConfiguration Get(IConfigurationRoot configurationRoot) + { + MetadataConfiguration result; +#if Linux + string environmentName = "Linux"; +#elif OSX + string environmentName = "OSX"; +#elif Windows + string environmentName = "Windows"; +#endif + string section = string.Concat(environmentName, ":", nameof(Configuration)); + IConfigurationSection configurationSection = configurationRoot.GetSection(section); +#pragma warning disable IL3050, IL2026 + Configuration? configuration = configurationSection.Get(); +#pragma warning restore IL3050, IL2026 + if (configuration is null) throw new NullReferenceException(nameof(configuration)); + result = Get(configuration); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Configuration))] +internal partial class BinderMetadataConfigurationSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Metadata/Models/MetadataConfiguration.cs b/Metadata/Models/MetadataConfiguration.cs new file mode 100644 index 0000000..4a2068e --- /dev/null +++ b/Metadata/Models/MetadataConfiguration.cs @@ -0,0 +1,127 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Metadata.Models; + +public class MetadataConfiguration : Shared.Models.Properties.IAAConfiguration +{ + + protected string _RootDirectory; + + public string RootDirectory => _RootDirectory; + + public string DateGroup { init; get; } + public string FileNameDirectorySeparator { init; get; } + public bool ForcePropertyLastWriteTimeToCreationTime { init; get; } + public string[] IgnoreExtensions { init; get; } + public string[] IgnoreRulesKeyWords { init; get; } + public int MaxImagesInDirectoryForTopLevelFirstPass { init; get; } + public string? ModelName { init; get; } + public int? NumberOfJitters { init; get; } + public int? NumberOfTimesToUpsample { init; get; } + public int Offset { init; get; } + public string Pattern { init; get; } + public string PersonBirthdayFormat { init; get; } + public bool PopulatePropertyId { init; get; } + public string? PredictorModelName { init; get; } + public bool PropertiesChangedForProperty { init; get; } + public string[] PropertyContentCollectionFiles { init; get; } + public string ResultAllInOne { init; get; } + public int ResultAllInOneSubdirectoryLength { init; get; } + public string ResultCollection { init; get; } + public string ResultContent { init; get; } + public string ResultSingleton { init; get; } + public string[] ValidImageFormatExtensions { init; get; } + + [JsonConstructor] + public MetadataConfiguration(string dateGroup, + string fileNameDirectorySeparator, + bool forcePropertyLastWriteTimeToCreationTime, + string[] ignoreExtensions, + string[] ignoreRulesKeyWords, + int maxImagesInDirectoryForTopLevelFirstPass, + string? modelName, + int? numberOfJitters, + int? numberOfTimesToUpsample, + int offset, + string pattern, + string personBirthdayFormat, + bool populatePropertyId, + string? predictorModelName, + bool propertiesChangedForProperty, + string[] propertyContentCollectionFiles, + string resultAllInOne, + int resultAllInOneSubdirectoryLength, + string resultCollection, + string resultContent, + string resultSingleton, + string rootDirectory, + string[] validImageFormatExtensions) + { + DateGroup = dateGroup; + FileNameDirectorySeparator = fileNameDirectorySeparator; + ForcePropertyLastWriteTimeToCreationTime = forcePropertyLastWriteTimeToCreationTime; + IgnoreExtensions = ignoreExtensions; + IgnoreRulesKeyWords = ignoreRulesKeyWords; + MaxImagesInDirectoryForTopLevelFirstPass = maxImagesInDirectoryForTopLevelFirstPass; + ModelName = modelName; + NumberOfJitters = numberOfJitters; + NumberOfTimesToUpsample = numberOfTimesToUpsample; + Offset = offset; + Pattern = pattern; + PersonBirthdayFormat = personBirthdayFormat; + PredictorModelName = predictorModelName; + PopulatePropertyId = populatePropertyId; + PropertiesChangedForProperty = propertiesChangedForProperty; + PropertyContentCollectionFiles = propertyContentCollectionFiles; + ResultAllInOne = resultAllInOne; + ResultAllInOneSubdirectoryLength = resultAllInOneSubdirectoryLength; + ResultCollection = resultCollection; + ResultContent = resultContent; + ResultSingleton = resultSingleton; + _RootDirectory = rootDirectory; + ValidImageFormatExtensions = validImageFormatExtensions; + } + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, MetadataConfigurationSourceGenerationContext.Default.MetadataConfiguration); + return result; + } + + public void ChangeRootDirectory(string rootDirectory) => + _RootDirectory = Path.GetFullPath(rootDirectory); + + public static void Verify(MetadataConfiguration propertyConfiguration, bool requireExist) + { + if (propertyConfiguration is null) + throw new NullReferenceException(nameof(propertyConfiguration)); + if (propertyConfiguration.IgnoreExtensions is null || propertyConfiguration.IgnoreExtensions.Length == 0) + throw new NullReferenceException(nameof(propertyConfiguration.IgnoreExtensions)); + if (propertyConfiguration.IgnoreRulesKeyWords is null || propertyConfiguration.IgnoreRulesKeyWords.Length == 0) + throw new NullReferenceException(nameof(propertyConfiguration.IgnoreRulesKeyWords)); + if (propertyConfiguration.PropertyContentCollectionFiles is null) + throw new NullReferenceException(nameof(propertyConfiguration.PropertyContentCollectionFiles)); + if (propertyConfiguration.ValidImageFormatExtensions is null || propertyConfiguration.ValidImageFormatExtensions.Length == 0) + throw new NullReferenceException(nameof(propertyConfiguration.ValidImageFormatExtensions)); + if (propertyConfiguration is null) + throw new NullReferenceException(nameof(propertyConfiguration)); + if (string.IsNullOrEmpty(propertyConfiguration.DateGroup)) + throw new NullReferenceException(nameof(propertyConfiguration.DateGroup)); + if (string.IsNullOrEmpty(propertyConfiguration.FileNameDirectorySeparator)) + throw new NullReferenceException(nameof(propertyConfiguration.FileNameDirectorySeparator)); + if (string.IsNullOrEmpty(propertyConfiguration.Pattern)) + throw new NullReferenceException(nameof(propertyConfiguration.Pattern)); + if (string.IsNullOrEmpty(propertyConfiguration.RootDirectory) || (requireExist && !Directory.Exists(propertyConfiguration.RootDirectory))) + throw new NullReferenceException(nameof(propertyConfiguration.RootDirectory)); + if (propertyConfiguration.RootDirectory != Path.GetFullPath(propertyConfiguration.RootDirectory)) + throw new Exception(); + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(MetadataConfiguration))] +internal partial class MetadataConfigurationSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Metadata/Models/Stateless/Dimensions.cs b/Metadata/Models/Stateless/Dimensions.cs new file mode 100644 index 0000000..c1d3ca1 --- /dev/null +++ b/Metadata/Models/Stateless/Dimensions.cs @@ -0,0 +1,173 @@ +using System.Drawing; + +namespace View_by_Distance.Metadata.Models.Stateless.Methods; + +internal static class Dimensions +{ + + const string _ErrorMessage = "Could not recognize image format."; + +#pragma warning disable IDE0230 + private static readonly Dictionary> _ImageFormatDecoders = new() + { + { new byte[] { 0x42, 0x4D }, DecodeBitmap }, + { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }, DecodeGif }, + { new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }, DecodeGif }, + { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }, DecodePng }, + { new byte[] { 0xff, 0xd8 }, DecodeJfif }, + { new byte[] { 0x52, 0x49, 0x46, 0x46 }, DecodeWebP }, + }; +#pragma warning restore IDE0230 + + private static bool StartsWith(byte[] thisBytes, byte[] thatBytes) + { + for (int i = 0; i < thatBytes.Length; i += 1) + { + if (thisBytes[i] != thatBytes[i]) + { + return false; + } + } + return true; + } + + private static short ReadLittleEndianInt16(BinaryReader binaryReader) + { + byte[] bytes = new byte[sizeof(short)]; + for (int i = 0; i < sizeof(short); i += 1) + { + bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte(); + } + return BitConverter.ToInt16(bytes, 0); + } + + private static int ReadLittleEndianInt32(BinaryReader binaryReader) + { + byte[] bytes = new byte[sizeof(int)]; + for (int i = 0; i < sizeof(int); i += 1) + { + bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte(); + } + return BitConverter.ToInt32(bytes, 0); + } + + private static Size DecodeBitmap(BinaryReader binaryReader) + { + _ = binaryReader.ReadBytes(16); + int width = binaryReader.ReadInt32(); + int height = binaryReader.ReadInt32(); + return new Size(width, height); + } + + private static Size DecodeGif(BinaryReader binaryReader) + { + int width = binaryReader.ReadInt16(); + int height = binaryReader.ReadInt16(); + return new Size(width, height); + } + + private static Size DecodePng(BinaryReader binaryReader) + { + _ = binaryReader.ReadBytes(8); + int width = ReadLittleEndianInt32(binaryReader); + int height = ReadLittleEndianInt32(binaryReader); + return new Size(width, height); + } + + private static Size DecodeJfif(BinaryReader binaryReader) + { + while (binaryReader.ReadByte() == 0xff) + { + byte marker = binaryReader.ReadByte(); + short chunkLength = ReadLittleEndianInt16(binaryReader); + + if (marker == 0xc0) + { + _ = binaryReader.ReadByte(); + + int height = ReadLittleEndianInt16(binaryReader); + int width = ReadLittleEndianInt16(binaryReader); + return new Size(width, height); + } + + if (chunkLength < 0) + { + ushort uChunkLength = (ushort)chunkLength; + _ = binaryReader.ReadBytes(uChunkLength - 2); + } + else + { + _ = binaryReader.ReadBytes(chunkLength - 2); + } + } + + throw new ArgumentException(_ErrorMessage); + } + + private static Size DecodeWebP(BinaryReader binaryReader) + { + _ = binaryReader.ReadUInt32(); // Size + _ = binaryReader.ReadBytes(15); // WEBP, VP8 + more + _ = binaryReader.ReadBytes(3); // SYNC + + int width = binaryReader.ReadUInt16() & 0b00_11111111111111; // 14 bits width + int height = binaryReader.ReadUInt16() & 0b00_11111111111111; // 14 bits height + + return new Size(width, height); + } + + /// + /// Gets the dimensions of an image. + /// + /// The path of the image to get the dimensions of. + /// The dimensions of the specified image. + /// The image was of an unrecognized format. + public static Size GetDimensions(BinaryReader binaryReader) + { + int maxMagicBytesLength = _ImageFormatDecoders.Keys.OrderByDescending(x => x.Length).First().Length; + + byte[] magicBytes = new byte[maxMagicBytesLength]; + + for (int i = 0; i < maxMagicBytesLength; i += 1) + { + magicBytes[i] = binaryReader.ReadByte(); + + foreach (KeyValuePair> kvPair in _ImageFormatDecoders) + { + if (StartsWith(magicBytes, kvPair.Key)) + { + return kvPair.Value(binaryReader); + } + } + } + + throw new ArgumentException(_ErrorMessage, nameof(binaryReader)); + } + + /// + /// Gets the dimensions of an image. + /// + /// The path of the image to get the dimensions of. + /// The dimensions of the specified image. + /// The image was of an unrecognized format. + public static Size GetDimensions(string path) + { + using BinaryReader binaryReader = new(File.OpenRead(path)); + try + { + return GetDimensions(binaryReader); + } + catch (ArgumentException e) + { + if (e.Message.StartsWith(_ErrorMessage)) + { + throw new ArgumentException(_ErrorMessage, nameof(path), e); + } + else + { + throw; + } + } + } + +} \ No newline at end of file diff --git a/Metadata/Models/Stateless/Exif.cs b/Metadata/Models/Stateless/Exif.cs new file mode 100644 index 0000000..edb7c6a --- /dev/null +++ b/Metadata/Models/Stateless/Exif.cs @@ -0,0 +1,357 @@ +using MetadataExtractor; +using MetadataExtractor.Formats.Exif; +using System.Globalization; + +namespace View_by_Distance.Metadata.Models.Stateless; + +internal abstract class Exif +{ + + private static DateTime? GetDateTime(string? value) + { + DateTime? result; + string dateTimeFormat = "yyyy:MM:dd HH:mm:ss"; + string alternateFormat = "ddd MMM dd HH:mm:ss yyyy"; + if (value is not null && DateTime.TryParse(value, out DateTime dateTime)) + result = dateTime; + else if (value is not null && value.Length == dateTimeFormat.Length && DateTime.TryParseExact(value, dateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime)) + result = dateTime; + else if (value is not null && value.Length == alternateFormat.Length && DateTime.TryParseExact(value, alternateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out dateTime)) + result = dateTime; + else + result = null; + return result; + } + + private static Shared.Models.AviDirectory GetAviDirectory(IReadOnlyList directories) + { + Shared.Models.AviDirectory result; + MetadataExtractor.Formats.Avi.AviDirectory? aviDirectory = directories.OfType().FirstOrDefault(); + if (aviDirectory is null) + result = new(null, null, null, null); + else + { + DateTime? dateTimeOriginal; + string? duration = aviDirectory.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagDuration); + string? height = aviDirectory.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagHeight); + string? width = aviDirectory.GetDescription(MetadataExtractor.Formats.Avi.AviDirectory.TagWidth); + if (aviDirectory.TryGetDateTime(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal, out DateTime checkDateTime)) + dateTimeOriginal = checkDateTime; + else + dateTimeOriginal = GetDateTime(aviDirectory.GetString(MetadataExtractor.Formats.Avi.AviDirectory.TagDateTimeOriginal)); + result = new(dateTimeOriginal, duration, height, width); + } + return result; + } + + private static Shared.Models.ExifDirectoryBase GetExifDirectoryBase(IReadOnlyList directories) + { + Shared.Models.ExifDirectoryBase result; + ExifDirectoryBase? exifDirectoryBase = directories.OfType().FirstOrDefault(); + if (exifDirectoryBase is null) + result = new(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + else + { + DateTime? dateTime; + DateTime checkDateTime; + DateTime? dateTimeOriginal; + DateTime? dateTimeDigitized; + string? aperture = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagAperture); + string? applicationNotes = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagApplicationNotes); + string? artist = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagArtist); + string? bitsPerSample = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagBitsPerSample); + string? bodySerialNumber = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagBodySerialNumber); + string? cameraOwnerName = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCameraOwnerName); + string? compressedAverageBitsPerPixel = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCompressedAverageBitsPerPixel); + string? compression = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCompression); + string? copyright = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagCopyright); + string? documentName = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagDocumentName); + string? exifVersion = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagExifVersion); + string? exposureTime = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagExposureTime); + string? fileSource = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagFileSource); + string? imageDescription = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageDescription); + string? imageHeight = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageHeight); + string? imageNumber = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageNumber); + string? imageUniqueId = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageUniqueId); + string? imageWidth = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagImageWidth); + string? isoSpeed = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagIsoSpeed); + string? lensMake = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagLensMake); + string? lensModel = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagLensModel); + string? lensSerialNumber = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagLensSerialNumber); + string? make = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagMake); + string? makerNote = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagMakernote); + string? model = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagModel); + string? orientation = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagOrientation); + int? orientationValue = orientation is null ? null : exifDirectoryBase.GetInt32(ExifDirectoryBase.TagOrientation); + string? rating = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagRating); + string? ratingPercent = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagRatingPercent); + string? securityClassification = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagSecurityClassification); + string? shutterSpeed = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagShutterSpeed); + string? software = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagSoftware); + string? timeZone = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagTimeZone); + string? timeZoneDigitized = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagTimeZoneDigitized); + string? timeZoneOriginal = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagTimeZoneOriginal); + string? userComment = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagUserComment); + string? winAuthor = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinAuthor); + string? winComment = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinComment); + string? winKeywords = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinKeywords); + string? winSubject = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinSubject); + string? winTitle = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagWinTitle); + string? xResolution = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagXResolution); + string? yResolution = exifDirectoryBase.GetDescription(ExifDirectoryBase.TagYResolution); + if (exifDirectoryBase.TryGetDateTime(ExifDirectoryBase.TagDateTime, out checkDateTime)) + dateTime = checkDateTime; + else + dateTime = GetDateTime(exifDirectoryBase.GetString(ExifDirectoryBase.TagDateTime)); + if (exifDirectoryBase.TryGetDateTime(ExifDirectoryBase.TagDateTimeOriginal, out checkDateTime)) + dateTimeOriginal = checkDateTime; + else + dateTimeOriginal = GetDateTime(exifDirectoryBase.GetString(ExifDirectoryBase.TagDateTimeOriginal)); + if (exifDirectoryBase.TryGetDateTime(ExifDirectoryBase.TagDateTimeDigitized, out checkDateTime)) + dateTimeDigitized = checkDateTime; + else + dateTimeDigitized = GetDateTime(exifDirectoryBase.GetString(ExifDirectoryBase.TagDateTimeDigitized)); + result = new(aperture, + applicationNotes, + artist, + bitsPerSample, + bodySerialNumber, + cameraOwnerName, + compressedAverageBitsPerPixel, + compression, + copyright, + dateTime, + dateTimeDigitized, + dateTimeOriginal, + documentName, + exifVersion, + exposureTime, + fileSource, + imageDescription, + imageHeight, + imageNumber, + imageUniqueId, + imageWidth, + isoSpeed, + lensMake, + lensModel, + lensSerialNumber, + make, + makerNote, + model, + orientation, + orientationValue, + rating, + ratingPercent, + securityClassification, + shutterSpeed, + software, + timeZone, + timeZoneDigitized, + timeZoneOriginal, + userComment, + winAuthor, + winComment, + winKeywords, + winSubject, + winTitle, + xResolution, + yResolution); + } + return result; + } + + private static Shared.Models.FileMetadataDirectory GetFileMetadataDirectory(string file, IReadOnlyList directories) + { + Shared.Models.FileMetadataDirectory result; + MetadataExtractor.Formats.FileSystem.FileMetadataDirectory? fileMetadataDirectory = directories.OfType().FirstOrDefault(); + if (fileMetadataDirectory is null) + result = new(null, null, null); + else + { + DateTime? fileModifiedDate; + string? fileName = fileMetadataDirectory.GetDescription(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileName); + string? fileSize = fileMetadataDirectory.GetDescription(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileSize); + if (fileMetadataDirectory.TryGetDateTime(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileModifiedDate, out DateTime checkDateTime)) + fileModifiedDate = checkDateTime; + else + fileModifiedDate = GetDateTime(fileMetadataDirectory.GetString(MetadataExtractor.Formats.FileSystem.FileMetadataDirectory.TagFileModifiedDate)); + if (fileName is null || !file.EndsWith(fileName)) + throw new NotSupportedException($"!{file}.EndsWith({fileName})"); + result = new(fileModifiedDate, fileName, fileSize); + } + return result; + } + + private static Shared.Models.GifHeaderDirectory GetGifHeaderDirectory(IReadOnlyList directories) + { + Shared.Models.GifHeaderDirectory result; + MetadataExtractor.Formats.Gif.GifHeaderDirectory? gifHeaderDirectory = directories.OfType().FirstOrDefault(); + if (gifHeaderDirectory is null) + result = new(null, null); + else + { + string? imageHeight = gifHeaderDirectory.GetDescription(MetadataExtractor.Formats.Gif.GifHeaderDirectory.TagImageHeight); + string? imageWidth = gifHeaderDirectory.GetDescription(MetadataExtractor.Formats.Gif.GifHeaderDirectory.TagImageWidth); + result = new(imageHeight, imageWidth); + } + return result; + } + + private static Shared.Models.PhotoshopDirectory GetPhotoshopDirectory(IReadOnlyList directories) + { + Shared.Models.PhotoshopDirectory result; + MetadataExtractor.Formats.Photoshop.PhotoshopDirectory? PhotoshopDirectory = directories.OfType().FirstOrDefault(); + if (PhotoshopDirectory is null) + result = new(null, null); + else + { + string? jpegQuality = PhotoshopDirectory.GetDescription(MetadataExtractor.Formats.Photoshop.PhotoshopDirectory.TagJpegQuality); + string? url = PhotoshopDirectory.GetDescription(MetadataExtractor.Formats.Photoshop.PhotoshopDirectory.TagUrl); + result = new(jpegQuality, url); + } + return result; + } + + private static Shared.Models.GpsDirectory GetGpsDirectory(IReadOnlyList directories) + { + Shared.Models.GpsDirectory result; + GpsDirectory? gpsDirectory = directories.OfType().FirstOrDefault(); + if (gpsDirectory is null) + result = new(null, null, null, null, null, null); + else + { + DateTime? timeStamp; + string? altitude = gpsDirectory.GetDescription(GpsDirectory.TagAltitude); + string? latitude = gpsDirectory.GetDescription(GpsDirectory.TagLatitude); + string? latitudeRef = gpsDirectory.GetDescription(GpsDirectory.TagLatitudeRef); + string? longitude = gpsDirectory.GetDescription(GpsDirectory.TagLongitude); + string? longitudeRef = gpsDirectory.GetDescription(GpsDirectory.TagLongitudeRef); + if (gpsDirectory.TryGetDateTime(GpsDirectory.TagTimeStamp, out DateTime checkDateTime)) + timeStamp = checkDateTime; + else + timeStamp = GetDateTime(gpsDirectory.GetString(GpsDirectory.TagTimeStamp)); + result = new(altitude, + latitude, + latitudeRef, + longitude, + longitudeRef, + timeStamp); + } + return result; + } + + private static Shared.Models.JpegDirectory GetJpegDirectory(IReadOnlyList directories) + { + Shared.Models.JpegDirectory result; + MetadataExtractor.Formats.Jpeg.JpegDirectory? jpegDirectory = directories.OfType().FirstOrDefault(); + if (jpegDirectory is null) + result = new(null, null); + else + { + string? imageHeight = jpegDirectory.GetDescription(MetadataExtractor.Formats.Jpeg.JpegDirectory.TagImageHeight); + string? imageWidth = jpegDirectory.GetDescription(MetadataExtractor.Formats.Jpeg.JpegDirectory.TagImageWidth); + result = new(imageHeight, imageWidth); + } + return result; + } + + private static Shared.Models.PngDirectory GetPngDirectory(IReadOnlyList directories) + { + Shared.Models.PngDirectory result; + MetadataExtractor.Formats.Png.PngDirectory? pngDirectory = directories.OfType().FirstOrDefault(); + if (pngDirectory is null) + result = new(null, null); + else + { + string? imageHeight = pngDirectory.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagImageHeight); + string? imageWidth = pngDirectory.GetDescription(MetadataExtractor.Formats.Png.PngDirectory.TagImageWidth); + result = new(imageHeight, imageWidth); + } + return result; + } + + private static Shared.Models.QuickTimeMovieHeaderDirectory GetQuickTimeMovieHeaderDirectoryDirectory(IReadOnlyList directories) + { + Shared.Models.QuickTimeMovieHeaderDirectory result; + MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory? aviDirectory = directories.OfType().FirstOrDefault(); + if (aviDirectory is null) + result = new(null); + else + { + DateTime? created; + if (aviDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated, out DateTime checkDateTime)) + created = checkDateTime; + else + created = GetDateTime(aviDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeMovieHeaderDirectory.TagCreated)); + result = new(created); + } + return result; + } + + private static Shared.Models.QuickTimeTrackHeaderDirectory GetQuickTimeTrackHeaderDirectoryDirectory(IReadOnlyList directories) + { + Shared.Models.QuickTimeTrackHeaderDirectory result; + MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory? aviDirectory = directories.OfType().FirstOrDefault(); + if (aviDirectory is null) + result = new(null); + else + { + DateTime? created; + if (aviDirectory.TryGetDateTime(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated, out DateTime checkDateTime)) + created = checkDateTime; + else + created = GetDateTime(aviDirectory.GetString(MetadataExtractor.Formats.QuickTime.QuickTimeTrackHeaderDirectory.TagCreated)); + result = new(created); + } + return result; + } + + private static Shared.Models.WebPDirectory GetWebPDirectory(IReadOnlyList directories) + { + Shared.Models.WebPDirectory result; + MetadataExtractor.Formats.WebP.WebPDirectory? WebPDirectory = directories.OfType().FirstOrDefault(); + if (WebPDirectory is null) + result = new(null, null); + else + { + string? imageHeight = WebPDirectory.GetDescription(MetadataExtractor.Formats.WebP.WebPDirectory.TagImageHeight); + string? imageWidth = WebPDirectory.GetDescription(MetadataExtractor.Formats.WebP.WebPDirectory.TagImageWidth); + result = new(imageHeight, imageWidth); + } + return result; + } + + internal static Shared.Models.ExifDirectory Covert(string file, FileInfo fileInfo, System.Drawing.Size? size, IReadOnlyList directories) + { + Shared.Models.ExifDirectory results; + Shared.Models.AviDirectory aviDirectory = GetAviDirectory(directories); + Shared.Models.GpsDirectory gpsDirectory = GetGpsDirectory(directories); + Shared.Models.PngDirectory pngDirectory = GetPngDirectory(directories); + Shared.Models.JpegDirectory jpegDirectory = GetJpegDirectory(directories); + Shared.Models.WebPDirectory webPDirectory = GetWebPDirectory(directories); + Shared.Models.ExifDirectoryBase exifDirectoryBase = GetExifDirectoryBase(directories); + Shared.Models.GifHeaderDirectory gifHeaderDirectory = GetGifHeaderDirectory(directories); + Shared.Models.PhotoshopDirectory photoshopDirectory = GetPhotoshopDirectory(directories); + Shared.Models.FileMetadataDirectory fileMetadataDirectory = GetFileMetadataDirectory(file, directories); + Shared.Models.QuickTimeMovieHeaderDirectory quickTimeMovieHeaderDirectory = GetQuickTimeMovieHeaderDirectoryDirectory(directories); + Shared.Models.QuickTimeTrackHeaderDirectory quickTimeTrackHeaderDirectory = GetQuickTimeTrackHeaderDirectoryDirectory(directories); + results = new(aviDirectory, + exifDirectoryBase, + file, + fileMetadataDirectory, + gifHeaderDirectory, + gpsDirectory, + size?.Height, + fileInfo.FullName, + jpegDirectory, + photoshopDirectory, + pngDirectory, + quickTimeMovieHeaderDirectory, + quickTimeTrackHeaderDirectory, + webPDirectory, + size?.Width); + return results; + } + +} \ No newline at end of file diff --git a/Metadata/Models/Stateless/GPS.cs b/Metadata/Models/Stateless/GPS.cs new file mode 100644 index 0000000..9fdd359 --- /dev/null +++ b/Metadata/Models/Stateless/GPS.cs @@ -0,0 +1,118 @@ +using View_by_Distance.Metadata.Models.Stateless.Methods; + +namespace View_by_Distance.Metadata.Models.Stateless; + +internal abstract class GPS +{ + + private static bool CoordinateValidatorValidate(double latitude, double longitude) + { + if (latitude is < (-90) or > 90) + return false; + if (longitude is < (-180) or > 180) + return false; + + return true; + } + + private static double GetRadius(IMetadata.DistanceUnit distanceUnit) + { + return distanceUnit switch + { + IMetadata.DistanceUnit.Kilometers => 6371.0, // EarthRadiusInKilometers; + IMetadata.DistanceUnit.Meters => 6371000.0, // EarthRadiusInMeters; + IMetadata.DistanceUnit.NauticalMiles => 3440.0, // EarthRadiusInNauticalMiles; + IMetadata.DistanceUnit.Miles => 3959.0, // EarthRadiusInMiles; + _ => throw new NotSupportedException() + }; + } + + private static double ToRadian(double d) => + d * (Math.PI / 180); + + private static double DiffRadian(double val1, double val2) => + ToRadian(val2) - ToRadian(val1); + + internal static double GetDistance(double originLatitude, double originLongitude, double destinationLatitude, double destinationLongitude, int decimalPlaces = 1, IMetadata.DistanceUnit distanceUnit = IMetadata.DistanceUnit.Miles) + { + if (!CoordinateValidatorValidate(originLatitude, originLongitude)) + throw new ArgumentException("Invalid origin coordinates supplied."); + if (!CoordinateValidatorValidate(destinationLatitude, destinationLongitude)) + throw new ArgumentException("Invalid destination coordinates supplied."); + double radius = GetRadius(distanceUnit); + return Math.Round( + radius * 2 * + Math.Asin(Math.Min(1, + Math.Sqrt( + Math.Pow(Math.Sin(DiffRadian(originLatitude, destinationLatitude) / 2.0), 2.0) + + Math.Cos(ToRadian(originLatitude)) * Math.Cos(ToRadian(destinationLatitude)) * + Math.Pow(Math.Sin(DiffRadian(originLongitude, destinationLongitude) / 2.0), + 2.0)))), decimalPlaces); + } + + private static double ParseValueFromDmsString(string value) + { + double result; + if (string.IsNullOrEmpty(value)) + return double.MinValue; + + double secondsValue; + string[] degrees = value.Split('°'); + if (degrees.Length != 2) + return double.MinValue; + if (!double.TryParse(degrees[0], out double degreesValue)) + return double.MinValue; + + string[] minutes = degrees[1].Split('\''); + if (minutes.Length != 2) + return double.MinValue; + if (!double.TryParse(minutes[0], out double minutesValue)) + return double.MinValue; + + string[] seconds = minutes[1].Split('"'); + if (seconds.Length != 2) + secondsValue = 0; + else + { + if (!double.TryParse(seconds[0], out secondsValue)) + return double.MinValue; + } + result = Math.Abs(degreesValue) + (minutesValue / 60) + (secondsValue / 3600); + + if (degreesValue < 0) + result *= -1; + + return result; + } + + // internal static GeoLocation? GeoLocation(ReadOnlyDictionary metadataExtractorDirectories) + // { + // GeoLocation? result; + // if (!metadataExtractorDirectories.TryGetValue("GPS", out MetadataExtractorDirectory? metadataExtractorDirectory)) + // result = null; + // else + // { + // MetadataExtractorTag? metadataExtractorTag; + // if (!metadataExtractorDirectory.Tags.TryGetValue((int)Shared.Models.Stateless.IExif.Tags.GPSLatitude, out metadataExtractorTag) || string.IsNullOrEmpty(metadataExtractorTag.Description)) + // result = null; + // else + // { + // string latitudeDMS = metadataExtractorTag.Description; + // double latitude = ParseValueFromDmsString(latitudeDMS); + // if (!metadataExtractorDirectory.Tags.TryGetValue((int)Shared.Models.Stateless.IExif.Tags.GPSLongitude, out metadataExtractorTag) || string.IsNullOrEmpty(metadataExtractorTag.Description)) + // result = null; + // else + // { + // string longitudeDMS = metadataExtractorTag.Description; + // double longitude = ParseValueFromDmsString(longitudeDMS); + // result = new(latitude, longitude); + // string dms = result.ToDmsString(); + // if ($"{latitudeDMS}, {longitudeDMS}" != dms) + // result = null; + // } + // } + // } + // return result; + // } + +} \ No newline at end of file diff --git a/Metadata/Models/Stateless/Methods/IMetadata.cs b/Metadata/Models/Stateless/Methods/IMetadata.cs new file mode 100644 index 0000000..3b5894a --- /dev/null +++ b/Metadata/Models/Stateless/Methods/IMetadata.cs @@ -0,0 +1,24 @@ +namespace View_by_Distance.Metadata.Models.Stateless.Methods; + +public interface IMetadata +{ + + enum DistanceUnit + { + Miles, + NauticalMiles, + Kilometers, + Meters + } + + Shared.Models.ExifDirectory TestStatic_Convert(string file, FileInfo fileInfo, System.Drawing.Size? size, IReadOnlyList directories) => + Convert(file, fileInfo, size, directories); + static Shared.Models.ExifDirectory Convert(string file, FileInfo fileInfo, System.Drawing.Size? size, IReadOnlyList directories) => + Exif.Covert(file, fileInfo, size, directories); + + double? TestStatic_GetDistance(double originLatitude, double originLongitude, double destinationLatitude, double destinationLongitude, int decimalPlaces = 1, DistanceUnit distanceUnit = DistanceUnit.Miles) => + GetDistance(originLatitude, originLongitude, destinationLatitude, destinationLongitude, decimalPlaces, distanceUnit); + static double? GetDistance(double originLatitude, double originLongitude, double destinationLatitude, double destinationLongitude, int decimalPlaces = 1, DistanceUnit distanceUnit = DistanceUnit.Miles) => + GPS.GetDistance(originLatitude, originLongitude, destinationLatitude, destinationLongitude, decimalPlaces, distanceUnit); + +} \ No newline at end of file diff --git a/Rename/AA.Rename.csproj b/Rename/AA.Rename.csproj new file mode 100644 index 0000000..181d035 --- /dev/null +++ b/Rename/AA.Rename.csproj @@ -0,0 +1,47 @@ + + + enable + enable + Exe + win-x64 + net8.0 + fa0fa59b-afe4-4960-9afc-18fcbc7fb41b + + + Phares.View.by.Distance.Rename + false + 8.0.101.1 + Mike Phares + Phares + true + snupkg + + + true + true + true + + + Windows + + + OSX + + + Linux + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Rename/Models/AppSettings.cs b/Rename/Models/AppSettings.cs new file mode 100644 index 0000000..1c0d3c4 --- /dev/null +++ b/Rename/Models/AppSettings.cs @@ -0,0 +1,26 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Rename.Models; + +public record AppSettings(string Company, + string DefaultUnknownDirectoryName, + bool ForceIdName, + int MaxDegreeOfParallelism, + int MaxMinutesDelta, + bool RenameUndo) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, AppSettingsSourceGenerationContext.Default.AppSettings); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(AppSettings))] +internal partial class AppSettingsSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Rename/Models/Binder/.editorconfig b/Rename/Models/Binder/.editorconfig new file mode 100644 index 0000000..1c444cd --- /dev/null +++ b/Rename/Models/Binder/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +csharp_preserve_single_line_statements = true \ No newline at end of file diff --git a/Rename/Models/Binder/AppSettings.cs b/Rename/Models/Binder/AppSettings.cs new file mode 100644 index 0000000..32cdafc --- /dev/null +++ b/Rename/Models/Binder/AppSettings.cs @@ -0,0 +1,72 @@ +using Microsoft.Extensions.Configuration; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Rename.Models.Binder; + +public class AppSettings +{ + + public string? Company { get; set; } + public string? DefaultUnknownDirectoryName { get; set; } + public bool? ForceIdName { get; set; } + public int? MaxDegreeOfParallelism { get; set; } + public int? MaxMinutesDelta { get; set; } + public bool? RenameUndo { get; set; } + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, BinderAppSettingsSourceGenerationContext.Default.AppSettings); + return result; + } + + private static Models.AppSettings Get(AppSettings? appSettings) + { + Models.AppSettings result; + if (appSettings?.Company is null) throw new NullReferenceException(nameof(appSettings.Company)); + if (appSettings?.DefaultUnknownDirectoryName is null) throw new NullReferenceException(nameof(appSettings.DefaultUnknownDirectoryName)); + if (appSettings?.ForceIdName is null) throw new NullReferenceException(nameof(appSettings.ForceIdName)); + if (appSettings?.MaxDegreeOfParallelism is null) throw new NullReferenceException(nameof(appSettings.MaxDegreeOfParallelism)); + if (appSettings?.MaxMinutesDelta is null) throw new NullReferenceException(nameof(appSettings.MaxMinutesDelta)); + if (appSettings?.RenameUndo is null) throw new NullReferenceException(nameof(appSettings.RenameUndo)); + result = new( + appSettings.Company, + appSettings.DefaultUnknownDirectoryName, + appSettings.ForceIdName.Value, + appSettings.MaxDegreeOfParallelism.Value, + appSettings.MaxMinutesDelta.Value, + appSettings.RenameUndo.Value + ); + return result; + } + + public static Models.AppSettings Get(IConfigurationRoot configurationRoot) + { + Models.AppSettings result; +#pragma warning disable IL3050, IL2026 + AppSettings? appSettings = configurationRoot.Get(); +#pragma warning restore IL3050, IL2026 + if (appSettings?.Company is null) + { + 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; + if (!physicalFileProvider.Root.Contains("UserSecrets")) + continue; + throw new NotSupportedException(physicalFileProvider.Root); + } + } + result = Get(appSettings); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(AppSettings))] +internal partial class BinderAppSettingsSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Rename/Models/Binder/Configuration.cs b/Rename/Models/Binder/Configuration.cs new file mode 100644 index 0000000..6062601 --- /dev/null +++ b/Rename/Models/Binder/Configuration.cs @@ -0,0 +1,62 @@ +using Microsoft.Extensions.Configuration; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Rename.Models.Binder; + +public class Configuration +{ + + public bool? ForceMetadataLastWriteTimeToCreationTime { get; set; } + public string[]? IgnoreExtensions { get; set; } + public string? PersonBirthdayFormat { get; set; } + public bool? PropertiesChangedForMetadata { get; set; } + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, BinderConfigurationSourceGenerationContext.Default.Configuration); + return result; + } + + private static Models.Configuration Get(Configuration? configuration, Metadata.Models.MetadataConfiguration metadataConfiguration) + { + Models.Configuration result; + if (configuration is null) throw new NullReferenceException(nameof(configuration)); + if (configuration.ForceMetadataLastWriteTimeToCreationTime is null) throw new NullReferenceException(nameof(configuration.ForceMetadataLastWriteTimeToCreationTime)); + if (configuration.IgnoreExtensions is null) throw new NullReferenceException(nameof(configuration.IgnoreExtensions)); + if (configuration.PersonBirthdayFormat is null) throw new NullReferenceException(nameof(configuration.PersonBirthdayFormat)); + if (configuration.PropertiesChangedForMetadata is null) throw new NullReferenceException(nameof(configuration.PropertiesChangedForMetadata)); + result = new(metadataConfiguration, + configuration.ForceMetadataLastWriteTimeToCreationTime.Value, + configuration.IgnoreExtensions, + configuration.PersonBirthdayFormat, + configuration.PropertiesChangedForMetadata.Value); + return result; + } + + public static Models.Configuration Get(IConfigurationRoot configurationRoot, Metadata.Models.MetadataConfiguration metadataConfiguration) + { + Models.Configuration result; +#if Linux + string environmentName = "Linux"; +#elif OSX + string environmentName = "OSX"; +#elif Windows + string environmentName = "Windows"; +#endif + string section = string.Concat(environmentName, ":", nameof(Configuration)); + IConfigurationSection configurationSection = configurationRoot.GetSection(section); +#pragma warning disable IL3050, IL2026 + Configuration? configuration = configurationSection.Get(); +#pragma warning restore IL3050, IL2026 + result = Get(configuration, metadataConfiguration); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Configuration))] +internal partial class BinderConfigurationSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Rename/Models/Configuration.cs b/Rename/Models/Configuration.cs new file mode 100644 index 0000000..29d27d9 --- /dev/null +++ b/Rename/Models/Configuration.cs @@ -0,0 +1,26 @@ + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Rename.Models; + +public record Configuration(Metadata.Models.MetadataConfiguration MetadataConfiguration, + bool ForceMetadataLastWriteTimeToCreationTime, + string[] IgnoreExtensions, + string PersonBirthdayFormat, + bool PropertiesChangedForMetadata) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, ConfigurationSourceGenerationContext.Default.Configuration); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(Configuration))] +internal partial class ConfigurationSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Rename/Program.cs b/Rename/Program.cs new file mode 100644 index 0000000..6494063 --- /dev/null +++ b/Rename/Program.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using View_by_Distance.Rename.Models; + +namespace View_by_Distance.Rename; + +public class Program +{ + + public static void Secondary(ILogger logger, List args) + { + IConfigurationBuilder configurationBuilder = new ConfigurationBuilder() + .AddEnvironmentVariables() + .AddUserSecrets(); + IConfigurationRoot configurationRoot = configurationBuilder.Build(); + AppSettings appSettings = Models.Binder.AppSettings.Get(configurationRoot); + if (appSettings.MaxDegreeOfParallelism > Environment.ProcessorCount) + throw new Exception("MaxDegreeOfParallelism must be =< Environment.ProcessorCount!"); + if (string.IsNullOrEmpty(appSettings.Company)) + throw new Exception("Company must have a value!"); + int silentIndex = args.IndexOf("s"); + if (silentIndex > -1) + args.RemoveAt(silentIndex); + try + { + if (args is null) + throw new Exception("args is null!"); + Shared.Models.Console console = new(); + _ = new Rename(args, logger, configurationRoot, appSettings, silentIndex > -1, console); + } + catch (Exception ex) + { + logger?.LogError(ex, "Error!"); + } + if (silentIndex > -1) + logger?.LogDebug("Done. Bye"); + else + { + logger?.LogDebug("Done. Press 'Enter' to end"); + _ = Console.ReadLine(); + } + } + + public static void Main(string[] args) + { +#pragma warning disable IL3050 + ILogger? logger = Host.CreateDefaultBuilder(args).Build().Services.GetRequiredService>(); +#pragma warning restore IL3050 + if (args is not null) + Secondary(logger, args.ToList()); + else + Secondary(logger, []); + } + +} \ No newline at end of file diff --git a/Rename/Rename.cs b/Rename/Rename.cs new file mode 100644 index 0000000..968c168 --- /dev/null +++ b/Rename/Rename.cs @@ -0,0 +1,71 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using ShellProgressBar; +using View_by_Distance.Metadata.Models; +using View_by_Distance.Rename.Models; +using View_by_Distance.Shared.Models; +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.Rename; + +public class Rename +{ + + private readonly AppSettings _AppSettings; + private readonly Configuration _Configuration; + private readonly IConfigurationRoot _ConfigurationRoot; + private readonly MetadataConfiguration _MetadataConfiguration; + + public Rename(List args, ILogger? logger, IConfigurationRoot configurationRoot, AppSettings appSettings, bool isSilent, IConsole console) + { + if (isSilent) + { } + if (args is null) + throw new NullReferenceException(nameof(args)); + if (console is null) + throw new NullReferenceException(nameof(console)); + _AppSettings = appSettings; + _ConfigurationRoot = configurationRoot; + MetadataConfiguration metadataConfiguration = Metadata.Models.Binder.Configuration.Get(configurationRoot); + Configuration configuration = Models.Binder.Configuration.Get(configurationRoot, metadataConfiguration); + _MetadataConfiguration = metadataConfiguration; + _Configuration = configuration; + logger?.LogInformation("{RootDirectory}", metadataConfiguration.RootDirectory); + MetadataConfiguration.Verify(metadataConfiguration, requireExist: false); + Verify(); + List linesB = RenameFilesInDirectories(logger); + if (linesB.Count != 0) + { + File.WriteAllLines($"D:/Tmp/Phares/{DateTime.Now.Ticks}.tsv", linesB); + _ = IPath.DeleteEmptyDirectories(metadataConfiguration.RootDirectory); + } + } + + private void Verify() + { + if (_AppSettings is null) + throw new NullReferenceException(nameof(_AppSettings)); + if (_Configuration is null) + throw new NullReferenceException(nameof(_Configuration)); + if (_ConfigurationRoot is null) + throw new NullReferenceException(nameof(_ConfigurationRoot)); + if (_MetadataConfiguration is null) + throw new NullReferenceException(nameof(_MetadataConfiguration)); + } + + private List RenameFilesInDirectories(ILogger? logger) + { + List old = []; + List exifDirectories = []; + string rootDirectoryFullPath = Path.GetFullPath(_MetadataConfiguration.RootDirectory); + ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = _AppSettings.MaxDegreeOfParallelism }; + IEnumerable files = Directory.EnumerateFiles(rootDirectoryFullPath, "*", SearchOption.AllDirectories); + A_Metadata metadata = new(_MetadataConfiguration, _Configuration.ForceMetadataLastWriteTimeToCreationTime, _Configuration.PropertiesChangedForMetadata); + ProgressBar progressBar = new(123000, "EnumerateFiles load", new ProgressBarOptions() { ProgressCharacter = '─', ProgressBarOnBottom = true, DisableBottomPercentage = true }); + files.AsParallel().ForAll(A_Metadata.GetResultCollection(metadata, exifDirectories, () => progressBar.Tick())); + if (progressBar.CurrentTick != exifDirectories.Count) + throw new NotSupportedException(); + return old; + } + +} \ No newline at end of file diff --git a/Shared/.vscode/mklink.md b/Shared/.vscode/mklink.md new file mode 100644 index 0000000..066d3fa --- /dev/null +++ b/Shared/.vscode/mklink.md @@ -0,0 +1,15 @@ +--- +type: "note" +created: "2023-10-20T03:58:11.564Z" +updated: "2023-10-20T03:59:01.765Z" +--- + +# mklink + +```bash +mklink /J "L:\Git\AA\Shared\.kanbn" "D:\5-Other-Small\Kanban\View-by-Distance" +``` + +```bash +mklink /J "L:\Git\AA\Shared\.kanbn" "D:\5-Other-Small\Kanban\View-by-Distance" +``` diff --git a/Shared/.vscode/settings.json b/Shared/.vscode/settings.json new file mode 100644 index 0000000..754cdcc --- /dev/null +++ b/Shared/.vscode/settings.json @@ -0,0 +1,61 @@ +{ + "cSpell.words": [ + "appsettings", + "Argb", + "ASPNETCORE", + "Barrick", + "bcdfghjklmnpqrstvwxyz", + "Bday", + "Beichler", + "Bohdi", + "Cobertura", + "cref", + "CUDA", + "Dlib", + "DSCN", + "Exif", + "eyeα", + "Getα", + "Greyscale", + "Hasher", + "Hmmss", + "Hmmssfff", + "jfif", + "JOSN", + "mmod", + "Nicéphore", + "Niépce", + "nosj", + "paramref", + "permille", + "permyriad", + "Phares", + "Phgtv", + "Photoshop", + "RDHC", + "recognise", + "Rects", + "resnet", + "Serilog", + "Subfile", + "Subfiles", + "Syncthing", + "Unmanaged", + "unrecognised", + "Upsample", + "Vericruz" + ], + "files.watcherExclude": { + "**/node_modules": true + }, + "cSpell.enabled": true, + "files.exclude": { + "**/.git": false, + "**/node_modules": true + }, + "coverage-gutters.coverageBaseDir": "./.vscode/ReportGenerator/Cobertura/*", + "extensions.ignoreRecommendations": true, + "[markdown]": { + "editor.wordWrap": "off" + } +} \ No newline at end of file diff --git a/Shared/AA.Shared.csproj b/Shared/AA.Shared.csproj new file mode 100644 index 0000000..9a8b3b6 --- /dev/null +++ b/Shared/AA.Shared.csproj @@ -0,0 +1,38 @@ + + + enable + enable + win-x64;linux-x64 + net8.0 + + + Phares.AA.Shared + false + 8.0.101.1 + Mike Phares + Phares + true + snupkg + + + true + true + true + + + Windows + + + OSX + + + Linux + + + + + + + + + \ No newline at end of file diff --git a/Shared/Models/AviDirectory.cs b/Shared/Models/AviDirectory.cs new file mode 100644 index 0000000..745d8b3 --- /dev/null +++ b/Shared/Models/AviDirectory.cs @@ -0,0 +1,24 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record AviDirectory(DateTime? DateTimeOriginal, + string? Duration, + string? Height, + string? Width) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, AviDirectorySourceGenerationContext.Default.AviDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(AviDirectory))] +public partial class AviDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/ExifDirectory.cs b/Shared/Models/ExifDirectory.cs new file mode 100644 index 0000000..45c5dc6 --- /dev/null +++ b/Shared/Models/ExifDirectory.cs @@ -0,0 +1,35 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record ExifDirectory(AviDirectory AviDirectory, + ExifDirectoryBase ExifDirectoryBase, + string File, + FileMetadataDirectory FileMetadataDirectory, + GifHeaderDirectory GifHeaderDirectory, + GpsDirectory GpsDirectory, + int? Height, + string JsonFile, + JpegDirectory JpegDirectory, + PhotoshopDirectory PhotoshopDirectory, + PngDirectory PngDirectory, + QuickTimeMovieHeaderDirectory QuickTimeMovieHeaderDirectory, + QuickTimeTrackHeaderDirectory QuickTimeTrackHeaderDirectory, + WebPDirectory WebPDirectory, + int? Width) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, ExifDirectorySourceGenerationContext.Default.ExifDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(ExifDirectory))] +public partial class ExifDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/ExifDirectoryBase.cs b/Shared/Models/ExifDirectoryBase.cs new file mode 100644 index 0000000..8235471 --- /dev/null +++ b/Shared/Models/ExifDirectoryBase.cs @@ -0,0 +1,66 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record ExifDirectoryBase(string? Aperture, + string? ApplicationNotes, + string? Artist, + string? BitsPerSample, + string? BodySerialNumber, + string? CameraOwnerName, + string? CompressedAverageBitsPerPixel, + string? Compression, + string? Copyright, + DateTime? DateTime, + DateTime? DateTimeDigitized, + DateTime? DateTimeOriginal, + string? DocumentName, + string? ExifVersion, + string? ExposureTime, + string? FileSource, + string? ImageDescription, + string? ImageHeight, + string? ImageNumber, + string? ImageUniqueId, + string? ImageWidth, + string? IsoSpeed, + string? LensMake, + string? LensModel, + string? LensSerialNumber, + string? Make, + string? MakerNote, + string? Model, + string? Orientation, + int? OrientationValue, + string? Rating, + string? RatingPercent, + string? SecurityClassification, + string? ShutterSpeed, + string? Software, + string? TimeZone, + string? TimeZoneDigitized, + string? TimeZoneOriginal, + string? UserComment, + string? WinAuthor, + string? WinComment, + string? WinKeywords, + string? WinSubject, + string? WinTitle, + string? XResolution, + string? YResolution) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, ExifDirectoryBaseSourceGenerationContext.Default.ExifDirectoryBase); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(ExifDirectoryBase))] +public partial class ExifDirectoryBaseSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/FileHolder.cs b/Shared/Models/FileHolder.cs new file mode 100644 index 0000000..562a2ce --- /dev/null +++ b/Shared/Models/FileHolder.cs @@ -0,0 +1,82 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public class FileHolder +{ + + protected readonly DateTime? _CreationTime; + protected readonly string? _DirectoryName; + protected readonly bool _Exists; + protected readonly string _ExtensionLowered; + protected readonly string _FullName; + protected readonly int? _Id; + protected readonly DateTime? _LastWriteTime; + protected readonly long? _Length; + protected readonly string _Name; + protected readonly string _NameWithoutExtension; + public DateTime? CreationTime => _CreationTime; + public string? DirectoryName => _DirectoryName; + public bool Exists => _Exists; + public string ExtensionLowered => _ExtensionLowered; + public string FullName => _FullName; + public int? Id => _Id; + public DateTime? LastWriteTime => _LastWriteTime; + public long? Length => _Length; + public string Name => _Name; + public string NameWithoutExtension => _NameWithoutExtension; + + public FileHolder(DateTime? creationTime, string? directoryName, bool exists, string extensionLowered, string fullName, int? id, DateTime? lastWriteTime, long? length, string name, string nameWithoutExtension) + { + _CreationTime = creationTime; + _DirectoryName = directoryName; + _Exists = exists; + _ExtensionLowered = extensionLowered; + _FullName = fullName; + _Id = id; + _LastWriteTime = lastWriteTime; + _Length = length; + _Name = name; + _NameWithoutExtension = nameWithoutExtension; + } + + public FileHolder(FileInfo fileInfo, int? id) + { + if (fileInfo.Exists) + { + _CreationTime = fileInfo.CreationTime; + _CreationTime = fileInfo.CreationTime; + _LastWriteTime = fileInfo.LastWriteTime; + _Length = fileInfo.Length; + } + _DirectoryName = fileInfo.DirectoryName; + _Exists = fileInfo.Exists; + _ExtensionLowered = fileInfo.Extension.ToLower(); + _Id = id; + _FullName = fileInfo.FullName; + _Name = fileInfo.Name; + _NameWithoutExtension = Path.GetFileNameWithoutExtension(fileInfo.FullName); + } + + public FileHolder(string fileName) : + this(new FileInfo(fileName), null) + { } + + public FileHolder(string fileName, int? id) : + this(new FileInfo(fileName), id) + { } + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, FileHolderSourceGenerationContext.Default.FileHolder); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(FileHolder))] +internal partial class FileHolderSourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/FileMetadataDirectory.cs b/Shared/Models/FileMetadataDirectory.cs new file mode 100644 index 0000000..1249ef5 --- /dev/null +++ b/Shared/Models/FileMetadataDirectory.cs @@ -0,0 +1,23 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record FileMetadataDirectory(DateTime? FileModifiedDate, + string? FileName, + string? FileSize) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, FileMetadataDirectorySourceGenerationContext.Default.FileMetadataDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(FileMetadataDirectory))] +public partial class FileMetadataDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/GifHeaderDirectory.cs b/Shared/Models/GifHeaderDirectory.cs new file mode 100644 index 0000000..11342d9 --- /dev/null +++ b/Shared/Models/GifHeaderDirectory.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record GifHeaderDirectory(string? ImageHeight, + string? ImageWidth) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, GifHeaderDirectorySourceGenerationContext.Default.GifHeaderDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(GifHeaderDirectory))] +public partial class GifHeaderDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/GpsDirectory.cs b/Shared/Models/GpsDirectory.cs new file mode 100644 index 0000000..beef966 --- /dev/null +++ b/Shared/Models/GpsDirectory.cs @@ -0,0 +1,26 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record GpsDirectory(string? Altitude, + string? Latitude, + string? LatitudeRef, + string? Longitude, + string? LongitudeRef, + DateTime? TimeStamp) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, GpsDirectorySourceGenerationContext.Default.GpsDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(GpsDirectory))] +public partial class GpsDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/JpegDirectory.cs b/Shared/Models/JpegDirectory.cs new file mode 100644 index 0000000..10747bf --- /dev/null +++ b/Shared/Models/JpegDirectory.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record JpegDirectory(string? ImageHeight, + string? ImageWidth) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, JpegDirectorySourceGenerationContext.Default.JpegDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(JpegDirectory))] +public partial class JpegDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/PhotoshopDirectory.cs b/Shared/Models/PhotoshopDirectory.cs new file mode 100644 index 0000000..ee42850 --- /dev/null +++ b/Shared/Models/PhotoshopDirectory.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record PhotoshopDirectory(string? JpegQuality, + string? Url) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, PhotoshopDirectorySourceGenerationContext.Default.PhotoshopDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(PhotoshopDirectory))] +public partial class PhotoshopDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/PngDirectory.cs b/Shared/Models/PngDirectory.cs new file mode 100644 index 0000000..4951a7f --- /dev/null +++ b/Shared/Models/PngDirectory.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record PngDirectory(string? ImageHeight, + string? ImageWidth) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, PngDirectorySourceGenerationContext.Default.PngDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(PngDirectory))] +public partial class PngDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/Properties/IAAConfiguration.cs b/Shared/Models/Properties/IAAConfiguration.cs new file mode 100644 index 0000000..0ab856d --- /dev/null +++ b/Shared/Models/Properties/IAAConfiguration.cs @@ -0,0 +1,25 @@ +namespace View_by_Distance.Shared.Models.Properties; + +public interface IAAConfiguration +{ + + public string DateGroup { init; get; } + public string[] IgnoreExtensions { init; get; } + public string[] IgnoreRulesKeyWords { init; get; } + public string PersonBirthdayFormat { init; get; } + public bool PropertiesChangedForProperty { init; get; } + public string[] PropertyContentCollectionFiles { init; get; } + public string ResultAllInOne { init; get; } + public int ResultAllInOneSubdirectoryLength { init; get; } + public string ResultCollection { init; get; } + public string ResultContent { init; get; } + public string ResultSingleton { init; get; } + public string[] ValidImageFormatExtensions { init; get; } + public string? ModelName { get; } + public int? NumberOfJitters { get; } + public int? NumberOfTimesToUpsample { get; } + public int Offset { init; get; } + public string? PredictorModelName { get; } + public string RootDirectory { get; } + +} \ No newline at end of file diff --git a/Shared/Models/QuickTimeMovieHeaderDirectory.cs b/Shared/Models/QuickTimeMovieHeaderDirectory.cs new file mode 100644 index 0000000..0601dbc --- /dev/null +++ b/Shared/Models/QuickTimeMovieHeaderDirectory.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record QuickTimeMovieHeaderDirectory(DateTime? Created) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, QuickTimeMovieHeaderDirectorySourceGenerationContext.Default.QuickTimeMovieHeaderDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(QuickTimeMovieHeaderDirectory))] +public partial class QuickTimeMovieHeaderDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/QuickTimeTrackHeaderDirectory.cs b/Shared/Models/QuickTimeTrackHeaderDirectory.cs new file mode 100644 index 0000000..236c39c --- /dev/null +++ b/Shared/Models/QuickTimeTrackHeaderDirectory.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record QuickTimeTrackHeaderDirectory(DateTime? Created) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, QuickTimeTrackHeaderDirectorySourceGenerationContext.Default.QuickTimeTrackHeaderDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(QuickTimeTrackHeaderDirectory))] +public partial class QuickTimeTrackHeaderDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Console.cs b/Shared/Models/Stateless/Console.cs new file mode 100644 index 0000000..892eb55 --- /dev/null +++ b/Shared/Models/Stateless/Console.cs @@ -0,0 +1,20 @@ +using View_by_Distance.Shared.Models.Stateless.Methods; + +namespace View_by_Distance.Shared.Models; + +public class Console : IConsole +{ + + ConsoleKey IConsole.ReadKey() + { + ConsoleKey result = System.Console.ReadKey().Key; + return result; + } + + string? IConsole.ReadLine() + { + string? result = System.Console.ReadLine(); + return result; + } + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Id.cs b/Shared/Models/Stateless/Id.cs new file mode 100644 index 0000000..a048f37 --- /dev/null +++ b/Shared/Models/Stateless/Id.cs @@ -0,0 +1,20 @@ +namespace View_by_Distance.Shared.Models.Stateless; + +internal abstract class Id +{ + + internal static bool NameWithoutExtensionIsIdFormat(string fileNameWithoutExtension) + { + bool result; + int intMinValueLength = int.MinValue.ToString().Length; + if (fileNameWithoutExtension.Length < 5 || fileNameWithoutExtension.Length > intMinValueLength) + result = false; + else + { + bool skipOneAllAreNumbers = fileNameWithoutExtension[1..].All(l => char.IsNumber(l)); + result = (skipOneAllAreNumbers && fileNameWithoutExtension[0] == '-') || (skipOneAllAreNumbers && char.IsNumber(fileNameWithoutExtension[0])); + } + return result; + } + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IConsole.cs b/Shared/Models/Stateless/Methods/IConsole.cs new file mode 100644 index 0000000..80f46e0 --- /dev/null +++ b/Shared/Models/Stateless/Methods/IConsole.cs @@ -0,0 +1,9 @@ +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface IConsole +{ + + string? ReadLine(); + ConsoleKey ReadKey(); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IDate.cs b/Shared/Models/Stateless/Methods/IDate.cs new file mode 100644 index 0000000..3b5ffda --- /dev/null +++ b/Shared/Models/Stateless/Methods/IDate.cs @@ -0,0 +1,16 @@ +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface IDate +{ + + (bool?, string[]) TestStatic_IsWrongYear(string[] segments, string year) => + IsWrongYear(segments, year); + static (bool?, string[]) IsWrongYear(string[] segments, string year) => + XDate.IsWrongYear(segments, year); + + (int Season, string seasonName) TestStatic_GetSeason(int dayOfYear) => + GetSeason(dayOfYear); + static (int Season, string seasonName) GetSeason(int dayOfYear) => + XDate.GetSeason(dayOfYear); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IId.cs b/Shared/Models/Stateless/Methods/IId.cs new file mode 100644 index 0000000..4e44718 --- /dev/null +++ b/Shared/Models/Stateless/Methods/IId.cs @@ -0,0 +1,39 @@ +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface IId +{ + + bool TestStatic_NameWithoutExtensionIsIdFormat(string fileNameWithoutExtension) => + NameWithoutExtensionIsIdFormat(fileNameWithoutExtension); + static bool NameWithoutExtensionIsIdFormat(string fileNameWithoutExtension) => + Id.NameWithoutExtensionIsIdFormat(fileNameWithoutExtension); + + bool TestStatic_NameWithoutExtensionIsIdFormat(FileHolder fileHolder) => + NameWithoutExtensionIsIdFormat(fileHolder); + static bool NameWithoutExtensionIsIdFormat(FileHolder fileHolder) => + NameWithoutExtensionIsIdFormat(fileHolder.NameWithoutExtension); + + string TestStatic_GetPaddedId(int intMinValueLength, int index, int id) => + GetPaddedId(intMinValueLength, index, id); + static string GetPaddedId(int intMinValueLength, int index, int id) => + id > -1 ? $"{index}070{id.ToString().PadLeft(intMinValueLength, '0')}" : $"{index}030{id.ToString()[1..].PadLeft(intMinValueLength, '0')}"; + + bool TestStatic_NameWithoutExtensionIsPaddedIdFormat(string fileNameWithoutExtension, int sortOrderOnlyLengthIndex) => + NameWithoutExtensionIsPaddedIdFormat(fileNameWithoutExtension, sortOrderOnlyLengthIndex); + static bool NameWithoutExtensionIsPaddedIdFormat(string fileNameWithoutExtension, int sortOrderOnlyLengthIndex) => + fileNameWithoutExtension.Length > sortOrderOnlyLengthIndex + && fileNameWithoutExtension[sortOrderOnlyLengthIndex] == '0' + && fileNameWithoutExtension[sortOrderOnlyLengthIndex - 3] == '0' + && fileNameWithoutExtension.All(l => char.IsNumber(l)); + + short TestStatic_GetSortOrderOnlyLengthIndex(int offset) => + GetSortOrderOnlyLengthIndex(offset); + static short GetSortOrderOnlyLengthIndex(int offset) => + (short)(offset.ToString().Length + 3); + + bool TestStatic_NameWithoutExtensionIsPaddedIdFormat(FileHolder fileHolder, int sortOrderOnlyLengthIndex) => + NameWithoutExtensionIsPaddedIdFormat(fileHolder, sortOrderOnlyLengthIndex); + static bool NameWithoutExtensionIsPaddedIdFormat(FileHolder fileHolder, int sortOrderOnlyLengthIndex) => + NameWithoutExtensionIsPaddedIdFormat(fileHolder.NameWithoutExtension, sortOrderOnlyLengthIndex); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IPath.cs b/Shared/Models/Stateless/Methods/IPath.cs new file mode 100644 index 0000000..121c625 --- /dev/null +++ b/Shared/Models/Stateless/Methods/IPath.cs @@ -0,0 +1,74 @@ +using View_by_Distance.Shared.Models.Properties; + +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface IPath +{ // ... + + string TestStatic_GetRelativePath(string path, int length) => + GetRelativePath(path, length); + static string GetRelativePath(string path, int length) => + XPath.GetRelativePath(path, length, forceExtensionToLower: false); + + bool TestStatic_DeleteEmptyDirectories(string rootDirectory) => + DeleteEmptyDirectories(rootDirectory); + static bool DeleteEmptyDirectories(string rootDirectory) => + XPath.DeleteEmptyDirectories(rootDirectory); + + void TestStatic_ChangeDateForEmptyDirectories(string rootDirectory, long ticks) => + ChangeDateForEmptyDirectories(rootDirectory, ticks); + static void ChangeDateForEmptyDirectories(string rootDirectory, long ticks) => + XPath.ChangeDateForEmptyDirectories(rootDirectory, ticks); + + void TestStatic_MakeHiddenIfAllItemsAreHidden(string rootDirectory) => + MakeHiddenIfAllItemsAreHidden(rootDirectory); + static void MakeHiddenIfAllItemsAreHidden(string rootDirectory) => + XPath.MakeHiddenIfAllItemsAreHidden(rootDirectory); + + void TestStatic_DeleteEmptyDirectories(string rootDirectory, List deletedDirectories) => + DeleteEmptyDirectories(rootDirectory, deletedDirectories); + static void DeleteEmptyDirectories(string rootDirectory, List deletedDirectories) => + XPath.DeleteEmptyDirectories(rootDirectory, deletedDirectories); + // $dirs = gci "" -directory -recurse | Where { (gci $_.fullName).count -eq 0 } | select -expandproperty FullName $dirs | Foreach-Object { Remove-Item $_ } + + string[] TestStatic_GetDirectoryNames(string directory) => + GetDirectoryNames(directory); + static string[] GetDirectoryNames(string directory) => + XPath.GetDirectoryNames(directory).ToArray(); + + string[] TestStatic_GetDirectories(string directory) => + GetDirectories(directory); + static string[] GetDirectories(string directory) => + XPath.GetDirectories(directory).ToArray(); + + string TestStatic_GetRelativePath(string path, int length, bool forceExtensionToLower) => + GetRelativePath(path, length, forceExtensionToLower); + static string GetRelativePath(string path, int length, bool forceExtensionToLower) => + XPath.GetRelativePath(path, length, forceExtensionToLower); + + bool TestStatic_WriteAllText(string path, string contents, bool updateDateWhenMatches, bool compareBeforeWrite, DateTime? updateToWhenMatches = null) => + WriteAllText(path, contents, updateDateWhenMatches, compareBeforeWrite, updateToWhenMatches); + static bool WriteAllText(string path, string contents, bool updateDateWhenMatches, bool compareBeforeWrite, DateTime? updateToWhenMatches = null) => + XPath.WriteAllText(path, contents, updateDateWhenMatches, compareBeforeWrite, updateToWhenMatches); + + (int level, List directories) TestStatic_Get(string rootDirectory, string sourceDirectory) => + Get(rootDirectory, sourceDirectory); + static (int level, List directories) Get(string rootDirectory, string sourceDirectory) => + XPath.Get(rootDirectory, sourceDirectory); + + string TestStatic_GetDirectory(string sourceDirectory, int level, string directoryName) => + GetDirectory(sourceDirectory, level, directoryName); + static string GetDirectory(string sourceDirectory, int level, string directoryName) => + XPath.GetDirectory(sourceDirectory, level, directoryName); + + (string, int) TestStatic_GetDirectoryNameAndIndex(int resultAllInOneSubdirectoryLength, string fileName) => + GetDirectoryNameAndIndex(resultAllInOneSubdirectoryLength, fileName); + static (string, int) GetDirectoryNameAndIndex(int resultAllInOneSubdirectoryLength, string fileName) => + XPath.GetDirectoryNameAndIndex(resultAllInOneSubdirectoryLength, fileName); + + Dictionary TestStatic_GetKeyValuePairs(IAAConfiguration aAConfiguration, string? resultsFullGroupDirectory, string[]? directories) => + GetKeyValuePairs(aAConfiguration, resultsFullGroupDirectory, directories); + static Dictionary GetKeyValuePairs(IAAConfiguration propertyConfiguration, string? resultsFullGroupDirectory, string[]? directories) => + XPath.GetKeyValuePairs(propertyConfiguration, resultsFullGroupDirectory, directories); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Methods/IResult.cs b/Shared/Models/Stateless/Methods/IResult.cs new file mode 100644 index 0000000..60cf7ab --- /dev/null +++ b/Shared/Models/Stateless/Methods/IResult.cs @@ -0,0 +1,48 @@ +using View_by_Distance.Shared.Models.Properties; + +namespace View_by_Distance.Shared.Models.Stateless.Methods; + +public interface IResult +{ + + string TestStatic_GetRelativePath(IAAConfiguration aAConfiguration, string path) => + GetRelativePath(aAConfiguration, path); + static string GetRelativePath(IAAConfiguration aAConfiguration, string path) => + XResult.GetRelativePath(aAConfiguration, path); + + string TestStatic_GetResultsGroupDirectory(IAAConfiguration aAConfiguration, string description, bool create) => + GetResultsGroupDirectory(aAConfiguration, description, create); + static string GetResultsGroupDirectory(IAAConfiguration aAConfiguration, string description, bool create) => + XResult.GetResultsGroupDirectory(aAConfiguration, description, create); + + string TestStatic_GetResultsGroupDirectory(IAAConfiguration aAConfiguration, string description) => + GetResultsGroupDirectory(aAConfiguration, description); + static string GetResultsGroupDirectory(IAAConfiguration aAConfiguration, string description) => + XResult.GetResultsGroupDirectory(aAConfiguration, description, create: true); + + string TestStatic_GetResultsDateGroupDirectory(IAAConfiguration aAConfiguration, string description) => + GetResultsDateGroupDirectory(aAConfiguration, description); + static string GetResultsDateGroupDirectory(IAAConfiguration aAConfiguration, string description) => + XResult.GetResultsDateGroupDirectory(aAConfiguration, description); + + string TestStatic_GetResultsDateGroupDirectory(IAAConfiguration aAConfiguration, string description, string jsonGroup) => + GetResultsDateGroupDirectory(aAConfiguration, description, jsonGroup); + static string GetResultsDateGroupDirectory(IAAConfiguration aAConfiguration, string description, string jsonGroup) => + XResult.GetResultsDateGroupDirectory(aAConfiguration, description, jsonGroup); + + List TestStatic_GetDirectoryInfoCollection(IAAConfiguration aAConfiguration, string sourceDirectory, string dateGroupDirectory, string contentDescription, string singletonDescription, string collectionDescription, bool converted) => + GetDirectoryInfoCollection(aAConfiguration, sourceDirectory, dateGroupDirectory, contentDescription, singletonDescription, collectionDescription, converted); + static List GetDirectoryInfoCollection(IAAConfiguration aAConfiguration, string sourceDirectory, string dateGroupDirectory, string contentDescription, string singletonDescription, string collectionDescription, bool converted) => + XResult.GetDirectoryInfoCollection(aAConfiguration, sourceDirectory, dateGroupDirectory, contentDescription, singletonDescription, collectionDescription, converted); + + string TestStatic_GetResultsFullGroupDirectory(IAAConfiguration aAConfiguration, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel) => + GetResultsFullGroupDirectory(aAConfiguration, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel); + static string GetResultsFullGroupDirectory(IAAConfiguration aAConfiguration, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel) => + XResult.GetResultsFullGroupDirectory(aAConfiguration, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel); + + List TestStatic_GetDirectoryInfoCollection(IAAConfiguration aAConfiguration, string sourceDirectory, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel, string contentDescription, string singletonDescription, string collectionDescription) => + GetDirectoryInfoCollection(aAConfiguration, sourceDirectory, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel, contentDescription, singletonDescription, collectionDescription); + static List GetDirectoryInfoCollection(IAAConfiguration aAConfiguration, string sourceDirectory, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel, string contentDescription, string singletonDescription, string collectionDescription) => + XResult.GetDirectoryInfoCollection(aAConfiguration, sourceDirectory, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel, contentDescription, singletonDescription, collectionDescription); + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/Model.cs b/Shared/Models/Stateless/Model.cs new file mode 100644 index 0000000..96214dc --- /dev/null +++ b/Shared/Models/Stateless/Model.cs @@ -0,0 +1,24 @@ +namespace View_by_Distance.Shared.Models.Stateless; + +/// +/// Specifies the model of face detector. +/// +public enum Model +{ + + /// + /// Specifies that the model is HOG (Histograms of Oriented Gradients) based face detector. + /// + Hog, + + /// + /// Specifies that the model is CNN (Convolutional Neural Network) based face detector. + /// + Cnn, + + /// + /// Specifies that the custom face detector. + /// + Custom + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/PredictorModel.cs b/Shared/Models/Stateless/PredictorModel.cs new file mode 100644 index 0000000..8770e80 --- /dev/null +++ b/Shared/Models/Stateless/PredictorModel.cs @@ -0,0 +1,24 @@ +namespace View_by_Distance.Shared.Models.Stateless; + +/// +/// Specifies the dimension of vector which be returned from detector. +/// +public enum PredictorModel +{ + + /// + /// Specifies that the large scale detector. The detector returns 68 points for represent face. + /// + Large, + + /// + /// Specifies that the small scale detector. The detector returns 5 points for represent face. + /// + Small, + + /// + /// Specifies that the custom detector. + /// + Custom + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/XDate.cs b/Shared/Models/Stateless/XDate.cs new file mode 100644 index 0000000..37e4919 --- /dev/null +++ b/Shared/Models/Stateless/XDate.cs @@ -0,0 +1,50 @@ +namespace View_by_Distance.Shared.Models.Stateless; + +internal abstract class XDate +{ + + internal static (int Season, string seasonName) GetSeason(int dayOfYear) + { + (int Season, string seasonName) result = dayOfYear switch + { + < 78 => new(0, "Winter"), + < 171 => new(1, "Spring"), + < 264 => new(2, "Summer"), + < 354 => new(3, "Fall"), + _ => new(4, "Winter") + }; + return result; + } + + internal static (bool?, string[]) IsWrongYear(string[] segments, string year) + { + bool? result; + string[] results = ( + from l + in segments + where l?.Length > 2 + && ( + l[..2] is "18" or "19" or "20" + || (l.Length == 5 && l.Substring(1, 2) is "18" or "19" or "20" && (l[0] is '~' or '=' or '-' or '^' or '#')) + || (l.Length == 6 && l[..2] is "18" or "19" or "20" && l[4] == '.') + || (l.Length == 7 && l.Substring(1, 2) is "18" or "19" or "20" && l[5] == '.') + ) + select l + ).ToArray(); + string[] matches = ( + from l + in results + where l == year + || (l.Length == 5 && l.Substring(1, 4) == year && (l[0] is '~' or '=' or '-' or '^' or '#')) + || (l.Length == 6 && l[..4] == year && l[4] == '.') + || (l.Length == 7 && l.Substring(1, 4) == year && l[5] == '.') + select l + ).ToArray(); + if (results.Length == 0) + result = null; + else + result = matches.Length == 0; + return new(result, results); + } + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/XPath.cs b/Shared/Models/Stateless/XPath.cs new file mode 100644 index 0000000..4cac013 --- /dev/null +++ b/Shared/Models/Stateless/XPath.cs @@ -0,0 +1,331 @@ +using View_by_Distance.Shared.Models.Properties; + +namespace View_by_Distance.Shared.Models.Stateless; + +internal abstract class XPath +{ + + internal static string GetRelativePath(string path, int length, bool forceExtensionToLower) + { + string result; + if (forceExtensionToLower) + { + string extension = Path.GetExtension(path); + string extensionLowered = Path.GetExtension(path).ToLower(); + if (extension != extensionLowered) + { + string? directoryName = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(directoryName)) + throw new NullReferenceException(directoryName); + string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path); + if (string.IsNullOrEmpty(fileNameWithoutExtension)) + throw new NullReferenceException(fileNameWithoutExtension); + path = Path.Combine(directoryName, $"{fileNameWithoutExtension}{extensionLowered}"); + } + } + result = path[length..].Replace(@"\", "/"); + return result; + } + + internal static bool DeleteEmptyDirectories(string rootDirectory) + { + bool result; + List results = []; + DeleteEmptyDirectories(rootDirectory, results); + result = results.Count > 0; + return result; + } + + internal static void DeleteEmptyDirectories(string rootDirectory, List deletedDirectories) + { + if (Directory.Exists(rootDirectory)) + { + string[] files; + string[] directories = Directory.GetDirectories(rootDirectory, "*", SearchOption.TopDirectoryOnly); + if (directories.Length > 0) + files = []; + else + files = Directory.GetFiles(rootDirectory, "*", SearchOption.AllDirectories); + if (directories.Length == 0 && files.Length == 0) + { + deletedDirectories.Add(rootDirectory); + try + { Directory.Delete(rootDirectory); } + catch (UnauthorizedAccessException) + { + new DirectoryInfo(rootDirectory).Attributes = FileAttributes.Normal; + Directory.Delete(rootDirectory); + } + } + else + { + List check = []; + foreach (string directory in directories) + { + DeleteEmptyDirectories(directory, check); + deletedDirectories.AddRange(check); + if (check.Count > 0) + DeleteEmptyDirectories(directory, deletedDirectories); + } + } + } + } + + internal static bool WriteAllText(string path, string contents, bool updateDateWhenMatches, bool compareBeforeWrite, DateTime? updateToWhenMatches) + { + bool result; + string text; + if (!compareBeforeWrite) + result = true; + else + { + if (!File.Exists(path)) + text = string.Empty; + else + text = File.ReadAllText(path); + result = text != contents; + if (!result && updateDateWhenMatches) + { + if (updateToWhenMatches is null) + File.SetLastWriteTime(path, DateTime.Now); + else + File.SetLastWriteTime(path, updateToWhenMatches.Value); + } + } + if (result) + { + if (path.Contains("()")) + File.WriteAllText(path, contents); + else if (path.Contains("{}") && !path.EndsWith(".json")) + File.WriteAllText(path, contents); + else if (path.Contains("[]") && !path.EndsWith(".json")) + File.WriteAllText(path, contents); + else if (path.Contains("{}") && path.EndsWith(".json") && contents[0] == '{') + File.WriteAllText(path, contents); + else if (path.Contains("[]") && path.EndsWith(".json") && contents[0] == '[') + File.WriteAllText(path, contents); + else + File.WriteAllText(path, contents); + } + return result; + } + + internal static List GetDirectoryNames(string directory) + { + List results = []; + string? fileName; + string? checkDirectory = directory; + string? pathRoot = Path.GetPathRoot(directory); + string extension = Path.GetExtension(directory); + if (string.IsNullOrEmpty(pathRoot)) + throw new NullReferenceException(nameof(pathRoot)); + if (Directory.Exists(directory)) + { + fileName = Path.GetFileName(directory); + if (!string.IsNullOrEmpty(fileName)) + results.Add(fileName); + } + else if ((string.IsNullOrEmpty(extension) || extension.Length > 3) && !File.Exists(directory)) + { + fileName = Path.GetFileName(directory); + if (!string.IsNullOrEmpty(fileName)) + results.Add(fileName); + } + for (int i = 0; i < int.MaxValue; i++) + { + checkDirectory = Path.GetDirectoryName(checkDirectory); + if (string.IsNullOrEmpty(checkDirectory) || checkDirectory == pathRoot) + break; + fileName = Path.GetFileName(checkDirectory); + if (string.IsNullOrEmpty(fileName)) + continue; + results.Add(fileName); + } + results.Add(pathRoot); + results.Reverse(); + return results; + } + + internal static List GetDirectories(string directory) + { + List results = []; + string? checkDirectory = directory; + string? pathRoot = Path.GetPathRoot(directory); + if (string.IsNullOrEmpty(pathRoot)) + throw new NullReferenceException(nameof(pathRoot)); + if (Directory.Exists(directory)) + results.Add(directory); + for (int i = 0; i < int.MaxValue; i++) + { + checkDirectory = Path.GetDirectoryName(checkDirectory); + if (string.IsNullOrEmpty(checkDirectory) || checkDirectory == pathRoot) + break; + results.Add(checkDirectory); + } + results.Add(pathRoot); + results.Reverse(); + return results; + } + + internal static (int level, List directories) Get(string rootDirectory, string sourceDirectory) + { + int result = 0; + string? directory; + string? checkDirectory; + List results = []; + checkDirectory = sourceDirectory; + for (int i = 0; i < int.MaxValue; i++) + { + result += 1; + directory = Path.GetFileName(checkDirectory); + if (string.IsNullOrEmpty(directory)) + break; + results.Add(directory); + checkDirectory = Path.GetDirectoryName(checkDirectory); + if (checkDirectory == rootDirectory) + break; + } + results.Reverse(); + return new(result, results); + } + + internal static string GetDirectory(string sourceDirectory, int level, string directoryName) + { + string result; + string? checkDirectory; + checkDirectory = Path.GetDirectoryName(sourceDirectory); + for (int i = 0; i < level; i++) + checkDirectory = Path.GetDirectoryName(checkDirectory); + if (string.IsNullOrEmpty(checkDirectory)) + throw new Exception(); + checkDirectory = Path.Combine(checkDirectory, directoryName); + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + result = checkDirectory; + return result; + } + + internal static void ChangeDateForEmptyDirectories(string rootDirectory, long ticks) + { + DateTime dateTime = new(ticks); + IEnumerable fileSystemEntries; + string[] directories; + if (!Directory.Exists(rootDirectory)) + directories = []; + else + directories = Directory.GetDirectories(rootDirectory, "*", SearchOption.AllDirectories); + foreach (string directory in directories) + { + fileSystemEntries = Directory.EnumerateFileSystemEntries(directory, "*", SearchOption.TopDirectoryOnly); + if (fileSystemEntries.Any()) + continue; + Directory.SetLastWriteTime(directory, dateTime); + } + } + + internal static void MakeHiddenIfAllItemsAreHidden(string rootDirectory) + { + bool check; + FileInfo fileInfo; + IEnumerable files; + DirectoryInfo directoryInfo; + IEnumerable subDirectories; + string[] directories = Directory.GetDirectories(rootDirectory, "*", SearchOption.AllDirectories); + foreach (string directory in directories) + { + directoryInfo = new(directory); + if (directoryInfo.Attributes.HasFlag(FileAttributes.Hidden)) + continue; + check = true; + subDirectories = Directory.EnumerateDirectories(directory, "*", SearchOption.TopDirectoryOnly); + foreach (string subDirectory in subDirectories) + { + directoryInfo = new(subDirectory); + if (!directoryInfo.Attributes.HasFlag(FileAttributes.Hidden)) + { + check = false; + break; + } + } + if (!check) + continue; + files = Directory.EnumerateFiles(directory, "*", SearchOption.TopDirectoryOnly); + foreach (string file in files) + { + fileInfo = new(file); + if (!fileInfo.Attributes.HasFlag(FileAttributes.Hidden)) + { + check = false; + break; + } + } + if (!check) + continue; + directoryInfo.Attributes |= FileAttributes.Hidden; + } + } + + internal static (string, int) GetDirectoryNameAndIndex(int resultAllInOneSubdirectoryLength, string fileName) + { + int converted; + string result; + string check = fileName.Length < resultAllInOneSubdirectoryLength ? new('-', resultAllInOneSubdirectoryLength) : fileName.Split('.')[0][^resultAllInOneSubdirectoryLength..]; + if (check.Any(l => !char.IsNumber(l))) + { + result = new('-', resultAllInOneSubdirectoryLength); + converted = int.Parse($"1{new string('0', resultAllInOneSubdirectoryLength)}"); + } + else + { + result = check; + converted = int.Parse(check); + } + return (result, converted); + } + + internal static Dictionary GetKeyValuePairs(IAAConfiguration aAConfiguration, string? resultsFullGroupDirectory, string[]? directories) + { + Dictionary results = []; + string directory; + string checkDirectory; + int converted = int.Parse($"1{new string('0', aAConfiguration.ResultAllInOneSubdirectoryLength)}"); + int plusOne = converted + 1; + List collection = []; + if (directories is not null) + { + foreach (string key in directories) + { + if (resultsFullGroupDirectory is null) + continue; + collection.Clear(); + for (int i = 0; i < plusOne; i++) + { + if (string.IsNullOrEmpty(key)) + { + if (i == converted) + checkDirectory = Path.GetFullPath(Path.Combine(resultsFullGroupDirectory, new('-', aAConfiguration.ResultAllInOneSubdirectoryLength))); + else + checkDirectory = Path.GetFullPath(Path.Combine(resultsFullGroupDirectory, i.ToString().PadLeft(aAConfiguration.ResultAllInOneSubdirectoryLength, '0'))); + } + else + { + directory = Path.Combine(resultsFullGroupDirectory, key, aAConfiguration.ResultAllInOne); + if (i == converted) + checkDirectory = Path.GetFullPath(Path.Combine(directory, new('-', aAConfiguration.ResultAllInOneSubdirectoryLength))); + else + checkDirectory = Path.GetFullPath(Path.Combine(directory, i.ToString().PadLeft(aAConfiguration.ResultAllInOneSubdirectoryLength, '0'))); + } + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + collection.Add(checkDirectory); + } + if (!string.IsNullOrEmpty(key)) + results.Add(key, collection.ToArray()); + else + results.Add(aAConfiguration.ResultAllInOne, collection.ToArray()); + } + } + return results; + } + +} \ No newline at end of file diff --git a/Shared/Models/Stateless/XResult.cs b/Shared/Models/Stateless/XResult.cs new file mode 100644 index 0000000..2074f54 --- /dev/null +++ b/Shared/Models/Stateless/XResult.cs @@ -0,0 +1,138 @@ +using View_by_Distance.Shared.Models.Properties; + +namespace View_by_Distance.Shared.Models.Stateless; + +internal abstract class XResult +{ + + internal static string GetResultsDateGroupDirectory(IAAConfiguration aAConfiguration, string description, string jsonGroup) + { + string result = Path.Combine(GetResultsDateGroupDirectory(aAConfiguration, description), jsonGroup); + if (!Directory.Exists(result)) + _ = Directory.CreateDirectory(result); + return result; + } + + internal static string GetResultsDateGroupDirectory(IAAConfiguration aAConfiguration, string description) + { + string result = Path.Combine(GetResultsGroupDirectory(aAConfiguration, description, create: true), aAConfiguration.DateGroup); + if (!Directory.Exists(result)) + _ = Directory.CreateDirectory(result); + return result; + } + + internal static string GetRelativePath(IAAConfiguration aAConfiguration, string path) + { + string result = Methods.IPath.GetRelativePath(path, aAConfiguration.RootDirectory.Length); + return result; + } + + private static void CheckContent(IAAConfiguration aAConfiguration, string dateGroupDirectory, string contentDescription, string result) + { + string checkDirectory; + checkDirectory = Path.Combine(dateGroupDirectory, aAConfiguration.ResultContent, aAConfiguration.ResultAllInOne); + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + string contentDirectory = new(result.Replace("<>", aAConfiguration.ResultContent)); + if (!Directory.Exists(contentDirectory)) + _ = Directory.CreateDirectory(contentDirectory); + checkDirectory = Path.Combine(dateGroupDirectory, string.Concat("() - ", contentDescription)); + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + } + + private static void CheckSingleton(IAAConfiguration aAConfiguration, string dateGroupDirectory, string singletonDescription, bool converted, string result) + { + string checkDirectory; + checkDirectory = Path.Combine(dateGroupDirectory, aAConfiguration.ResultSingleton, aAConfiguration.ResultAllInOne); + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + if (!converted) + { + string singletonDirectory = new(result.Replace("<>", aAConfiguration.ResultSingleton)); + if (!Directory.Exists(singletonDirectory)) + _ = Directory.CreateDirectory(singletonDirectory); + } + checkDirectory = Path.Combine(dateGroupDirectory, string.Concat("{} - ", singletonDescription)); + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + } + + private static void CheckCollection(IAAConfiguration aAConfiguration, string dateGroupDirectory, string collectionDescription, bool converted, string result) + { + string checkDirectory = Path.Combine(dateGroupDirectory, aAConfiguration.ResultCollection, aAConfiguration.ResultAllInOne); + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + if (!converted) + { + string collectionDirectory = new(result.Replace("<>", aAConfiguration.ResultCollection)); + if (!Directory.Exists(collectionDirectory)) + _ = Directory.CreateDirectory(collectionDirectory); + } + checkDirectory = Path.Combine(dateGroupDirectory, string.Concat("[] - ", collectionDescription)); + if (!Directory.Exists(checkDirectory)) + _ = Directory.CreateDirectory(checkDirectory); + } + + internal static string GetResultsFullGroupDirectory(IAAConfiguration aAConfiguration, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel) + { + string result = GetResultsDateGroupDirectory(aAConfiguration, description); + if (includeResizeGroup) + result = Path.Combine(result, outputResolution); + if (includeModel && includePredictorModel) + { + string modelName; + string predictorModelName; + if (aAConfiguration.ModelName is null) + modelName = Model.Hog.ToString(); + else + modelName = aAConfiguration.ModelName; + if (aAConfiguration.PredictorModelName is null) + predictorModelName = PredictorModel.Large.ToString(); + else + predictorModelName = aAConfiguration.PredictorModelName; + string dateGroupDirectory = string.Concat(outputResolution.Replace(" ", string.Empty), "-", modelName, "-", predictorModelName, "-", aAConfiguration.NumberOfJitters, "-", aAConfiguration.NumberOfTimesToUpsample); + result = Path.Combine(result, dateGroupDirectory); + } + else if (includeModel) + throw new Exception(); + else if (includePredictorModel) + throw new Exception(); + if (!Directory.Exists(result)) + _ = Directory.CreateDirectory(result); + return result; + } + + internal static List GetDirectoryInfoCollection(IAAConfiguration aAConfiguration, string sourceDirectory, string dateGroupDirectory, string contentDescription, string singletonDescription, string collectionDescription, bool converted) + { + List results = []; + string sourceDirectorySegment = GetRelativePath(aAConfiguration, sourceDirectory); + string result = string.Concat(Path.Combine(dateGroupDirectory, "<>"), sourceDirectorySegment); + if (!string.IsNullOrEmpty(contentDescription)) + CheckContent(aAConfiguration, dateGroupDirectory, contentDescription, result); + if (!string.IsNullOrEmpty(singletonDescription)) + CheckSingleton(aAConfiguration, dateGroupDirectory, singletonDescription, converted, result); + if (!string.IsNullOrEmpty(collectionDescription)) + CheckCollection(aAConfiguration, dateGroupDirectory, collectionDescription, converted, result); + results.Add(result); + return results; + } + + internal static string GetResultsGroupDirectory(IAAConfiguration aAConfiguration, string description, bool create) + { + string result = Path.Combine($"{aAConfiguration.RootDirectory}-Results", description.Replace('_', ')')); + if (create && !Directory.Exists(result)) + _ = Directory.CreateDirectory(result); + return result; + } + + internal static List GetDirectoryInfoCollection(IAAConfiguration aAConfiguration, string sourceDirectory, string description, string outputResolution, bool includeResizeGroup, bool includeModel, bool includePredictorModel, string contentDescription, string singletonDescription, string collectionDescription) + { + List results; + bool converted = false; + string dateGroupDirectory = GetResultsFullGroupDirectory(aAConfiguration, description, outputResolution, includeResizeGroup, includeModel, includePredictorModel); + results = GetDirectoryInfoCollection(aAConfiguration, sourceDirectory, dateGroupDirectory, contentDescription, singletonDescription, collectionDescription, converted); + return results; + } + +} \ No newline at end of file diff --git a/Shared/Models/WebPDirectory.cs b/Shared/Models/WebPDirectory.cs new file mode 100644 index 0000000..9e84df8 --- /dev/null +++ b/Shared/Models/WebPDirectory.cs @@ -0,0 +1,22 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace View_by_Distance.Shared.Models; + +public record WebPDirectory(string? ImageHeight, + string? ImageWidth) +{ + + public override string ToString() + { + string result = JsonSerializer.Serialize(this, WebPDirectorySourceGenerationContext.Default.WebPDirectory); + return result; + } + +} + +[JsonSourceGenerationOptions(WriteIndented = true)] +[JsonSerializable(typeof(WebPDirectory))] +public partial class WebPDirectorySourceGenerationContext : JsonSerializerContext +{ +} \ No newline at end of file