From a640063dd42d4a855a6cd253a5e5166c082702e8 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 10 Oct 2024 13:37:14 +1300 Subject: [PATCH 001/134] Add .gitattributes and .gitignore. --- .gitattributes | 63 +++++++++ .gitignore | 363 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 426 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..1ff0c423 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9491a2fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,363 @@ +## 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/ +[Oo]ut/ +[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/ + +# 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 \ No newline at end of file From 54e3029cbfe80db66fd3b87e37c6f6433edb4a8a Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 10 Oct 2024 13:37:15 +1300 Subject: [PATCH 002/134] Add project files. --- .editorconfig | 372 +++++++++++++++++++++++++ .globalconfig | 186 +++++++++++++ OpenMcdf3.Tests/BinaryReaderTests.cs | 15 + OpenMcdf3.Tests/OpenMcdf3.Tests.csproj | 28 ++ OpenMcdf3.sln | 37 +++ OpenMcdf3/AssemblyInfo.cs | 19 ++ OpenMcdf3/McdfBinaryReader.cs | 16 ++ OpenMcdf3/McdfBinaryWriter.cs | 21 ++ OpenMcdf3/OpenMcdf3.csproj | 11 + OpenMcdf3/Storage.cs | 8 + 10 files changed, 713 insertions(+) create mode 100644 .editorconfig create mode 100644 .globalconfig create mode 100644 OpenMcdf3.Tests/BinaryReaderTests.cs create mode 100644 OpenMcdf3.Tests/OpenMcdf3.Tests.csproj create mode 100644 OpenMcdf3.sln create mode 100644 OpenMcdf3/AssemblyInfo.cs create mode 100644 OpenMcdf3/McdfBinaryReader.cs create mode 100644 OpenMcdf3/McdfBinaryWriter.cs create mode 100644 OpenMcdf3/OpenMcdf3.csproj create mode 100644 OpenMcdf3/Storage.cs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..faaf28ca --- /dev/null +++ b/.editorconfig @@ -0,0 +1,372 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +#### Visual Studio Spell Checker #### + +[*] +spelling_exclusion_path = .\ignored-words.txt + +#### VS Spell Checker #### + +[*] +vsspell_section_id = 57c612f5006c4c25a138fae34ef5a504 +vsspell_determine_resource_file_language_from_name = true +vsspell_additional_dictionary_folders_57c612f5006c4c25a138fae34ef5a504 = .\dictionaries +vsspell_ignored_words_57c612f5006c4c25a138fae34ef5a504 = clear_inherited|File:ignored-words.txt|Readit + +[*.{cs,da,de,el,es,fr,fi,it,ko,nb-NO,nl-BE,pl,pt,pt-BR,ru,sk,sv}.resx] +vsspell_spell_check_as_you_type = false +vsspell_include_in_project_spell_check = false + +[*.{Designer.cs,ai,crproj,csproj,props,runsettings,targets,vcxproj,wixproj,wxi,wxl,wxs,xaml,xml}] +vsspell_spell_check_as_you_type = false +vsspell_include_in_project_spell_check = false + +[Service References/*/*] +vsspell_spell_check_as_you_type = false +vsspell_include_in_project_spell_check = false + +[{ignored-words.txt,ControlWords.cs,LanguageIdTests.cs,Phoneme.cs,UnicodeData.txt}] +vsspell_spell_check_as_you_type = false +vsspell_include_in_project_spell_check = false + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 +trim_trailing_whitespace = true + +# New line preferences +end_of_line = crlf +insert_final_newline =true + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:none +dotnet_style_qualification_for_field = false:none +dotnet_style_qualification_for_method = false:none +dotnet_style_qualification_for_property = false:none + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:none +dotnet_style_predefined_type_for_member_access = true:none + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:none +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:none + +# Expression-level preferences +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:none +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:suggestion + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false:suggestion +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:suggestion +csharp_style_expression_bodied_constructors = false:suggestion +csharp_style_expression_bodied_indexers = true:suggestion +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_expression_bodied_local_functions = true:suggestion +csharp_style_expression_bodied_methods = when_on_single_line:suggestion +csharp_style_expression_bodied_operators = when_on_single_line:suggestion +csharp_style_expression_bodied_properties = true:suggestion + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_prefer_static_local_function = true:suggestion +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:none + +# Code-block preferences +csharp_prefer_braces = when_multiline:none +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = file_scoped:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:none + +#### C# Formatting Rules #### + +# New line preferences +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 + +# Indentation preferences +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 + +# Space preferences +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 + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = warning +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.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.begins_with_i.capitalization = pascal_case + +csharp_style_prefer_method_group_conversion = true:none +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = true:none +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:none +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:none +csharp_style_prefer_parameter_null_checking = true:suggestion +csharp_style_prefer_pattern_matching = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_prefer_top_level_statements = false:none +csharp_style_prefer_primary_constructors = true:suggestion + +# CS1591: Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1591.severity = none + +# IDE0005: Using directive is unnecessary. +dotnet_diagnostic.IDE0005.severity = warning + +# SA1101: Prefix local calls with this +dotnet_diagnostic.SA1101.severity = none + +# SA1121: Use built-in type alias +dotnet_diagnostic.SA1121.severity = none + +# SA1124: Do not use regions +dotnet_diagnostic.SA1124.severity = suggestion + +# SA1202: Elements should be ordered by access +dotnet_diagnostic.SA1202.severity = none + +# SA1400: Access modifier should be declared +dotnet_diagnostic.SA1400.severity = none + +# SA1402: File may only contain a single type +dotnet_diagnostic.SA1402.severity = none + +# SA1404: Code analysis suppression should have justification +dotnet_diagnostic.SA1404.severity = none + +# SA1405: Debug.Assert should provide message text +dotnet_diagnostic.SA1405.severity = none + +# SA1407: Arithmetic expressions should declare precedence +dotnet_diagnostic.SA1407.severity = none + +# SA1503: Braces should not be omitted +dotnet_diagnostic.SA1503.severity = none + +# SA1513: Closing brace should be followed by blank line +dotnet_diagnostic.SA1513.severity = none + +# SA1600: Elements should be documented +dotnet_diagnostic.SA1600.severity = none + +# S1104: Fields should not have public accessibility +dotnet_diagnostic.S1104.severity = none + +# S2344: Enumeration type names should not have "Flags" or "Enum" suffixes +dotnet_diagnostic.S2344.severity = none +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_prefer_static_anonymous_function = true:suggestion + +[*.{config,crproj,csproj,props,pubxml,runsettings,settings,svcinfo,targets,vcxproj,wixproj,wxi,wxl,wxs,xml}] + +# Indentation and spacing +indent_size = 2 +indent_style = space +tab_width = 2 +trim_trailing_whitespace = true + +[*.xaml] + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 +trim_trailing_whitespace = true + +[*.{cs,vb}] +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +trim_trailing_whitespace = true +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:none +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_readonly_field = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:none +dotnet_style_allow_multiple_blank_lines_experimental = true:none +dotnet_style_allow_statement_immediately_after_block_experimental = true:none +dotnet_code_quality_unused_parameters = all:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:none +dotnet_style_qualification_for_field = false:none +dotnet_style_qualification_for_property = false:none +dotnet_style_qualification_for_method = false:none +dotnet_style_qualification_for_event = false:none +dotnet_style_prefer_collection_expression = when_types_exactly_match:suggestion + +[*.{hlsl}] + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 +trim_trailing_whitespace = true + +# Specific files +[{ort_inference.cc,ort_inference.h,pixel_classifier.h}] + +indent_size = 4 +indent_style = space +tab_width = 4 +trim_trailing_whitespace = true +cpp_indent_namespace_contents = false +cpp_indent_multi_line_relative_to = innermost_parenthesis +cpp_indent_within_parentheses = align_to_parenthesis +cpp_indent_preserve_within_parentheses = true +tab_width = 4 diff --git a/.globalconfig b/.globalconfig new file mode 100644 index 00000000..8be6656a --- /dev/null +++ b/.globalconfig @@ -0,0 +1,186 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +dotnet_analyzer_diagnostic.category-Design.severity = warning +dotnet_analyzer_diagnostic.category-Interoperability.severity = suggestion +dotnet_analyzer_diagnostic.category-Maintainability.severity = warning +dotnet_analyzer_diagnostic.category-Naming.severity = warning +dotnet_analyzer_diagnostic.category-Performance.severity = warning +dotnet_analyzer_diagnostic.category-Reliability.severity = warning +dotnet_analyzer_diagnostic.category-Style.severity = warning + +dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = none + +# IDE0010: Populate switch +dotnet_diagnostic.IDE0010.severity = none + +# IDE0011: Add braces to 'if' statement +dotnet_diagnostic.IDE0011.severity = suggestion + +# IDE0022: Use expression body for method +dotnet_diagnostic.IDE0022.severity = suggestion + +# IDE0025: Use expression body for property +dotnet_diagnostic.IDE0025.severity = suggestion + +# IDE0028: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0028) +dotnet_diagnostic.IDE0028.severity = none + +# IDE0028: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0028) +dotnet_diagnostic.IDE0030.severity = suggestion + +# IDE0040: Accessibility modifiers required (disabled on build) +dotnet_diagnostic.IDE0040.severity = none + +# IDE0045: Use conditional expression for assignment +dotnet_diagnostic.IDE0045.severity = suggestion + +# IDE0046: Use conditional expression for return +dotnet_diagnostic.IDE0046.severity = none + +# IDE0047: Parentheses can be removed +dotnet_diagnostic.IDE0047.severity = suggestion + +# IDE0052: Remove unread private member +dotnet_diagnostic.IDE0052.severity = warning + +# IDE0058: Expression value is never used +dotnet_diagnostic.IDE0058.severity = suggestion + +# IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0060.severity = suggestion + +# IDE0072: Populate switch +dotnet_diagnostic.IDE0072.severity = none + +# IDE0130: Namespace does not match folder structure +dotnet_diagnostic.IDE0130.severity = suggestion + +# IDE0251: Make member readonly +dotnet_diagnostic.IDE0251.severity = warning + +# IDE0270: Null check can be simplified +dotnet_diagnostic.IDE0270.severity = suggestion + +# IDE0290: Use primary constructor +dotnet_diagnostic.IDE0290.severity = suggestion + +# IDE0300: Collection initialization can be simplified +dotnet_diagnostic.IDE0300.severity = suggestion + +# IDE0300: Collection initialization can be simplified (false positive on empty arrays) +dotnet_diagnostic.IDE0301.severity = suggestion + +# IDE0303: Collection initialization can be simplified +dotnet_diagnostic.IDE0302.severity = suggestion + +# IDE0303: Collection initialization can be simplified +dotnet_diagnostic.IDE0303.severity = suggestion + +# IDE0305: Collection initialization can be simplified +dotnet_diagnostic.IDE0305.severity = suggestion + +#CA1008: Enums should have zero value +dotnet_diagnostic.CA1008.severity = suggestion + +# CA1028: Enum storage should be Int32 +dotnet_diagnostic.CA1028.severity = suggestion + +# CA1031: Do not catch general exception types +dotnet_diagnostic.CA1031.severity = suggestion + +# CA1051: Do not declare visible instance fields +dotnet_diagnostic.CA1051.severity = none + +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.severity = none + +# CA1304: Specify CultureInfo +dotnet_diagnostic.CA1304.severity = suggestion + +# CA1305: Specify IFormatProvider +dotnet_diagnostic.CA1305.severity = suggestion + +# CA1308: Normalize strings to uppercase +dotnet_diagnostic.CA1308.severity = suggestion + +# CA1309: Use ordinal string comparison +dotnet_diagnostic.CA1309.severity = suggestion + +# CA1416: Validate platform compatibility +dotnet_diagnostic.CA1416.severity = none + +# CA1848: Use the LoggerMessage delegates +dotnet_diagnostic.CA1848.severity = none + +# CA1711: Identifiers should not have incorrect suffix +dotnet_code_quality.CA1711.allowed_suffixes = Flag|Flags + +# CA1721: Property names should not match get methods +dotnet_diagnostic.CA1721.severity = suggestion + +# CA1724: Type names should not match namespaces +dotnet_diagnostic.CA1724.severity = suggestion + +# CA1814: Prefer jagged arrays over multidimensional +dotnet_diagnostic.CA1814.severity = suggestion + +# CA1826: Use property instead of Linq Enumerable method +dotnet_code_quality.CA1826.exclude_ordefault_methods = true + +# CA1863: Use 'CompositeFormat' +dotnet_diagnostic.CA1863.severity = none + +# CA2007: Do not directly await a Task +dotnet_diagnostic.CA2007.severity = suggestion + +# CA2008: Do not create tasks without passing a TaskScheduler +dotnet_diagnostic.CA2008.severity = suggestion + +# CA2109: Review visible event handlers +dotnet_diagnostic.CA2109.severity = none + +# CA2229: Implement serialization constructors +dotnet_diagnostic.CA2229.severity = none + +# CA2300: Do not use insecure deserializer BinaryFormatter +dotnet_diagnostic.CA2300.severity = none + +# CA2302: Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize +dotnet_diagnostic.CA2302.severity = suggestion + +# CA5392: Use DefaultDllImportSearchPaths attribute for P/Invokes +dotnet_diagnostic.CA5392.severity = suggestion + +# CA5393: Do not use unsafe DllImportSearchPath value +dotnet_diagnostic.CA5393.severity = none + +# CA5394: Do not use insecure randomness +dotnet_diagnostic.CA5394.severity = suggestion + +# CS1591: Missing XML comment for publicly visible type or member +dotnet_diagnostic.CS1591.severity = none + +# MSTEST0015: Test method should not be ignored +dotnet_diagnostic.MSTEST0015.severity = none + +# SYSLIB0011: Type or member is obsolete +dotnet_diagnostic.SYSLIB0011.severity = none + +# SA0001: XML comment analysis disabled +dotnet_diagnostic.SA0001.severity = none + +# SA1215: An instance readonly element is positioned beneath an instance non-readonly element of the same type +dotnet_diagnostic.SA1215.severity = warning + +# SA1515: Single-line comment should be preceded by blank line +dotnet_diagnostic.SA1515.severity = suggestion + +# SA1628: Documentation text should begin with a capital letter +dotnet_diagnostic.SA1628.severity = warning + +# SA1201: Elements must appear in the correct order +dotnet_diagnostic.SA1201.severity = suggestion + +# SYSLIB1045: Use GeneratedRegexAttribute to generate the regular expression implementation at compile time +dotnet_diagnostic.SYSLIB1045.severity = suggestion diff --git a/OpenMcdf3.Tests/BinaryReaderTests.cs b/OpenMcdf3.Tests/BinaryReaderTests.cs new file mode 100644 index 00000000..0a687fed --- /dev/null +++ b/OpenMcdf3.Tests/BinaryReaderTests.cs @@ -0,0 +1,15 @@ +namespace OpenMcdf3.Tests; + +[TestClass] +public class BinaryReaderTests +{ + [TestMethod] + public void Guid() + { + byte[] bytes = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 }; + using MemoryStream stream = new(bytes); + using McdfBinaryReader reader = new(stream); + Guid guid = reader.ReadGuid(); + Assert.AreEqual(new Guid(bytes), guid); + } +} \ No newline at end of file diff --git a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj new file mode 100644 index 00000000..adc3c029 --- /dev/null +++ b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj @@ -0,0 +1,28 @@ + + + + netstandard2.0 + 11.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/OpenMcdf3.sln b/OpenMcdf3.sln new file mode 100644 index 00000000..5999bf3c --- /dev/null +++ b/OpenMcdf3.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenMcdf3", "D:\OpenMcdf3\OpenMcdf3\OpenMcdf3.csproj", "{B90DDE7E-803A-4890-82F0-09DAD0FF66D8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenMcdf3.Tests", "D:\OpenMcdf3\OpenMcdf3.Tests\OpenMcdf3.Tests.csproj", "{96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{34030FA7-0A06-43D1-85DD-ADD39D502C3C}" + ProjectSection(SolutionItems) = preProject + D:\OpenMcdf3\.editorconfig = D:\OpenMcdf3\.editorconfig + D:\OpenMcdf3\.globalconfig = D:\OpenMcdf3\.globalconfig + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B90DDE7E-803A-4890-82F0-09DAD0FF66D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B90DDE7E-803A-4890-82F0-09DAD0FF66D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B90DDE7E-803A-4890-82F0-09DAD0FF66D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B90DDE7E-803A-4890-82F0-09DAD0FF66D8}.Release|Any CPU.Build.0 = Release|Any CPU + {96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {927A4DA2-8926-4AC2-A48C-976978B5F2AA} + EndGlobalSection +EndGlobal diff --git a/OpenMcdf3/AssemblyInfo.cs b/OpenMcdf3/AssemblyInfo.cs new file mode 100644 index 00000000..c16894d6 --- /dev/null +++ b/OpenMcdf3/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// In SDK-style projects such as this one, several assembly attributes that were historically +// defined in this file are now automatically added during build and populated with +// values defined in project properties. For details of which attributes are included +// and how to customise this process see: https://aka.ms/assembly-info-properties + + +// Setting ComVisible to false makes the types in this assembly not visible to COM +// components. If you need to access a type in this assembly from COM, set the ComVisible +// attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM. + +[assembly: Guid("a96ebb34-8c16-4c7e-b9f7-651ba754b722")] +[assembly: InternalsVisibleTo("OpenMcdf3.Tests")] diff --git a/OpenMcdf3/McdfBinaryReader.cs b/OpenMcdf3/McdfBinaryReader.cs new file mode 100644 index 00000000..87a54a07 --- /dev/null +++ b/OpenMcdf3/McdfBinaryReader.cs @@ -0,0 +1,16 @@ +namespace OpenMcdf3; + +internal class McdfBinaryReader : BinaryReader +{ + public McdfBinaryReader(Stream input) : base(input) + { + } + + public Guid ReadGuid() => new(ReadBytes(16)); + + public DateTime ReadFileTime() + { + long fileTime = ReadInt64(); + return DateTime.FromFileTimeUtc(fileTime); + } +} diff --git a/OpenMcdf3/McdfBinaryWriter.cs b/OpenMcdf3/McdfBinaryWriter.cs new file mode 100644 index 00000000..89743606 --- /dev/null +++ b/OpenMcdf3/McdfBinaryWriter.cs @@ -0,0 +1,21 @@ +namespace OpenMcdf3; + +internal class McdfBinaryWriter : BinaryWriter +{ + public McdfBinaryWriter(Stream input) : base(input) + { + } + + public void Write(Guid value) + { + // TODO: Avoid heap allocation + byte[] bytes = value.ToByteArray(); + Write(bytes, 0, bytes.Length); + } + + public void Write(DateTime value) + { + long fileTime = value.ToFileTimeUtc(); + Write(fileTime); + } +} diff --git a/OpenMcdf3/OpenMcdf3.csproj b/OpenMcdf3/OpenMcdf3.csproj new file mode 100644 index 00000000..1ace06ea --- /dev/null +++ b/OpenMcdf3/OpenMcdf3.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + 11.0 + enable + enable + true + + + diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs new file mode 100644 index 00000000..6ace1d1a --- /dev/null +++ b/OpenMcdf3/Storage.cs @@ -0,0 +1,8 @@ +namespace OpenMcdf3; + +public class Storage +{ + public static void Open(string fileName) + { + } +} From 1e7ff7f08101e6b40ae13e5ed033fd5254273973 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 10 Oct 2024 15:40:40 +1300 Subject: [PATCH 003/134] Read/write headers --- OpenMcdf3.Tests/BinaryReaderTests.cs | 4 +- OpenMcdf3.Tests/HeaderTests.cs | 13 +++++ OpenMcdf3.Tests/OpenMcdf3.Tests.csproj | 18 ++++-- OpenMcdf3.Tests/_Test.ppt | Bin 0 -> 174592 bytes OpenMcdf3/Header.cs | 77 +++++++++++++++++++++++++ OpenMcdf3/McdfBinaryReader.cs | 39 +++++++++++++ OpenMcdf3/McdfBinaryWriter.cs | 28 ++++++++- OpenMcdf3/RootStorage.cs | 38 ++++++++++++ OpenMcdf3/Sector.cs | 6 ++ OpenMcdf3/SectorType.cs | 10 ++++ OpenMcdf3/Storage.cs | 3 - 11 files changed, 224 insertions(+), 12 deletions(-) create mode 100644 OpenMcdf3.Tests/HeaderTests.cs create mode 100644 OpenMcdf3.Tests/_Test.ppt create mode 100644 OpenMcdf3/Header.cs create mode 100644 OpenMcdf3/RootStorage.cs create mode 100644 OpenMcdf3/Sector.cs create mode 100644 OpenMcdf3/SectorType.cs diff --git a/OpenMcdf3.Tests/BinaryReaderTests.cs b/OpenMcdf3.Tests/BinaryReaderTests.cs index 0a687fed..34f06412 100644 --- a/OpenMcdf3.Tests/BinaryReaderTests.cs +++ b/OpenMcdf3.Tests/BinaryReaderTests.cs @@ -1,7 +1,7 @@ namespace OpenMcdf3.Tests; [TestClass] -public class BinaryReaderTests +public sealed class BinaryReaderTests { [TestMethod] public void Guid() @@ -12,4 +12,4 @@ public void Guid() Guid guid = reader.ReadGuid(); Assert.AreEqual(new Guid(bytes), guid); } -} \ No newline at end of file +} diff --git a/OpenMcdf3.Tests/HeaderTests.cs b/OpenMcdf3.Tests/HeaderTests.cs new file mode 100644 index 00000000..3968ee36 --- /dev/null +++ b/OpenMcdf3.Tests/HeaderTests.cs @@ -0,0 +1,13 @@ +namespace OpenMcdf3.Tests; + +[TestClass] +public sealed class HeaderTests +{ + [TestMethod] + public void Header() + { + using FileStream stream = File.OpenRead("_Test.ppt"); + using McdfBinaryReader reader = new(stream); + Header header = reader.ReadHeader(); + } +} diff --git a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj index adc3c029..664cdf8c 100644 --- a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj +++ b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj @@ -1,20 +1,20 @@ - netstandard2.0 - 11.0 + net8.0 + Exe + 11.0 enable enable false true + true - - - - + + @@ -25,4 +25,10 @@ + + + PreserveNewest + + + diff --git a/OpenMcdf3.Tests/_Test.ppt b/OpenMcdf3.Tests/_Test.ppt new file mode 100644 index 0000000000000000000000000000000000000000..4b5a7692c487ed3675640407315cdb4240fdb68a GIT binary patch literal 174592 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 sTd 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 literal 0 HcmV?d00001 diff --git a/OpenMcdf3/Header.cs b/OpenMcdf3/Header.cs new file mode 100644 index 00000000..0ab79724 --- /dev/null +++ b/OpenMcdf3/Header.cs @@ -0,0 +1,77 @@ +namespace OpenMcdf3; + + +internal sealed class Header +{ + internal const int DifatLength = 109; + internal const ushort LittleEndian = 0xFFFE; + internal const ushort SectorShiftV3 = 0x0009; + internal const ushort SectorShiftV4 = 0x000C; + internal const short MiniSectorShift = 6; + internal const uint MiniStreamCutoffSize = 4096; + + internal static readonly byte[] Signature = new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; + + internal static readonly byte[] Unused = new byte[6]; + private ushort majorVersion; + private ushort sectorShift = SectorShiftV3; + + public Guid CLSID { get; set; } + + public ushort MinorVersion { get; set; } + + public ushort MajorVersion + { + get => majorVersion; set + { + if (value is not 3 and not 4) + throw new FormatException($"Unsupported major version: {value}. Only 3 and 4 are supported"); + majorVersion = value; + } + } + + public ushort SectorShift + { + get => sectorShift; set + { + if (MajorVersion == 3 && value != SectorShiftV3) + throw new FormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV3:X4} is supported for Major Version 3"); + if (MajorVersion == 4 && value != SectorShiftV4) + throw new FormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV4:X4} is supported for Major Version 4"); + + sectorShift = value; + } + } + + public uint DirectorySectorCount { get; set; } + + public uint FatSectorCount { get; set; } + + public uint FirstDirectorySectorID { get; set; } = (uint)SectorType.EndOfChain; + + public uint TransactionSignature { get; set; } + + ///

+ /// This integer field contains the starting sector number for the mini FAT + /// + public uint FirstMiniFatSectorID { get; set; } = (uint)SectorType.EndOfChain; + + public uint MiniFatSectorCount { get; set; } + + public uint FirstDifatSectorID { get; set; } = (uint)SectorType.EndOfChain; + + public uint DifatSectorCount { get; set; } + + public uint[] Difat { get; } = new uint[DifatLength]; + + public int SectorSize => 1 << SectorShift; + + public Header(Version version = Version.V3) + { + MajorVersion = (ushort)version; + for (int i = 0; i < Difat.Length; i++) + { + Difat[i] = (uint)SectorType.Free; + } + } +} diff --git a/OpenMcdf3/McdfBinaryReader.cs b/OpenMcdf3/McdfBinaryReader.cs index 87a54a07..2f98116c 100644 --- a/OpenMcdf3/McdfBinaryReader.cs +++ b/OpenMcdf3/McdfBinaryReader.cs @@ -13,4 +13,43 @@ public DateTime ReadFileTime() long fileTime = ReadInt64(); return DateTime.FromFileTimeUtc(fileTime); } + + private void ReadBytes(byte[] buffer) => Read(buffer, 0, buffer.Length); + + public Header ReadHeader() + { + Header header = new(); + Read(buffer, 0, Header.Signature.Length); + if (!buffer.Take(Header.Signature.Length).SequenceEqual(Header.Signature)) + throw new FormatException("Invalid signature"); + header.CLSID = ReadGuid(); + header.MinorVersion = ReadUInt16(); + header.MajorVersion = ReadUInt16(); + ushort byteOrder = ReadUInt16(); + if (byteOrder != Header.LittleEndian) + throw new FormatException($"Unsupported byte order: {byteOrder:X4}. Only little-endian is supported ({Header.LittleEndian:X4})"); + header.SectorShift = ReadUInt16(); + ushort miniSectorShift = ReadUInt16(); + if (miniSectorShift != Header.MiniSectorShift) + throw new FormatException($"Unsupported sector shift {miniSectorShift}. Only {Header.MiniSectorShift} is supported"); + this.FillBuffer(6); + header.DirectorySectorCount = ReadUInt32(); + header.FatSectorCount = ReadUInt32(); + header.FirstDirectorySectorID = ReadUInt32(); + this.FillBuffer(4); + uint miniStreamCutoffSize = ReadUInt32(); + if (miniStreamCutoffSize != Header.MiniStreamCutoffSize) + throw new FormatException("Mini stream cutoff size must be 4096 byte"); + header.FirstMiniFatSectorID = ReadUInt32(); + header.MiniFatSectorCount = ReadUInt32(); + header.FirstDifatSectorID = ReadUInt32(); + header.DifatSectorCount = ReadUInt32(); + + for (int i = 0; i < Header.DifatLength; i++) + { + header.Difat[i] = ReadUInt32(); + } + + return header; + } } diff --git a/OpenMcdf3/McdfBinaryWriter.cs b/OpenMcdf3/McdfBinaryWriter.cs index 89743606..e9befdf0 100644 --- a/OpenMcdf3/McdfBinaryWriter.cs +++ b/OpenMcdf3/McdfBinaryWriter.cs @@ -1,4 +1,7 @@ -namespace OpenMcdf3; +using System.IO; +using System.Security.Claims; + +namespace OpenMcdf3; internal class McdfBinaryWriter : BinaryWriter { @@ -18,4 +21,27 @@ public void Write(DateTime value) long fileTime = value.ToFileTimeUtc(); Write(fileTime); } + + private void WriteBytes(byte[] buffer) => Write(buffer, 0, buffer.Length); + + public void Write(Header header) + { + Write(Header.Signature); + Write(header.CLSID); + Write(header.MinorVersion); + Write(header.MajorVersion); + Write(Header.LittleEndian); + Write(header.SectorShift); + Write(Header.MiniSectorShift); + WriteBytes(Header.Unused); + Write(header.DirectorySectorCount); + Write(header.FatSectorCount); + Write(header.FirstDirectorySectorID); + Write((uint)0); + Write(Header.MiniStreamCutoffSize); + Write(header.FirstMiniFatSectorID); + Write(header.MiniFatSectorCount); + Write(header.FirstDifatSectorID); + Write(header.DifatSectorCount); + } } diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs new file mode 100644 index 00000000..c1c1b2b6 --- /dev/null +++ b/OpenMcdf3/RootStorage.cs @@ -0,0 +1,38 @@ +namespace OpenMcdf3; + +public enum Version : ushort +{ + V3 = 3, + V4 = 4 +} + +public sealed class RootStorage : Storage +{ + readonly Header header; + readonly McdfBinaryReader reader; + readonly McdfBinaryWriter? writer; + + RootStorage(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer = null) + { + this.header = header; + this.reader = reader; + this.writer = writer; + } + + public static RootStorage Create(string fileName, Version version = Version.V3) + { + FileStream stream = File.Create(fileName); + Header header = new(version); + McdfBinaryReader reader = new(stream); + McdfBinaryWriter writer = new(stream); + return new RootStorage(header, reader, writer); + } + + public static RootStorage Open(string fileName, FileMode mode) + { + FileStream stream = File.Open(fileName, mode); + McdfBinaryReader reader = new(stream); + Header header = reader.ReadHeader(); + return new RootStorage(header, reader); + } +} diff --git a/OpenMcdf3/Sector.cs b/OpenMcdf3/Sector.cs new file mode 100644 index 00000000..18b27bb2 --- /dev/null +++ b/OpenMcdf3/Sector.cs @@ -0,0 +1,6 @@ +namespace OpenMcdf3; + +internal class Sector +{ + const int MiniSectorSize = 64; +} diff --git a/OpenMcdf3/SectorType.cs b/OpenMcdf3/SectorType.cs new file mode 100644 index 00000000..f38feef9 --- /dev/null +++ b/OpenMcdf3/SectorType.cs @@ -0,0 +1,10 @@ +namespace OpenMcdf3; + +internal enum SectorType : uint +{ + Maximum = 0xFFFFFFFA, + Difat = 0xFFFFFFFC, // Specifies a DIFAT sector in the FAT. + Fat = 0xFFFFFFFD, + EndOfChain = 0xFFFFFFFE, + Free = 0xFFFFFFFF, +} diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index 6ace1d1a..8bc4cfe4 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -2,7 +2,4 @@ public class Storage { - public static void Open(string fileName) - { - } } From 43e329272847c803b71ad95edef135977d626cee Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 10 Oct 2024 17:00:46 +1300 Subject: [PATCH 004/134] Read/write directory entries --- OpenMcdf3.Tests/BinaryReaderTests.cs | 20 ++++++- OpenMcdf3.Tests/HeaderTests.cs | 13 ----- OpenMcdf3/DirectoryEntry.cs | 82 ++++++++++++++++++++++++++++ OpenMcdf3/McdfBinaryReader.cs | 45 ++++++++++++++- OpenMcdf3/McdfBinaryWriter.cs | 23 +++++++- OpenMcdf3/Storage.cs | 10 ++++ 6 files changed, 176 insertions(+), 17 deletions(-) delete mode 100644 OpenMcdf3.Tests/HeaderTests.cs create mode 100644 OpenMcdf3/DirectoryEntry.cs diff --git a/OpenMcdf3.Tests/BinaryReaderTests.cs b/OpenMcdf3.Tests/BinaryReaderTests.cs index 34f06412..4e570624 100644 --- a/OpenMcdf3.Tests/BinaryReaderTests.cs +++ b/OpenMcdf3.Tests/BinaryReaderTests.cs @@ -4,7 +4,7 @@ namespace OpenMcdf3.Tests; public sealed class BinaryReaderTests { [TestMethod] - public void Guid() + public void ReadGuid() { byte[] bytes = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 }; using MemoryStream stream = new(bytes); @@ -12,4 +12,22 @@ public void Guid() Guid guid = reader.ReadGuid(); Assert.AreEqual(new Guid(bytes), guid); } + + [TestMethod] + public void ReadFileTime() + { + byte[] bytes = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + using MemoryStream stream = new(bytes); + using McdfBinaryReader reader = new(stream); + DateTime actual = reader.ReadFileTime(); + Assert.AreEqual(DirectoryEntry.ZeroFileTime, actual); + } + + [TestMethod] + public void ReadHeader() + { + using FileStream stream = File.OpenRead("_Test.ppt"); + using McdfBinaryReader reader = new(stream); + Header header = reader.ReadHeader(); + } } diff --git a/OpenMcdf3.Tests/HeaderTests.cs b/OpenMcdf3.Tests/HeaderTests.cs deleted file mode 100644 index 3968ee36..00000000 --- a/OpenMcdf3.Tests/HeaderTests.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace OpenMcdf3.Tests; - -[TestClass] -public sealed class HeaderTests -{ - [TestMethod] - public void Header() - { - using FileStream stream = File.OpenRead("_Test.ppt"); - using McdfBinaryReader reader = new(stream); - Header header = reader.ReadHeader(); - } -} diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs new file mode 100644 index 00000000..45ae5a19 --- /dev/null +++ b/OpenMcdf3/DirectoryEntry.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices.ComTypes; +using System.Text; + +namespace OpenMcdf3; + +enum Color +{ + Red = 0, + Black = 1 +} + +internal sealed class DirectoryEntry +{ + internal const int Length = 128; + internal const int NameFieldLength = 64; + internal const uint MaxV3StreamLength = 0x80000000; + + internal static readonly DateTime ZeroFileTime = DateTime.FromFileTimeUtc(0); + + string name = string.Empty; + DateTime creationTime; + DateTime modifiedTime; + + public string Name + { + get => name; + set + { + if (value.Contains(@"\") || value.Contains(@"/") || value.Contains(@":") || value.Contains(@"!")) + throw new ArgumentException("Name cannot contain any of the following characters: '\\', '/', ':','!'"); + + if (Encoding.Unicode.GetByteCount(value) + 2 > NameFieldLength) + throw new ArgumentException($"{value} exceeds maximum encoded length of {NameFieldLength} bytes"); + + name = value; + } + } + + public StorageType Type { get; set; } = StorageType.Invalid; + + public Color Color { get; set; } + + public uint LeftSiblingID { get; set; } + + public uint RightSiblingID { get; set; } + + public uint ChildID { get; set; } + + public Guid CLSID { get; set; } + + public uint StateBits { get; set; } + + public DateTime CreationTime + { + get => creationTime; + set + { + if (Type is StorageType.Stream or StorageType.Root && value != ZeroFileTime) + throw new ArgumentException("Creation time must be zero for streams and root"); + + creationTime = value; + } + } + + public DateTime ModifiedTime + { + get => modifiedTime; + set + { + if (Type is StorageType.Stream && value != ZeroFileTime) + throw new ArgumentException("Modified time must be zero for streams"); + + modifiedTime = value; + } + } + + public uint StartSectorLocation { get; set; } + + public long StreamLength { get; set; } +} diff --git a/OpenMcdf3/McdfBinaryReader.cs b/OpenMcdf3/McdfBinaryReader.cs index 2f98116c..fe0c4c4d 100644 --- a/OpenMcdf3/McdfBinaryReader.cs +++ b/OpenMcdf3/McdfBinaryReader.cs @@ -1,7 +1,11 @@ -namespace OpenMcdf3; +using System.Text; + +namespace OpenMcdf3; internal class McdfBinaryReader : BinaryReader { + readonly byte[] buffer = new byte[DirectoryEntry.NameFieldLength]; + public McdfBinaryReader(Stream input) : base(input) { } @@ -52,4 +56,43 @@ public Header ReadHeader() return header; } + + public StorageType ReadStorageType() => (StorageType)ReadByte(); + + public Color ReadColor() => (Color)ReadByte(); + + public DirectoryEntry ReadDirectoryEntry(Version version) + { + if (version is not Version.V3 and not Version.V4) + throw new ArgumentException($"Unsupported version: {version}"); + + DirectoryEntry entry = new(); + Read(buffer, 0, DirectoryEntry.NameFieldLength); + int nameLength = Math.Max(0, ReadUInt16() - 2); + entry.Name = Encoding.Unicode.GetString(buffer, 0, nameLength); + entry.Type = ReadStorageType(); + entry.Color = ReadColor(); + entry.LeftSiblingID = ReadUInt32(); + entry.RightSiblingID = ReadUInt32(); + entry.ChildID = ReadUInt32(); + entry.CLSID = ReadGuid(); + entry.StateBits = ReadUInt32(); + entry.CreationTime = ReadFileTime(); + entry.ModifiedTime = ReadFileTime(); + entry.StartSectorLocation = ReadUInt32(); + + if (version == Version.V3) + { + entry.StreamLength = ReadUInt32(); + if (entry.StreamLength > DirectoryEntry.MaxV3StreamLength) + throw new FormatException($"Stream length {entry.StreamLength} exceeds maximum value {DirectoryEntry.MaxV3StreamLength}"); + ReadUInt32(); // Skip unused 4 bytes + } + else if (version == Version.V4) + { + entry.StreamLength = ReadInt64(); + } + + return entry; + } } diff --git a/OpenMcdf3/McdfBinaryWriter.cs b/OpenMcdf3/McdfBinaryWriter.cs index e9befdf0..9f38a07c 100644 --- a/OpenMcdf3/McdfBinaryWriter.cs +++ b/OpenMcdf3/McdfBinaryWriter.cs @@ -1,10 +1,11 @@ -using System.IO; -using System.Security.Claims; +using System.Text; namespace OpenMcdf3; internal class McdfBinaryWriter : BinaryWriter { + readonly byte[] buffer = new byte[DirectoryEntry.NameFieldLength]; + public McdfBinaryWriter(Stream input) : base(input) { } @@ -44,4 +45,22 @@ public void Write(Header header) Write(header.FirstDifatSectorID); Write(header.DifatSectorCount); } + + public void Write(DirectoryEntry entry) + { + int nameLength = Encoding.Unicode.GetBytes(entry.Name, 0, entry.Name.Length, buffer, 0); + Write(nameLength); + Write(buffer, 0, DirectoryEntry.NameFieldLength); + Write((byte)entry.Type); + Write((byte)entry.Color); + Write(entry.LeftSiblingID); + Write(entry.RightSiblingID); + Write(entry.ChildID); + Write(entry.CLSID); + Write(entry.StateBits); + Write(entry.CreationTime); + Write(entry.ModifiedTime); + Write(entry.StartSectorLocation); + Write(entry.StreamLength); + } } diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index 8bc4cfe4..a4aa9bcc 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -1,5 +1,15 @@ namespace OpenMcdf3; +public enum StorageType +{ + Invalid = 0, + Storage = 1, + Stream = 2, + Lockbytes = 3, + Property = 4, + Root = 5 +} + public class Storage { } From 95a0c773e5009acd35dbf77202278faf1666deab Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 10 Oct 2024 23:11:36 +1300 Subject: [PATCH 005/134] Enumerate directory entries --- OpenMcdf3.Tests/EntryInfoTests.cs | 13 ++++ OpenMcdf3/EntryInfo.cs | 6 ++ OpenMcdf3/McdfBinaryReader.cs | 2 + OpenMcdf3/RootStorage.cs | 103 +++++++++++++++++++++++++++--- OpenMcdf3/Sector.cs | 16 ++++- 5 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 OpenMcdf3.Tests/EntryInfoTests.cs create mode 100644 OpenMcdf3/EntryInfo.cs diff --git a/OpenMcdf3.Tests/EntryInfoTests.cs b/OpenMcdf3.Tests/EntryInfoTests.cs new file mode 100644 index 00000000..33e3c850 --- /dev/null +++ b/OpenMcdf3.Tests/EntryInfoTests.cs @@ -0,0 +1,13 @@ +namespace OpenMcdf3.Tests; + +[TestClass] +public sealed class EntryInfoTests +{ + [TestMethod] + [DataRow("_Test.ppt", 5)] + public void EnumerateEntryInfos(string fileName, int count) + { + using var rootStorage = RootStorage.Open(fileName, FileMode.Open); + Assert.AreEqual(count, rootStorage.EnumerateEntries().Count()); + } +} diff --git a/OpenMcdf3/EntryInfo.cs b/OpenMcdf3/EntryInfo.cs new file mode 100644 index 00000000..40dd2066 --- /dev/null +++ b/OpenMcdf3/EntryInfo.cs @@ -0,0 +1,6 @@ +namespace OpenMcdf3; + +public class EntryInfo +{ + public string Name { get; internal set; } +} diff --git a/OpenMcdf3/McdfBinaryReader.cs b/OpenMcdf3/McdfBinaryReader.cs index fe0c4c4d..91ecf4f1 100644 --- a/OpenMcdf3/McdfBinaryReader.cs +++ b/OpenMcdf3/McdfBinaryReader.cs @@ -95,4 +95,6 @@ public DirectoryEntry ReadDirectoryEntry(Version version) return entry; } + + public void Seek(long offset) => BaseStream.Seek(offset, SeekOrigin.Begin); } diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index c1c1b2b6..a3111efe 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -1,4 +1,6 @@ -namespace OpenMcdf3; +using System.Diagnostics; + +namespace OpenMcdf3; public enum Version : ushort { @@ -6,18 +8,12 @@ public enum Version : ushort V4 = 4 } -public sealed class RootStorage : Storage +public sealed class RootStorage : Storage, IDisposable { readonly Header header; readonly McdfBinaryReader reader; readonly McdfBinaryWriter? writer; - - RootStorage(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer = null) - { - this.header = header; - this.reader = reader; - this.writer = writer; - } + bool disposed; public static RootStorage Create(string fileName, Version version = Version.V3) { @@ -35,4 +31,93 @@ public static RootStorage Open(string fileName, FileMode mode) Header header = reader.ReadHeader(); return new RootStorage(header, reader); } + + RootStorage(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer = null) + { + this.header = header; + this.reader = reader; + this.writer = writer; + } + + public void Dispose() + { + if (disposed) + return; + + writer?.Dispose(); + reader.Dispose(); + disposed = true; + } + + IEnumerable EnumerateDifatSectorChain() + { + uint nextId = header.FirstDifatSectorID; + while (nextId != (uint)SectorType.EndOfChain) + { + Sector s = new(nextId, header.SectorSize); + yield return s; + long nextIdOffset = s.EndOffset - sizeof(uint); + reader.Seek(nextIdOffset); + nextId = reader.ReadUInt32(); + } + } + + IEnumerable EnumerateFatSectors() + { + for (uint i = 0; i < header.FatSectorCount && i < Header.DifatLength; i++) + { + uint nextId = header.Difat[i]; + Sector s = new(nextId, header.SectorSize); + yield return s; + } + + foreach (Sector difatSector in EnumerateDifatSectorChain()) + { + reader.Seek(difatSector.StartOffset); + int difatElementCount = header.SectorSize / sizeof(uint) - 1; + for (int i = 0; i < difatElementCount; i++) + { + uint nextId = reader.ReadUInt32(); + Sector s = new(nextId, header.SectorSize); + yield return s; + } + } + } + + uint GetNextFatSectorId(uint id) + { + int elementLength = header.SectorSize / sizeof(uint); + int sectorId = (int)Math.DivRem(id, elementLength, out long sectorOffset); + Sector sector = EnumerateFatSectors().ElementAt(sectorId); + long position = sector.StartOffset + sectorOffset * sizeof(uint); + reader.Seek(position); + uint nextId = reader.ReadUInt32(); + return nextId; + } + + IEnumerable EnumerateFatSectorChain(uint id) + { + while (id != (uint)SectorType.EndOfChain) + { + Sector sector = new(id, header.SectorSize); + yield return sector; + id = GetNextFatSectorId(id); + } + } + + public IEnumerable EnumerateEntries() + { + foreach (Sector sector in EnumerateFatSectorChain(header.FirstDirectorySectorID)) + { + reader.Seek(sector.StartOffset); + + int entryCount = header.SectorSize / DirectoryEntry.Length; + for (int i = 0; i < entryCount; i++) + { + DirectoryEntry entry = reader.ReadDirectoryEntry((Version)header.MajorVersion); + if (entry.Type is not StorageType.Invalid) + yield return new EntryInfo { Name = entry.Name }; + } + } + } } diff --git a/OpenMcdf3/Sector.cs b/OpenMcdf3/Sector.cs index 18b27bb2..75700be3 100644 --- a/OpenMcdf3/Sector.cs +++ b/OpenMcdf3/Sector.cs @@ -1,6 +1,20 @@ namespace OpenMcdf3; -internal class Sector +internal struct Sector { const int MiniSectorSize = 64; + + public Sector(uint index, long length) + { + Index = index; + Length = length; + } + + public uint Index { get; } + + public long Length { get; } + + public readonly long StartOffset => (Index + 1) * Length; + + public readonly long EndOffset => (Index + 2) * Length; } From 264fe6c3fcfde511b50a36d377b18f05c92a335c Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Fri, 11 Oct 2024 11:11:39 +1300 Subject: [PATCH 006/134] Open and read streams --- OpenMcdf3.Tests/CfbStreamTests.cs | 19 +++++++ OpenMcdf3.Tests/EntryInfoTests.cs | 1 + OpenMcdf3.Tests/OpenMcdf3.Tests.csproj | 3 + OpenMcdf3.Tests/test.cfb | Bin 0 -> 1058304 bytes OpenMcdf3/CfbStream.cs | 73 +++++++++++++++++++++++++ OpenMcdf3/RootStorage.cs | 32 ++++++++--- 6 files changed, 121 insertions(+), 7 deletions(-) create mode 100644 OpenMcdf3.Tests/CfbStreamTests.cs create mode 100644 OpenMcdf3.Tests/test.cfb create mode 100644 OpenMcdf3/CfbStream.cs diff --git a/OpenMcdf3.Tests/CfbStreamTests.cs b/OpenMcdf3.Tests/CfbStreamTests.cs new file mode 100644 index 00000000..464a8a4a --- /dev/null +++ b/OpenMcdf3.Tests/CfbStreamTests.cs @@ -0,0 +1,19 @@ +namespace OpenMcdf3.Tests; + +[TestClass] +public sealed class CfbStreamTests +{ + [TestMethod] + [DataRow("_Test.ppt", "Current User", 62)] + [DataRow("test.cfb", "MyStream0", 1048576)] + public void CfbStreamTest(string fileName, string streamName, long length) + { + using var rootStorage = RootStorage.Open(fileName, FileMode.Open); + using var stream = rootStorage.OpenStream(streamName); + Assert.AreEqual(length, stream.Length); + + using MemoryStream memoryStream = new(); + stream.CopyTo(memoryStream); + Assert.AreEqual(length, memoryStream.Length); + } +} diff --git a/OpenMcdf3.Tests/EntryInfoTests.cs b/OpenMcdf3.Tests/EntryInfoTests.cs index 33e3c850..0db90a0f 100644 --- a/OpenMcdf3.Tests/EntryInfoTests.cs +++ b/OpenMcdf3.Tests/EntryInfoTests.cs @@ -5,6 +5,7 @@ public sealed class EntryInfoTests { [TestMethod] [DataRow("_Test.ppt", 5)] + [DataRow("test.cfb", 2)] public void EnumerateEntryInfos(string fileName, int count) { using var rootStorage = RootStorage.Open(fileName, FileMode.Open); diff --git a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj index 664cdf8c..009e0b55 100644 --- a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj +++ b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj @@ -26,6 +26,9 @@ + + PreserveNewest + PreserveNewest diff --git a/OpenMcdf3.Tests/test.cfb b/OpenMcdf3.Tests/test.cfb new file mode 100644 index 0000000000000000000000000000000000000000..861c57cbcc2b12a208cdab4262854f362298ca98 GIT binary patch literal 1058304 zcmeI&RSa|2p`g*fVPN8SCu<|Hn4>fPn}6{>P952K%@BN9_8DL;SBhMi|ie zAAkJo)_?2=H1?+&s0OY7}8m@+~5voJl2oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ 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 R_vZhvAOAml true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override long Length => length; + + public override long Position + { + get => position; + set => position = value; + } + + public override void Flush() + { + //rootStorage.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + int sectorSkipCount = (int)Math.DivRem(position, sectorLength, out long sectorOffset); + int maxCount = (int)Math.Min(Math.Max(length - position, 0), int.MaxValue); + int realCount = Math.Min(count, maxCount); + int readCount = 0; + int remaining = realCount; + foreach (Sector sector in rootStorage.EnumerateFatSectorChain(directoryEntry.StartSectorLocation).Skip(sectorSkipCount)) + { + long readLength = Math.Min(remaining, sector.Length - sectorOffset); + rootStorage.Reader.Seek(sector.StartOffset + sectorOffset); + int read = rootStorage.Reader.Read(buffer, offset, (int)readLength); + if (read == 0) + return 0; + position += read; + readCount += read; + if (readCount >= realCount) + return readCount; + } + + return readCount; + } + + public override long Seek(long offset, SeekOrigin origin) + { + position = offset; + return position; + } + + public override void SetLength(long value) + { + length = value; + } + + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); +} diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index a3111efe..fc77dd5c 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -15,6 +15,8 @@ public sealed class RootStorage : Storage, IDisposable readonly McdfBinaryWriter? writer; bool disposed; + internal McdfBinaryReader Reader => reader; + public static RootStorage Create(string fileName, Version version = Version.V3) { FileStream stream = File.Create(fileName); @@ -27,9 +29,15 @@ public static RootStorage Create(string fileName, Version version = Version.V3) public static RootStorage Open(string fileName, FileMode mode) { FileStream stream = File.Open(fileName, mode); + return Open(stream); + } + + public static RootStorage Open(Stream stream) + { McdfBinaryReader reader = new(stream); + McdfBinaryWriter? writer = stream.CanWrite ? new(stream) : null; Header header = reader.ReadHeader(); - return new RootStorage(header, reader); + return new RootStorage(header, reader, writer); } RootStorage(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer = null) @@ -95,17 +103,18 @@ uint GetNextFatSectorId(uint id) return nextId; } - IEnumerable EnumerateFatSectorChain(uint id) + internal IEnumerable EnumerateFatSectorChain(uint startId) { - while (id != (uint)SectorType.EndOfChain) + uint nextId = startId; + while (nextId is not (uint)SectorType.EndOfChain and not (uint)SectorType.Free) { - Sector sector = new(id, header.SectorSize); + Sector sector = new(nextId, header.SectorSize); yield return sector; - id = GetNextFatSectorId(id); + nextId = GetNextFatSectorId(nextId); } } - public IEnumerable EnumerateEntries() + IEnumerable EnumerateDirectoryEntries() { foreach (Sector sector in EnumerateFatSectorChain(header.FirstDirectorySectorID)) { @@ -116,8 +125,17 @@ public IEnumerable EnumerateEntries() { DirectoryEntry entry = reader.ReadDirectoryEntry((Version)header.MajorVersion); if (entry.Type is not StorageType.Invalid) - yield return new EntryInfo { Name = entry.Name }; + yield return entry; } } } + + public IEnumerable EnumerateEntries() => EnumerateDirectoryEntries().Select(e => new EntryInfo { Name = e.Name }); + + public CfbStream OpenStream(string name) + { + DirectoryEntry? entry = EnumerateDirectoryEntries() + .FirstOrDefault(entry => entry.Name == name) ?? throw new FileNotFoundException("Stream not found", name); + return new CfbStream(this, header.SectorSize, entry); + } } From e9baa90fd40f162d5311f30a81d37fac084d7075 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Fri, 11 Oct 2024 11:55:33 +1300 Subject: [PATCH 007/134] Add benchmark --- OpenMcdf3.Benchmarks/InMemory.cs | 97 +++++++++++++++++++ .../OpenMcdf3.Benchmarks.csproj | 19 ++++ OpenMcdf3.Benchmarks/Program.cs | 11 +++ OpenMcdf3.sln | 14 ++- OpenMcdf3/McdfBinaryReader.cs | 3 +- OpenMcdf3/McdfBinaryWriter.cs | 3 +- OpenMcdf3/RootStorage.cs | 1 + 7 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 OpenMcdf3.Benchmarks/InMemory.cs create mode 100644 OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj create mode 100644 OpenMcdf3.Benchmarks/Program.cs diff --git a/OpenMcdf3.Benchmarks/InMemory.cs b/OpenMcdf3.Benchmarks/InMemory.cs new file mode 100644 index 00000000..c33a02f1 --- /dev/null +++ b/OpenMcdf3.Benchmarks/InMemory.cs @@ -0,0 +1,97 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; +using OpenMcdf; + +namespace OpenMcdf3.Benchmark; + +[SimpleJob] +[CsvExporter] +[HtmlExporter] +[MarkdownExporter] +//[DryCoreJob] // I always forget this attribute, so please leave it commented out +[MemoryDiagnoser] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] +public class InMemory : IDisposable +{ + private const int Kb = 1024; + private const int Mb = Kb * Kb; + private const string storageName = "MyStorage"; + private const string streamName = "MyStream"; + + private byte[] _readBuffer; + + private MemoryStream _stream; + + [Params(Kb / 2, Kb, 4 * Kb, 128 * Kb, 256 * Kb, 512 * Kb, Kb * Kb)] + public int BufferSize { get; set; } + + [Params(Mb /*, 8 * Mb, 64 * Mb, 128 * Mb*/)] + public int TotalStreamSize { get; set; } + + public void Dispose() + { + _stream?.Dispose(); + } + + [GlobalSetup] + public void GlobalSetup() + { + _stream = new MemoryStream(); + //_stream = File.Create("D:\\test.cfb"); + _readBuffer = new byte[BufferSize]; + CreateFile(1); + } + + [GlobalCleanup] + public void GlobalCleanup() + { + _stream.Dispose(); + _stream = null; + _readBuffer = null; + } + + [Benchmark] + public void Test() + { + // + _stream.Seek(0L, SeekOrigin.Begin); + // + using var compoundFile = RootStorage.Open(_stream); + using CfbStream cfStream = compoundFile.OpenStream(streamName + 0); + long streamSize = cfStream.Length; + long position = 0L; + while (true) + { + if (position >= streamSize) + break; + int read = cfStream.Read(_readBuffer, 0, _readBuffer.Length); + position += read; + if (read <= 0) break; + } + + //compoundFile.Close(); + } + + private void CreateFile(int streamCount) + { + var iterationCount = TotalStreamSize / BufferSize; + + var buffer = new byte[BufferSize]; + Array.Fill(buffer, byte.MaxValue); + const CFSConfiguration flags = CFSConfiguration.Default | CFSConfiguration.LeaveOpen; + using (var compoundFile = new CompoundFile(CFSVersion.Ver_3, flags)) + { + //var st = compoundFile.RootStorage.AddStorage(storageName); + var st = compoundFile.RootStorage; + for (var streamId = 0; streamId < streamCount; ++streamId) + { + var sm = st.AddStream(streamName + streamId); + + for (var iteration = 0; iteration < iterationCount; ++iteration) sm.Append(buffer); + } + + compoundFile.Save(_stream); + compoundFile.Close(); + } + } +} diff --git a/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj b/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj new file mode 100644 index 00000000..94e38e90 --- /dev/null +++ b/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/OpenMcdf3.Benchmarks/Program.cs b/OpenMcdf3.Benchmarks/Program.cs new file mode 100644 index 00000000..f00ccb3e --- /dev/null +++ b/OpenMcdf3.Benchmarks/Program.cs @@ -0,0 +1,11 @@ +using BenchmarkDotNet.Running; + +namespace OpenMcdf3.Benchmarks; + +internal class Program +{ + private static void Main(string[] args) + { + BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); + } +} diff --git a/OpenMcdf3.sln b/OpenMcdf3.sln index 5999bf3c..4f19d749 100644 --- a/OpenMcdf3.sln +++ b/OpenMcdf3.sln @@ -3,16 +3,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.11.35327.3 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenMcdf3", "D:\OpenMcdf3\OpenMcdf3\OpenMcdf3.csproj", "{B90DDE7E-803A-4890-82F0-09DAD0FF66D8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3", "D:\OpenMcdf3\OpenMcdf3\OpenMcdf3.csproj", "{B90DDE7E-803A-4890-82F0-09DAD0FF66D8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenMcdf3.Tests", "D:\OpenMcdf3\OpenMcdf3.Tests\OpenMcdf3.Tests.csproj", "{96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3.Tests", "D:\OpenMcdf3\OpenMcdf3.Tests\OpenMcdf3.Tests.csproj", "{96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{34030FA7-0A06-43D1-85DD-ADD39D502C3C}" ProjectSection(SolutionItems) = preProject - D:\OpenMcdf3\.editorconfig = D:\OpenMcdf3\.editorconfig - D:\OpenMcdf3\.globalconfig = D:\OpenMcdf3\.globalconfig + .editorconfig = .editorconfig + .globalconfig = .globalconfig EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenMcdf3.Benchmarks", "OpenMcdf3.Benchmarks\OpenMcdf3.Benchmarks.csproj", "{44C718AD-F7FE-4733-80A8-636E5E7E63F3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}.Debug|Any CPU.Build.0 = Debug|Any CPU {96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}.Release|Any CPU.ActiveCfg = Release|Any CPU {96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}.Release|Any CPU.Build.0 = Release|Any CPU + {44C718AD-F7FE-4733-80A8-636E5E7E63F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44C718AD-F7FE-4733-80A8-636E5E7E63F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44C718AD-F7FE-4733-80A8-636E5E7E63F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44C718AD-F7FE-4733-80A8-636E5E7E63F3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/OpenMcdf3/McdfBinaryReader.cs b/OpenMcdf3/McdfBinaryReader.cs index 91ecf4f1..441445bf 100644 --- a/OpenMcdf3/McdfBinaryReader.cs +++ b/OpenMcdf3/McdfBinaryReader.cs @@ -6,7 +6,8 @@ internal class McdfBinaryReader : BinaryReader { readonly byte[] buffer = new byte[DirectoryEntry.NameFieldLength]; - public McdfBinaryReader(Stream input) : base(input) + public McdfBinaryReader(Stream input) + : base(input, Encoding.Unicode, true) { } diff --git a/OpenMcdf3/McdfBinaryWriter.cs b/OpenMcdf3/McdfBinaryWriter.cs index 9f38a07c..067036f1 100644 --- a/OpenMcdf3/McdfBinaryWriter.cs +++ b/OpenMcdf3/McdfBinaryWriter.cs @@ -6,7 +6,8 @@ internal class McdfBinaryWriter : BinaryWriter { readonly byte[] buffer = new byte[DirectoryEntry.NameFieldLength]; - public McdfBinaryWriter(Stream input) : base(input) + public McdfBinaryWriter(Stream input) + : base(input, Encoding.Unicode, true) { } diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index fc77dd5c..345d6476 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -54,6 +54,7 @@ public void Dispose() writer?.Dispose(); reader.Dispose(); + reader.BaseStream.Dispose(); disposed = true; } From aa8fd9e2b4d68e73d4a223804a2fc91c0615a414 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Fri, 11 Oct 2024 12:51:08 +1300 Subject: [PATCH 008/134] Cache sectors --- OpenMcdf3/RootStorage.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index 345d6476..06db0198 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -54,7 +54,7 @@ public void Dispose() writer?.Dispose(); reader.Dispose(); - reader.BaseStream.Dispose(); + //reader.BaseStream.Dispose(); disposed = true; } @@ -93,11 +93,14 @@ IEnumerable EnumerateFatSectors() } } + List fatSectors; + uint GetNextFatSectorId(uint id) { int elementLength = header.SectorSize / sizeof(uint); int sectorId = (int)Math.DivRem(id, elementLength, out long sectorOffset); - Sector sector = EnumerateFatSectors().ElementAt(sectorId); + fatSectors ??= EnumerateFatSectors().ToList(); + Sector sector = fatSectors[sectorId]; long position = sector.StartOffset + sectorOffset * sizeof(uint); reader.Seek(position); uint nextId = reader.ReadUInt32(); From c58349a62b6e84a43f9f8e8714a6a530f634e0f5 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Sun, 13 Oct 2024 14:47:04 +1300 Subject: [PATCH 009/134] Add IOContext --- OpenMcdf3/CfbStream.cs | 16 ++++--- OpenMcdf3/ChainIterator.cs | 57 +++++++++++++++++++++++ OpenMcdf3/IOContext.cs | 68 ++++++++++++++++++++++++++++ OpenMcdf3/RootStorage.cs | 93 ++++++-------------------------------- 4 files changed, 148 insertions(+), 86 deletions(-) create mode 100644 OpenMcdf3/ChainIterator.cs create mode 100644 OpenMcdf3/IOContext.cs diff --git a/OpenMcdf3/CfbStream.cs b/OpenMcdf3/CfbStream.cs index ff19beba..03437e9b 100644 --- a/OpenMcdf3/CfbStream.cs +++ b/OpenMcdf3/CfbStream.cs @@ -2,18 +2,20 @@ public class CfbStream : Stream { - readonly RootStorage rootStorage; + readonly IOContext ioContext; readonly long sectorLength; - private readonly DirectoryEntry directoryEntry; + readonly DirectoryEntry directoryEntry; + readonly List sectorChain; long length; long position; - internal CfbStream(RootStorage rootStorage, long sectorLength, DirectoryEntry directoryEntry) + internal CfbStream(IOContext ioContext, long sectorLength, DirectoryEntry directoryEntry) { - this.rootStorage = rootStorage; + this.ioContext = ioContext; this.sectorLength = sectorLength; this.directoryEntry = directoryEntry; length = directoryEntry.StreamLength; + sectorChain = ioContext.EnumerateFatSectorChain(directoryEntry.StartSectorLocation).ToList(); } public override bool CanRead => true; @@ -42,11 +44,11 @@ public override int Read(byte[] buffer, int offset, int count) int realCount = Math.Min(count, maxCount); int readCount = 0; int remaining = realCount; - foreach (Sector sector in rootStorage.EnumerateFatSectorChain(directoryEntry.StartSectorLocation).Skip(sectorSkipCount)) + foreach (Sector sector in sectorChain.Skip(sectorSkipCount)) { long readLength = Math.Min(remaining, sector.Length - sectorOffset); - rootStorage.Reader.Seek(sector.StartOffset + sectorOffset); - int read = rootStorage.Reader.Read(buffer, offset, (int)readLength); + ioContext.Reader.Seek(sector.StartOffset + sectorOffset); + int read = ioContext.Reader.Read(buffer, offset, (int)readLength); if (read == 0) return 0; position += read; diff --git a/OpenMcdf3/ChainIterator.cs b/OpenMcdf3/ChainIterator.cs new file mode 100644 index 00000000..9ef5ec4d --- /dev/null +++ b/OpenMcdf3/ChainIterator.cs @@ -0,0 +1,57 @@ +using System.Collections; + +namespace OpenMcdf3; + +internal class ChainIterator : IEnumerator +{ + readonly IOContext ioContext; + private uint nextId; + + public ChainIterator(IOContext ioContext) + { + this.ioContext = ioContext; + this.nextId = ioContext.Header.FirstDifatSectorID; + Current = default; + } + + public Sector Current { get; private set; } + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + if (nextId == (uint)SectorType.EndOfChain) + return false; + + Current = new Sector(nextId, ioContext.Header.SectorSize); + long nextIdOffset = Current.EndOffset - sizeof(uint); + ioContext.Reader.Seek(nextIdOffset); + nextId = ioContext.Reader.ReadUInt32(); + return true; + } + + public void Reset() + { + nextId = ioContext.Header.FirstDifatSectorID; + Current = default; + } + + public void Dispose() + { + // No resources to dispose + } +} + +internal class ChainEnumerable : IEnumerable +{ + private readonly IOContext ioContext; + + public ChainEnumerable(IOContext ioContext) + { + this.ioContext = ioContext; + } + + public IEnumerator GetEnumerator() => (IEnumerator)(new ChainIterator(ioContext)); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs new file mode 100644 index 00000000..6f1b0204 --- /dev/null +++ b/OpenMcdf3/IOContext.cs @@ -0,0 +1,68 @@ +namespace OpenMcdf3; + +internal sealed class IOContext : IDisposable +{ + public Header Header { get; } + public McdfBinaryReader Reader { get; } + public McdfBinaryWriter? Writer { get; } + List fatSectors; + + public IOContext(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer = null) + { + Header = header; + Reader = reader; + Writer = writer; + } + + public void Dispose() + { + Reader.Dispose(); + Writer?.Dispose(); + } + + IEnumerable EnumerateFatSectors() + { + for (uint i = 0; i < Header.FatSectorCount && i < Header.DifatLength; i++) + { + uint nextId = Header.Difat[i]; + Sector s = new(nextId, Header.SectorSize); + yield return s; + } + + ChainEnumerable iterator = new(this); + foreach (Sector difatSector in iterator) + { + Reader.Seek(difatSector.StartOffset); + int difatElementCount = Header.SectorSize / sizeof(uint) - 1; + for (int i = 0; i < difatElementCount; i++) + { + uint nextId = Reader.ReadUInt32(); + Sector s = new(nextId, Header.SectorSize); + yield return s; + } + } + } + + uint GetNextFatSectorId(uint id) + { + int elementLength = Header.SectorSize / sizeof(uint); + int sectorId = (int)Math.DivRem(id, elementLength, out long sectorOffset); + fatSectors ??= EnumerateFatSectors().ToList(); + Sector sector = fatSectors[sectorId]; + long position = sector.StartOffset + sectorOffset * sizeof(uint); + Reader.Seek(position); + uint nextId = Reader.ReadUInt32(); + return nextId; + } + + internal IEnumerable EnumerateFatSectorChain(uint startId) + { + uint nextId = startId; + while (nextId is not (uint)SectorType.EndOfChain and not (uint)SectorType.Free) + { + Sector sector = new(nextId, Header.SectorSize); + yield return sector; + nextId = GetNextFatSectorId(nextId); + } + } +} diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index 06db0198..5e2dbefe 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -10,20 +10,17 @@ public enum Version : ushort public sealed class RootStorage : Storage, IDisposable { - readonly Header header; - readonly McdfBinaryReader reader; - readonly McdfBinaryWriter? writer; + readonly IOContext ioContext; bool disposed; - internal McdfBinaryReader Reader => reader; - public static RootStorage Create(string fileName, Version version = Version.V3) { FileStream stream = File.Create(fileName); Header header = new(version); McdfBinaryReader reader = new(stream); McdfBinaryWriter writer = new(stream); - return new RootStorage(header, reader, writer); + IOContext ioContext = new(header, reader, writer); + return new RootStorage(ioContext); } public static RootStorage Open(string fileName, FileMode mode) @@ -37,14 +34,13 @@ public static RootStorage Open(Stream stream) McdfBinaryReader reader = new(stream); McdfBinaryWriter? writer = stream.CanWrite ? new(stream) : null; Header header = reader.ReadHeader(); - return new RootStorage(header, reader, writer); + IOContext ioContext = new(header, reader, writer); + return new RootStorage(ioContext); } - RootStorage(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer = null) + RootStorage(IOContext ioContext) { - this.header = header; - this.reader = reader; - this.writer = writer; + this.ioContext = ioContext; } public void Dispose() @@ -52,82 +48,21 @@ public void Dispose() if (disposed) return; - writer?.Dispose(); - reader.Dispose(); - //reader.BaseStream.Dispose(); + ioContext.Writer?.Dispose(); + ioContext.Reader.Dispose(); disposed = true; } - IEnumerable EnumerateDifatSectorChain() - { - uint nextId = header.FirstDifatSectorID; - while (nextId != (uint)SectorType.EndOfChain) - { - Sector s = new(nextId, header.SectorSize); - yield return s; - long nextIdOffset = s.EndOffset - sizeof(uint); - reader.Seek(nextIdOffset); - nextId = reader.ReadUInt32(); - } - } - - IEnumerable EnumerateFatSectors() - { - for (uint i = 0; i < header.FatSectorCount && i < Header.DifatLength; i++) - { - uint nextId = header.Difat[i]; - Sector s = new(nextId, header.SectorSize); - yield return s; - } - - foreach (Sector difatSector in EnumerateDifatSectorChain()) - { - reader.Seek(difatSector.StartOffset); - int difatElementCount = header.SectorSize / sizeof(uint) - 1; - for (int i = 0; i < difatElementCount; i++) - { - uint nextId = reader.ReadUInt32(); - Sector s = new(nextId, header.SectorSize); - yield return s; - } - } - } - - List fatSectors; - - uint GetNextFatSectorId(uint id) - { - int elementLength = header.SectorSize / sizeof(uint); - int sectorId = (int)Math.DivRem(id, elementLength, out long sectorOffset); - fatSectors ??= EnumerateFatSectors().ToList(); - Sector sector = fatSectors[sectorId]; - long position = sector.StartOffset + sectorOffset * sizeof(uint); - reader.Seek(position); - uint nextId = reader.ReadUInt32(); - return nextId; - } - - internal IEnumerable EnumerateFatSectorChain(uint startId) - { - uint nextId = startId; - while (nextId is not (uint)SectorType.EndOfChain and not (uint)SectorType.Free) - { - Sector sector = new(nextId, header.SectorSize); - yield return sector; - nextId = GetNextFatSectorId(nextId); - } - } - IEnumerable EnumerateDirectoryEntries() { - foreach (Sector sector in EnumerateFatSectorChain(header.FirstDirectorySectorID)) + foreach (Sector sector in ioContext.EnumerateFatSectorChain(ioContext.Header.FirstDirectorySectorID)) { - reader.Seek(sector.StartOffset); + ioContext.Reader.Seek(sector.StartOffset); - int entryCount = header.SectorSize / DirectoryEntry.Length; + int entryCount = ioContext.Header.SectorSize / DirectoryEntry.Length; for (int i = 0; i < entryCount; i++) { - DirectoryEntry entry = reader.ReadDirectoryEntry((Version)header.MajorVersion); + DirectoryEntry entry = ioContext.Reader.ReadDirectoryEntry((Version)ioContext.Header.MajorVersion); if (entry.Type is not StorageType.Invalid) yield return entry; } @@ -140,6 +75,6 @@ public CfbStream OpenStream(string name) { DirectoryEntry? entry = EnumerateDirectoryEntries() .FirstOrDefault(entry => entry.Name == name) ?? throw new FileNotFoundException("Stream not found", name); - return new CfbStream(this, header.SectorSize, entry); + return new CfbStream(ioContext, ioContext.Header.SectorSize, entry); } } From 2e69028c41dcd11e81177132e558b486520cb735 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Sun, 13 Oct 2024 15:20:32 +1300 Subject: [PATCH 010/134] Add enumerators --- OpenMcdf3.Tests/CfbStreamTests.cs | 4 +- OpenMcdf3.Tests/EntryInfoTests.cs | 5 +- OpenMcdf3/CfbStream.cs | 20 ++++-- OpenMcdf3/ChainIterator.cs | 57 ---------------- OpenMcdf3/EntryInfo.cs | 2 + OpenMcdf3/FatSectorChainEnumerator.cs | 61 +++++++++++++++++ OpenMcdf3/FatSectorEnumerator.cs | 94 +++++++++++++++++++++++++++ OpenMcdf3/Header.cs | 8 +-- OpenMcdf3/IOContext.cs | 47 -------------- OpenMcdf3/RootStorage.cs | 11 +++- OpenMcdf3/Sector.cs | 16 ++--- OpenMcdf3/SectorType.cs | 12 ++-- 12 files changed, 200 insertions(+), 137 deletions(-) delete mode 100644 OpenMcdf3/ChainIterator.cs create mode 100644 OpenMcdf3/FatSectorChainEnumerator.cs create mode 100644 OpenMcdf3/FatSectorEnumerator.cs diff --git a/OpenMcdf3.Tests/CfbStreamTests.cs b/OpenMcdf3.Tests/CfbStreamTests.cs index 464a8a4a..037f99c9 100644 --- a/OpenMcdf3.Tests/CfbStreamTests.cs +++ b/OpenMcdf3.Tests/CfbStreamTests.cs @@ -8,8 +8,8 @@ public sealed class CfbStreamTests [DataRow("test.cfb", "MyStream0", 1048576)] public void CfbStreamTest(string fileName, string streamName, long length) { - using var rootStorage = RootStorage.Open(fileName, FileMode.Open); - using var stream = rootStorage.OpenStream(streamName); + using var rootStorage = RootStorage.OpenRead(fileName); + using CfbStream stream = rootStorage.OpenStream(streamName); Assert.AreEqual(length, stream.Length); using MemoryStream memoryStream = new(); diff --git a/OpenMcdf3.Tests/EntryInfoTests.cs b/OpenMcdf3.Tests/EntryInfoTests.cs index 0db90a0f..3af05877 100644 --- a/OpenMcdf3.Tests/EntryInfoTests.cs +++ b/OpenMcdf3.Tests/EntryInfoTests.cs @@ -8,7 +8,8 @@ public sealed class EntryInfoTests [DataRow("test.cfb", 2)] public void EnumerateEntryInfos(string fileName, int count) { - using var rootStorage = RootStorage.Open(fileName, FileMode.Open); - Assert.AreEqual(count, rootStorage.EnumerateEntries().Count()); + using var rootStorage = RootStorage.OpenRead(fileName); + IEnumerable entries = rootStorage.EnumerateEntries(); + Assert.AreEqual(count, entries.Count()); } } diff --git a/OpenMcdf3/CfbStream.cs b/OpenMcdf3/CfbStream.cs index 03437e9b..d060d714 100644 --- a/OpenMcdf3/CfbStream.cs +++ b/OpenMcdf3/CfbStream.cs @@ -4,8 +4,7 @@ public class CfbStream : Stream { readonly IOContext ioContext; readonly long sectorLength; - readonly DirectoryEntry directoryEntry; - readonly List sectorChain; + readonly FatSectorChainEnumerator chain; long length; long position; @@ -13,11 +12,13 @@ internal CfbStream(IOContext ioContext, long sectorLength, DirectoryEntry direct { this.ioContext = ioContext; this.sectorLength = sectorLength; - this.directoryEntry = directoryEntry; + DirectoryEntry = directoryEntry; length = directoryEntry.StreamLength; - sectorChain = ioContext.EnumerateFatSectorChain(directoryEntry.StartSectorLocation).ToList(); + chain = new(ioContext, directoryEntry.StartSectorLocation); } + internal DirectoryEntry DirectoryEntry { get; private set; } + public override bool CanRead => true; public override bool CanSeek => true; @@ -39,13 +40,20 @@ public override void Flush() public override int Read(byte[] buffer, int offset, int count) { - int sectorSkipCount = (int)Math.DivRem(position, sectorLength, out long sectorOffset); + int sectorIndex = (int)Math.DivRem(position, sectorLength, out long sectorOffset); + while (sectorIndex > 0 && (chain.Index == SectorType.EndOfChain || chain.Index < sectorIndex)) + { + if (!chain.MoveNext()) + return 0; + } + int maxCount = (int)Math.Min(Math.Max(length - position, 0), int.MaxValue); int realCount = Math.Min(count, maxCount); int readCount = 0; int remaining = realCount; - foreach (Sector sector in sectorChain.Skip(sectorSkipCount)) + while (chain.MoveNext()) { + Sector sector = chain.Current; long readLength = Math.Min(remaining, sector.Length - sectorOffset); ioContext.Reader.Seek(sector.StartOffset + sectorOffset); int read = ioContext.Reader.Read(buffer, offset, (int)readLength); diff --git a/OpenMcdf3/ChainIterator.cs b/OpenMcdf3/ChainIterator.cs deleted file mode 100644 index 9ef5ec4d..00000000 --- a/OpenMcdf3/ChainIterator.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Collections; - -namespace OpenMcdf3; - -internal class ChainIterator : IEnumerator -{ - readonly IOContext ioContext; - private uint nextId; - - public ChainIterator(IOContext ioContext) - { - this.ioContext = ioContext; - this.nextId = ioContext.Header.FirstDifatSectorID; - Current = default; - } - - public Sector Current { get; private set; } - - object IEnumerator.Current => Current; - - public bool MoveNext() - { - if (nextId == (uint)SectorType.EndOfChain) - return false; - - Current = new Sector(nextId, ioContext.Header.SectorSize); - long nextIdOffset = Current.EndOffset - sizeof(uint); - ioContext.Reader.Seek(nextIdOffset); - nextId = ioContext.Reader.ReadUInt32(); - return true; - } - - public void Reset() - { - nextId = ioContext.Header.FirstDifatSectorID; - Current = default; - } - - public void Dispose() - { - // No resources to dispose - } -} - -internal class ChainEnumerable : IEnumerable -{ - private readonly IOContext ioContext; - - public ChainEnumerable(IOContext ioContext) - { - this.ioContext = ioContext; - } - - public IEnumerator GetEnumerator() => (IEnumerator)(new ChainIterator(ioContext)); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} diff --git a/OpenMcdf3/EntryInfo.cs b/OpenMcdf3/EntryInfo.cs index 40dd2066..8904164a 100644 --- a/OpenMcdf3/EntryInfo.cs +++ b/OpenMcdf3/EntryInfo.cs @@ -3,4 +3,6 @@ public class EntryInfo { public string Name { get; internal set; } + + public override string ToString() => Name; } diff --git a/OpenMcdf3/FatSectorChainEnumerator.cs b/OpenMcdf3/FatSectorChainEnumerator.cs new file mode 100644 index 00000000..a7ea20ba --- /dev/null +++ b/OpenMcdf3/FatSectorChainEnumerator.cs @@ -0,0 +1,61 @@ +using System.Collections; + +namespace OpenMcdf3; + +internal sealed class FatSectorChainEnumerator : IEnumerator +{ + private readonly FatSectorEnumerator fatEnumerator; + private readonly IOContext ioContext; + private readonly uint startId; + private uint nextId; + private Sector current; + + public FatSectorChainEnumerator(IOContext ioContext, uint startId) + { + fatEnumerator = new(ioContext); + this.ioContext = ioContext; + Index = SectorType.EndOfChain; + this.startId = startId; + this.nextId = SectorType.Free; + this.current = new Sector(0, 0); // Initialize with a default value + } + + public uint Index { get; private set; } + + public Sector Current => current; + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + if (nextId is SectorType.Free) + { + Index = 0; + nextId = startId; + } + + if (nextId is SectorType.EndOfChain) + { + Index = SectorType.EndOfChain; + return false; + } + + Index++; + current = new Sector(nextId, ioContext.Header.SectorSize); + nextId = fatEnumerator.GetNextFatSectorId(nextId); + return true; + } + + public void Reset() + { + Index = SectorType.EndOfChain; + nextId = SectorType.Free; + current = new Sector(0, 0); // Reset to default value + fatEnumerator.Reset(); + } + + public void Dispose() + { + // No resources to dispose + } +} diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs new file mode 100644 index 00000000..c34bb474 --- /dev/null +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -0,0 +1,94 @@ +using System.Collections; +using System.Diagnostics.SymbolStore; + +namespace OpenMcdf3; + +internal sealed class FatSectorEnumerator : IEnumerator +{ + private readonly IOContext ioContext; + private uint index = SectorType.EndOfChain; + uint nextDifatSectorId = SectorType.EndOfChain; + uint difatSectorElementIndex = SectorType.EndOfChain; + + public FatSectorEnumerator(IOContext ioContext) + { + this.ioContext = ioContext; + this.index = SectorType.EndOfChain; + this.nextDifatSectorId = ioContext.Header.FirstDifatSectorID; + Current = default; + } + + public Sector Current { get; private set; } + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + if (index == SectorType.EndOfChain) + { + index = 0; + } + + if (index < ioContext.Header.FatSectorCount && index < Header.DifatLength) + { + uint nextId = ioContext.Header.Difat[index]; + Current = new Sector(nextId, ioContext.Header.SectorSize); + index++; + return true; + } + + if (nextDifatSectorId == SectorType.EndOfChain) + return false; + + int difatElementCount = ioContext.Header.SectorSize / sizeof(uint) - 1; + Sector difatSector = new(nextDifatSectorId, ioContext.Header.SectorSize); + if (difatSectorElementIndex == difatElementCount) + { + long nextIdOffset = difatSector.EndOffset - sizeof(uint); + ioContext.Reader.Seek(nextIdOffset); + nextDifatSectorId = ioContext.Reader.ReadUInt32(); + difatSectorElementIndex = 0; + } + + if (difatSectorElementIndex < difatElementCount) + { + long position = difatSector.StartOffset + difatSectorElementIndex * sizeof(uint); + ioContext.Reader.Seek(position); + uint nextId = ioContext.Reader.ReadUInt32(); + Current = new Sector(nextId, ioContext.Header.SectorSize); + difatSectorElementIndex++; + index++; + return true; + } + + return false; + } + + public void Reset() + { + index = SectorType.EndOfChain; + difatSectorElementIndex = SectorType.EndOfChain; + Current = new Sector(0, 0); // Reset to default value + } + + public uint GetNextFatSectorId(uint id) + { + int elementLength = ioContext.Header.SectorSize / sizeof(uint); + int sectorId = (int)Math.DivRem(id, elementLength, out long sectorOffset); + while (index == SectorType.EndOfChain || index - 1 < sectorId) + { + if (!MoveNext()) + return SectorType.EndOfChain; + } + + Sector sector = Current; + long position = sector.StartOffset + sectorOffset * sizeof(uint); + ioContext.Reader.Seek(position); + uint nextId = ioContext.Reader.ReadUInt32(); + return nextId; + } + + public void Dispose() + { + } +} diff --git a/OpenMcdf3/Header.cs b/OpenMcdf3/Header.cs index 0ab79724..95e19d94 100644 --- a/OpenMcdf3/Header.cs +++ b/OpenMcdf3/Header.cs @@ -47,18 +47,18 @@ public ushort SectorShift public uint FatSectorCount { get; set; } - public uint FirstDirectorySectorID { get; set; } = (uint)SectorType.EndOfChain; + public uint FirstDirectorySectorID { get; set; } = SectorType.EndOfChain; public uint TransactionSignature { get; set; } /// /// This integer field contains the starting sector number for the mini FAT /// - public uint FirstMiniFatSectorID { get; set; } = (uint)SectorType.EndOfChain; + public uint FirstMiniFatSectorID { get; set; } = SectorType.EndOfChain; public uint MiniFatSectorCount { get; set; } - public uint FirstDifatSectorID { get; set; } = (uint)SectorType.EndOfChain; + public uint FirstDifatSectorID { get; set; } = SectorType.EndOfChain; public uint DifatSectorCount { get; set; } @@ -71,7 +71,7 @@ public Header(Version version = Version.V3) MajorVersion = (ushort)version; for (int i = 0; i < Difat.Length; i++) { - Difat[i] = (uint)SectorType.Free; + Difat[i] = SectorType.Free; } } } diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index 6f1b0204..abe22fbe 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -5,7 +5,6 @@ internal sealed class IOContext : IDisposable public Header Header { get; } public McdfBinaryReader Reader { get; } public McdfBinaryWriter? Writer { get; } - List fatSectors; public IOContext(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer = null) { @@ -19,50 +18,4 @@ public void Dispose() Reader.Dispose(); Writer?.Dispose(); } - - IEnumerable EnumerateFatSectors() - { - for (uint i = 0; i < Header.FatSectorCount && i < Header.DifatLength; i++) - { - uint nextId = Header.Difat[i]; - Sector s = new(nextId, Header.SectorSize); - yield return s; - } - - ChainEnumerable iterator = new(this); - foreach (Sector difatSector in iterator) - { - Reader.Seek(difatSector.StartOffset); - int difatElementCount = Header.SectorSize / sizeof(uint) - 1; - for (int i = 0; i < difatElementCount; i++) - { - uint nextId = Reader.ReadUInt32(); - Sector s = new(nextId, Header.SectorSize); - yield return s; - } - } - } - - uint GetNextFatSectorId(uint id) - { - int elementLength = Header.SectorSize / sizeof(uint); - int sectorId = (int)Math.DivRem(id, elementLength, out long sectorOffset); - fatSectors ??= EnumerateFatSectors().ToList(); - Sector sector = fatSectors[sectorId]; - long position = sector.StartOffset + sectorOffset * sizeof(uint); - Reader.Seek(position); - uint nextId = Reader.ReadUInt32(); - return nextId; - } - - internal IEnumerable EnumerateFatSectorChain(uint startId) - { - uint nextId = startId; - while (nextId is not (uint)SectorType.EndOfChain and not (uint)SectorType.Free) - { - Sector sector = new(nextId, Header.SectorSize); - yield return sector; - nextId = GetNextFatSectorId(nextId); - } - } } diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index 5e2dbefe..6792f402 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -29,6 +29,12 @@ public static RootStorage Open(string fileName, FileMode mode) return Open(stream); } + public static RootStorage OpenRead(string fileName) + { + FileStream stream = File.OpenRead(fileName); + return Open(stream); + } + public static RootStorage Open(Stream stream) { McdfBinaryReader reader = new(stream); @@ -55,9 +61,10 @@ public void Dispose() IEnumerable EnumerateDirectoryEntries() { - foreach (Sector sector in ioContext.EnumerateFatSectorChain(ioContext.Header.FirstDirectorySectorID)) + using FatSectorChainEnumerator chainEnumerator = new(ioContext, ioContext.Header.FirstDirectorySectorID); + while (chainEnumerator.MoveNext()) { - ioContext.Reader.Seek(sector.StartOffset); + ioContext.Reader.Seek(chainEnumerator.Current.StartOffset); int entryCount = ioContext.Header.SectorSize / DirectoryEntry.Length; for (int i = 0; i < entryCount; i++) diff --git a/OpenMcdf3/Sector.cs b/OpenMcdf3/Sector.cs index 75700be3..8974d320 100644 --- a/OpenMcdf3/Sector.cs +++ b/OpenMcdf3/Sector.cs @@ -1,20 +1,14 @@ namespace OpenMcdf3; -internal struct Sector +internal record struct Sector(uint Index, int Length) { - const int MiniSectorSize = 64; + public const int MiniSectorSize = 64; - public Sector(uint index, long length) - { - Index = index; - Length = length; - } - - public uint Index { get; } - - public long Length { get; } + public static readonly Sector EndOfChain = new(SectorType.EndOfChain, 0); public readonly long StartOffset => (Index + 1) * Length; public readonly long EndOffset => (Index + 2) * Length; + + public override readonly string ToString() => $"{Index}"; } diff --git a/OpenMcdf3/SectorType.cs b/OpenMcdf3/SectorType.cs index f38feef9..f4820042 100644 --- a/OpenMcdf3/SectorType.cs +++ b/OpenMcdf3/SectorType.cs @@ -1,10 +1,10 @@ namespace OpenMcdf3; -internal enum SectorType : uint +internal static class SectorType { - Maximum = 0xFFFFFFFA, - Difat = 0xFFFFFFFC, // Specifies a DIFAT sector in the FAT. - Fat = 0xFFFFFFFD, - EndOfChain = 0xFFFFFFFE, - Free = 0xFFFFFFFF, + public const uint Maximum = 0xFFFFFFFA; + public const uint Difat = 0xFFFFFFFC; // Specifies a DIFAT sector in the FAT. + public const uint Fat = 0xFFFFFFFD; + public const uint EndOfChain = 0xFFFFFFFE; + public const uint Free = 0xFFFFFFFF; } From cacc76a4c5f184b8e47de798c8194738553a3ce4 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 14 Oct 2024 14:07:04 +1300 Subject: [PATCH 011/134] Move EntryInfo enumerator etc. to Storage --- OpenMcdf3/FatSectorChainEnumerator.cs | 2 +- OpenMcdf3/IOContext.cs | 2 +- OpenMcdf3/RootStorage.cs | 36 +++------------------------ OpenMcdf3/Storage.cs | 36 +++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 34 deletions(-) diff --git a/OpenMcdf3/FatSectorChainEnumerator.cs b/OpenMcdf3/FatSectorChainEnumerator.cs index a7ea20ba..c368f8e2 100644 --- a/OpenMcdf3/FatSectorChainEnumerator.cs +++ b/OpenMcdf3/FatSectorChainEnumerator.cs @@ -56,6 +56,6 @@ public void Reset() public void Dispose() { - // No resources to dispose + fatEnumerator.Dispose(); } } diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index abe22fbe..8ba4a857 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -6,7 +6,7 @@ internal sealed class IOContext : IDisposable public McdfBinaryReader Reader { get; } public McdfBinaryWriter? Writer { get; } - public IOContext(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer = null) + public IOContext(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer, bool leaveOpen = false) { Header = header; Reader = reader; diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index 6792f402..5c613702 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -10,7 +10,6 @@ public enum Version : ushort public sealed class RootStorage : Storage, IDisposable { - readonly IOContext ioContext; bool disposed; public static RootStorage Create(string fileName, Version version = Version.V3) @@ -35,18 +34,18 @@ public static RootStorage OpenRead(string fileName) return Open(stream); } - public static RootStorage Open(Stream stream) + public static RootStorage Open(Stream stream, bool leaveOpen = false) { McdfBinaryReader reader = new(stream); McdfBinaryWriter? writer = stream.CanWrite ? new(stream) : null; Header header = reader.ReadHeader(); - IOContext ioContext = new(header, reader, writer); + IOContext ioContext = new(header, reader, writer, leaveOpen); return new RootStorage(ioContext); } RootStorage(IOContext ioContext) + : base(ioContext, ioContext.Header.FirstDirectorySectorID) { - this.ioContext = ioContext; } public void Dispose() @@ -54,34 +53,7 @@ public void Dispose() if (disposed) return; - ioContext.Writer?.Dispose(); - ioContext.Reader.Dispose(); + IOContext?.Dispose(); disposed = true; } - - IEnumerable EnumerateDirectoryEntries() - { - using FatSectorChainEnumerator chainEnumerator = new(ioContext, ioContext.Header.FirstDirectorySectorID); - while (chainEnumerator.MoveNext()) - { - ioContext.Reader.Seek(chainEnumerator.Current.StartOffset); - - int entryCount = ioContext.Header.SectorSize / DirectoryEntry.Length; - for (int i = 0; i < entryCount; i++) - { - DirectoryEntry entry = ioContext.Reader.ReadDirectoryEntry((Version)ioContext.Header.MajorVersion); - if (entry.Type is not StorageType.Invalid) - yield return entry; - } - } - } - - public IEnumerable EnumerateEntries() => EnumerateDirectoryEntries().Select(e => new EntryInfo { Name = e.Name }); - - public CfbStream OpenStream(string name) - { - DirectoryEntry? entry = EnumerateDirectoryEntries() - .FirstOrDefault(entry => entry.Name == name) ?? throw new FileNotFoundException("Stream not found", name); - return new CfbStream(ioContext, ioContext.Header.SectorSize, entry); - } } diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index a4aa9bcc..9af23672 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -12,4 +12,40 @@ public enum StorageType public class Storage { + internal IOContext IOContext { get; } + + uint firstDirectorySector; + + internal Storage(IOContext ioContext, uint firstDirectorySector) + { + IOContext = ioContext; + this.firstDirectorySector = firstDirectorySector; + } + + IEnumerable EnumerateDirectoryEntries() + { + var version = (Version)IOContext.Header.MajorVersion; + int entryCount = IOContext.Header.SectorSize / DirectoryEntry.Length; + using FatSectorChainEnumerator chainEnumerator = new(IOContext, firstDirectorySector); + while (chainEnumerator.MoveNext()) + { + IOContext.Reader.Seek(chainEnumerator.Current.StartOffset); + + for (int i = 0; i < entryCount; i++) + { + DirectoryEntry entry = IOContext.Reader.ReadDirectoryEntry(version); + if (entry.Type is not StorageType.Invalid) + yield return entry; + } + } + } + + public IEnumerable EnumerateEntries() => EnumerateDirectoryEntries().Select(e => new EntryInfo { Name = e.Name }); + + public CfbStream OpenStream(string name) + { + DirectoryEntry? entry = EnumerateDirectoryEntries() + .FirstOrDefault(entry => entry.Name == name) ?? throw new FileNotFoundException("Stream not found", name); + return new CfbStream(IOContext, IOContext.Header.SectorSize, entry); + } } From 7821a5d148cb523d1f3c347680c5eeccc25de3e3 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 14 Oct 2024 14:11:16 +1300 Subject: [PATCH 012/134] Improve Sector validation --- OpenMcdf3/FatSectorChainEnumerator.cs | 4 ++-- OpenMcdf3/FatSectorEnumerator.cs | 7 +++---- OpenMcdf3/Sector.cs | 24 ++++++++++++++++++++++-- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/OpenMcdf3/FatSectorChainEnumerator.cs b/OpenMcdf3/FatSectorChainEnumerator.cs index c368f8e2..b385ebe8 100644 --- a/OpenMcdf3/FatSectorChainEnumerator.cs +++ b/OpenMcdf3/FatSectorChainEnumerator.cs @@ -17,7 +17,7 @@ public FatSectorChainEnumerator(IOContext ioContext, uint startId) Index = SectorType.EndOfChain; this.startId = startId; this.nextId = SectorType.Free; - this.current = new Sector(0, 0); // Initialize with a default value + this.current = Sector.EndOfChain; } public uint Index { get; private set; } @@ -50,7 +50,7 @@ public void Reset() { Index = SectorType.EndOfChain; nextId = SectorType.Free; - current = new Sector(0, 0); // Reset to default value + current = Sector.EndOfChain; fatEnumerator.Reset(); } diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index c34bb474..03a70903 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -15,7 +15,7 @@ public FatSectorEnumerator(IOContext ioContext) this.ioContext = ioContext; this.index = SectorType.EndOfChain; this.nextDifatSectorId = ioContext.Header.FirstDifatSectorID; - Current = default; + Current = Sector.EndOfChain; } public Sector Current { get; private set; } @@ -68,7 +68,7 @@ public void Reset() { index = SectorType.EndOfChain; difatSectorElementIndex = SectorType.EndOfChain; - Current = new Sector(0, 0); // Reset to default value + Current = Sector.EndOfChain; } public uint GetNextFatSectorId(uint id) @@ -81,8 +81,7 @@ public uint GetNextFatSectorId(uint id) return SectorType.EndOfChain; } - Sector sector = Current; - long position = sector.StartOffset + sectorOffset * sizeof(uint); + long position = Current.StartOffset + sectorOffset * sizeof(uint); ioContext.Reader.Seek(position); uint nextId = ioContext.Reader.ReadUInt32(); return nextId; diff --git a/OpenMcdf3/Sector.cs b/OpenMcdf3/Sector.cs index 8974d320..4149614f 100644 --- a/OpenMcdf3/Sector.cs +++ b/OpenMcdf3/Sector.cs @@ -6,9 +6,29 @@ internal record struct Sector(uint Index, int Length) public static readonly Sector EndOfChain = new(SectorType.EndOfChain, 0); - public readonly long StartOffset => (Index + 1) * Length; + readonly void ThrowIfInvalid() + { + if (Index > SectorType.Maximum) + throw new InvalidOperationException($"Invalid sector index: {Index}"); + } - public readonly long EndOffset => (Index + 2) * Length; + public readonly long StartOffset + { + get + { + ThrowIfInvalid(); + return (Index + 1) * Length; + } + } + + public readonly long EndOffset + { + get + { + ThrowIfInvalid(); + return (Index + 2) * Length; + } + } public override readonly string ToString() => $"{Index}"; } From b2fc989e4c3e6b682c8416bf5aa72b3fe37ae9e3 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 14 Oct 2024 16:24:01 +1300 Subject: [PATCH 013/134] Allow storage directory entry enumeration --- OpenMcdf3.Tests/EntryInfoTests.cs | 4 +- OpenMcdf3.Tests/MultipleStorage.cfs | Bin 0 -> 4096 bytes OpenMcdf3.Tests/MultipleStorage2.cfs | Bin 0 -> 4608 bytes OpenMcdf3.Tests/MultipleStorage3.cfs | Bin 0 -> 22016 bytes OpenMcdf3.Tests/MultipleStorage4.cfs | Bin 0 -> 53248 bytes OpenMcdf3.Tests/OpenMcdf3.Tests.csproj | 12 ++++ OpenMcdf3.Tests/StorageTests.cs | 22 +++++++ OpenMcdf3/CfbStream.cs | 6 +- OpenMcdf3/DirectoryEntry.cs | 25 ++++++-- OpenMcdf3/DirectoryEntryEnumerator.cs | 79 +++++++++++++++++++++++++ OpenMcdf3/DirectoryTreeEnumerator.cs | 55 +++++++++++++++++ OpenMcdf3/FatSectorChainEnumerator.cs | 1 + OpenMcdf3/IOContext.cs | 7 +++ OpenMcdf3/RootStorage.cs | 10 ++-- OpenMcdf3/Storage.cs | 51 +++++++--------- 15 files changed, 229 insertions(+), 43 deletions(-) create mode 100644 OpenMcdf3.Tests/MultipleStorage.cfs create mode 100644 OpenMcdf3.Tests/MultipleStorage2.cfs create mode 100644 OpenMcdf3.Tests/MultipleStorage3.cfs create mode 100644 OpenMcdf3.Tests/MultipleStorage4.cfs create mode 100644 OpenMcdf3.Tests/StorageTests.cs create mode 100644 OpenMcdf3/DirectoryEntryEnumerator.cs create mode 100644 OpenMcdf3/DirectoryTreeEnumerator.cs diff --git a/OpenMcdf3.Tests/EntryInfoTests.cs b/OpenMcdf3.Tests/EntryInfoTests.cs index 3af05877..290aec8e 100644 --- a/OpenMcdf3.Tests/EntryInfoTests.cs +++ b/OpenMcdf3.Tests/EntryInfoTests.cs @@ -4,8 +4,8 @@ public sealed class EntryInfoTests { [TestMethod] - [DataRow("_Test.ppt", 5)] - [DataRow("test.cfb", 2)] + [DataRow("_Test.ppt", 4)] + [DataRow("test.cfb", 1)] public void EnumerateEntryInfos(string fileName, int count) { using var rootStorage = RootStorage.OpenRead(fileName); diff --git a/OpenMcdf3.Tests/MultipleStorage.cfs b/OpenMcdf3.Tests/MultipleStorage.cfs new file mode 100644 index 0000000000000000000000000000000000000000..8cdb2e03caf64aae2c1d047bd047288e46d87f95 GIT binary patch literal 4096 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 literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj index 009e0b55..bad7483e 100644 --- a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj +++ b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj @@ -26,6 +26,18 @@ + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/OpenMcdf3.Tests/StorageTests.cs b/OpenMcdf3.Tests/StorageTests.cs new file mode 100644 index 00000000..68695a5c --- /dev/null +++ b/OpenMcdf3.Tests/StorageTests.cs @@ -0,0 +1,22 @@ +using System.Diagnostics; + +namespace OpenMcdf3.Tests; + +[TestClass] +public sealed class StorageTests +{ + [TestMethod] + [DataRow("_Test.ppt", 0)] + [DataRow("MultipleStorage.cfs", 1)] + [DataRow("MultipleStorage2.cfs", 1)] + [DataRow("MultipleStorage3.cfs", 1)] + [DataRow("MultipleStorage4.cfs", 1)] + public void Read(string fileName, long storageCount) + { + using var rootStorage = RootStorage.OpenRead(fileName); + IEnumerable storageEntries = rootStorage.EnumerateEntries(StorageType.Storage); + //foreach (var entryInfo in storageEntries) + // Trace.WriteLine($"{entryInfo}"); + Assert.AreEqual(storageCount, storageEntries.Count()); + } +} diff --git a/OpenMcdf3/CfbStream.cs b/OpenMcdf3/CfbStream.cs index d060d714..9ca19690 100644 --- a/OpenMcdf3/CfbStream.cs +++ b/OpenMcdf3/CfbStream.cs @@ -3,15 +3,13 @@ public class CfbStream : Stream { readonly IOContext ioContext; - readonly long sectorLength; readonly FatSectorChainEnumerator chain; long length; long position; - internal CfbStream(IOContext ioContext, long sectorLength, DirectoryEntry directoryEntry) + internal CfbStream(IOContext ioContext, DirectoryEntry directoryEntry) { this.ioContext = ioContext; - this.sectorLength = sectorLength; DirectoryEntry = directoryEntry; length = directoryEntry.StreamLength; chain = new(ioContext, directoryEntry.StartSectorLocation); @@ -40,7 +38,7 @@ public override void Flush() public override int Read(byte[] buffer, int offset, int count) { - int sectorIndex = (int)Math.DivRem(position, sectorLength, out long sectorOffset); + int sectorIndex = (int)Math.DivRem(position, ioContext.Header.SectorSize, out long sectorOffset); while (sectorIndex > 0 && (chain.Index == SectorType.EndOfChain || chain.Index < sectorIndex)) { if (!chain.MoveNext()) diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index 45ae5a19..127bb208 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -1,16 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices.ComTypes; -using System.Text; +using System.Text; namespace OpenMcdf3; +public enum StorageType +{ + Unallocated = 0, + Storage = 1, + Stream = 2, + Root = 5 +} + enum Color { Red = 0, Black = 1 } +internal static class StreamId +{ + public const uint Maximum = 0xFFFFFFFA; + public const uint NoStream = 0xFFFFFFFF; +} + internal sealed class DirectoryEntry { internal const int Length = 128; @@ -38,7 +49,7 @@ public string Name } } - public StorageType Type { get; set; } = StorageType.Invalid; + public StorageType Type { get; set; } = StorageType.Unallocated; public Color Color { get; set; } @@ -79,4 +90,8 @@ public DateTime ModifiedTime public uint StartSectorLocation { get; set; } public long StreamLength { get; set; } + + public EntryInfo ToEntryInfo() => new() { Name = Name }; + + public override string ToString() => Name; } diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs new file mode 100644 index 00000000..aec02b47 --- /dev/null +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -0,0 +1,79 @@ +using System.Collections; + +namespace OpenMcdf3; + +internal sealed class DirectoryEntryEnumerator : IEnumerator +{ + private readonly IOContext ioContext; + private readonly Version version; + private readonly int entryCount; + private readonly FatSectorChainEnumerator chainEnumerator; + private int entryIndex; + private DirectoryEntry? current; + + public DirectoryEntryEnumerator(IOContext ioContext) + { + this.ioContext = ioContext; + this.version = (Version)ioContext.Header.MajorVersion; + this.entryCount = ioContext.Header.SectorSize / DirectoryEntry.Length; + this.chainEnumerator = new FatSectorChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorID); + this.entryIndex = -1; + this.current = default; + } + + public DirectoryEntry Current => current!; + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + if (entryIndex == -1 || entryIndex >= entryCount) + { + if (!chainEnumerator.MoveNext()) + return false; + + ioContext.Reader.Seek(chainEnumerator.Current.StartOffset); + entryIndex = 0; + } + + current = ioContext.Reader.ReadDirectoryEntry(version); + entryIndex++; + return current.Type != StorageType.Unallocated; + } + + public DirectoryEntry? Get(uint id) + { + if (id == StreamId.NoStream) + return null; + + int sectorIndex = Math.DivRem((int)id, entryCount, out int entryIndex); + if (sectorIndex < chainEnumerator.Index) + { + chainEnumerator.Reset(); + chainEnumerator.MoveNext(); + } + + while (chainEnumerator.Index - 1 < sectorIndex) + { + if (!chainEnumerator.MoveNext()) + return null; + } + + long position = chainEnumerator.Current.StartOffset + entryIndex * DirectoryEntry.Length; + ioContext.Reader.Seek(position); + current = ioContext.Reader.ReadDirectoryEntry(version); + return current; + } + + public void Reset() + { + chainEnumerator.Reset(); + entryIndex = -1; + current = default!; + } + + public void Dispose() + { + chainEnumerator.Dispose(); + } +} diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs new file mode 100644 index 00000000..631cfd15 --- /dev/null +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -0,0 +1,55 @@ +using System.Collections; + +namespace OpenMcdf3; + +internal sealed class DirectoryTreeEnumerator : IEnumerator +{ + private readonly DirectoryEntry? child; + private readonly Stack stack = new(); + private readonly DirectoryEntryEnumerator directoryEntryEnumerator; + DirectoryEntry current; + + internal DirectoryTreeEnumerator(IOContext ioContext, DirectoryEntry root) + { + directoryEntryEnumerator = new(ioContext); + this.child = directoryEntryEnumerator.Get(root.ChildID); + PushLeft(child); + current = default!; + } + + public void Dispose() + { + directoryEntryEnumerator.Dispose(); + } + + public DirectoryEntry Current => current; + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + if (stack.Count == 0) + return false; + + current = stack.Pop(); + DirectoryEntry? rightSibling = directoryEntryEnumerator.Get(Current.RightSiblingID); + PushLeft(rightSibling); + return true; + } + + public void Reset() + { + current = default!; + stack.Clear(); + PushLeft(child); + } + + private void PushLeft(DirectoryEntry? node) + { + while (node is not null) + { + stack.Push(node); + node = directoryEntryEnumerator.Get(node.LeftSiblingID); + } + } +} diff --git a/OpenMcdf3/FatSectorChainEnumerator.cs b/OpenMcdf3/FatSectorChainEnumerator.cs index b385ebe8..f0a72af6 100644 --- a/OpenMcdf3/FatSectorChainEnumerator.cs +++ b/OpenMcdf3/FatSectorChainEnumerator.cs @@ -20,6 +20,7 @@ public FatSectorChainEnumerator(IOContext ioContext, uint startId) this.current = Sector.EndOfChain; } + // TODO: Fix off-by one error public uint Index { get; private set; } public Sector Current => current; diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index 8ba4a857..b59774f3 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -18,4 +18,11 @@ public void Dispose() Reader.Dispose(); Writer?.Dispose(); } + + public IEnumerable EnumerateDirectoryEntries() + { + using DirectoryEntryEnumerator directoryEntriesEnumerator = new(this); + while (directoryEntriesEnumerator.MoveNext()) + yield return directoryEntriesEnumerator.Current; + } } diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index 5c613702..d3a4cf4f 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -19,7 +19,8 @@ public static RootStorage Create(string fileName, Version version = Version.V3) McdfBinaryReader reader = new(stream); McdfBinaryWriter writer = new(stream); IOContext ioContext = new(header, reader, writer); - return new RootStorage(ioContext); + DirectoryEntry directoryEntry = new(); + return new RootStorage(ioContext, directoryEntry); } public static RootStorage Open(string fileName, FileMode mode) @@ -40,11 +41,12 @@ public static RootStorage Open(Stream stream, bool leaveOpen = false) McdfBinaryWriter? writer = stream.CanWrite ? new(stream) : null; Header header = reader.ReadHeader(); IOContext ioContext = new(header, reader, writer, leaveOpen); - return new RootStorage(ioContext); + DirectoryEntry rootDirectoryEntry = ioContext.EnumerateDirectoryEntries().First(); + return new RootStorage(ioContext, rootDirectoryEntry); } - RootStorage(IOContext ioContext) - : base(ioContext, ioContext.Header.FirstDirectorySectorID) + RootStorage(IOContext ioContext, DirectoryEntry rootDirectoryEntry) + : base(ioContext, rootDirectoryEntry) { } diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index 9af23672..cee6f926 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -1,51 +1,46 @@ namespace OpenMcdf3; -public enum StorageType -{ - Invalid = 0, - Storage = 1, - Stream = 2, - Lockbytes = 3, - Property = 4, - Root = 5 -} - public class Storage { internal IOContext IOContext { get; } - uint firstDirectorySector; + internal DirectoryEntry DirectoryEntry { get; } - internal Storage(IOContext ioContext, uint firstDirectorySector) + internal Storage(IOContext ioContext, DirectoryEntry directoryEntry) { IOContext = ioContext; - this.firstDirectorySector = firstDirectorySector; + DirectoryEntry = directoryEntry; } + public IEnumerable EnumerateEntries() => EnumerateDirectoryEntries() + .Select(e => e.ToEntryInfo()); + + public IEnumerable EnumerateEntries(StorageType type) => EnumerateDirectoryEntries(type) + .Select(e => e.ToEntryInfo()); + IEnumerable EnumerateDirectoryEntries() { - var version = (Version)IOContext.Header.MajorVersion; - int entryCount = IOContext.Header.SectorSize / DirectoryEntry.Length; - using FatSectorChainEnumerator chainEnumerator = new(IOContext, firstDirectorySector); - while (chainEnumerator.MoveNext()) + using DirectoryTreeEnumerator treeEnumerator = new(IOContext, DirectoryEntry); + while (treeEnumerator.MoveNext()) { - IOContext.Reader.Seek(chainEnumerator.Current.StartOffset); - - for (int i = 0; i < entryCount; i++) - { - DirectoryEntry entry = IOContext.Reader.ReadDirectoryEntry(version); - if (entry.Type is not StorageType.Invalid) - yield return entry; - } + yield return treeEnumerator.Current; } } - public IEnumerable EnumerateEntries() => EnumerateDirectoryEntries().Select(e => new EntryInfo { Name = e.Name }); + IEnumerable EnumerateDirectoryEntries(StorageType type) => EnumerateDirectoryEntries() + .Where(e => e.Type == type); + + public Storage OpenStorage(string name) + { + DirectoryEntry? entry = EnumerateDirectoryEntries(StorageType.Storage) + .FirstOrDefault(entry => entry.Name == name) ?? throw new DirectoryNotFoundException($"Directory not found {name}"); + return new Storage(IOContext, entry); + } public CfbStream OpenStream(string name) { - DirectoryEntry? entry = EnumerateDirectoryEntries() + DirectoryEntry? entry = EnumerateDirectoryEntries(StorageType.Stream) .FirstOrDefault(entry => entry.Name == name) ?? throw new FileNotFoundException("Stream not found", name); - return new CfbStream(IOContext, IOContext.Header.SectorSize, entry); + return new CfbStream(IOContext, entry); } } From 4d511e09962e72e130362a1751312505f5491e9f Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 14 Oct 2024 21:30:01 +1300 Subject: [PATCH 014/134] Improve enumerator validation --- OpenMcdf3.Tests/CfbStreamTests.cs | 17 ++++- OpenMcdf3/CfbStream.cs | 20 +++--- OpenMcdf3/DirectoryEntryEnumerator.cs | 29 ++++----- OpenMcdf3/DirectoryTreeEnumerator.cs | 20 ++++-- OpenMcdf3/FatSectorChainEnumerator.cs | 69 +++++++++++++------- OpenMcdf3/FatSectorEnumerator.cs | 91 ++++++++++++++++----------- OpenMcdf3/Sector.cs | 26 +++++--- 7 files changed, 176 insertions(+), 96 deletions(-) diff --git a/OpenMcdf3.Tests/CfbStreamTests.cs b/OpenMcdf3.Tests/CfbStreamTests.cs index 037f99c9..1035ac00 100644 --- a/OpenMcdf3.Tests/CfbStreamTests.cs +++ b/OpenMcdf3.Tests/CfbStreamTests.cs @@ -6,7 +6,7 @@ public sealed class CfbStreamTests [TestMethod] [DataRow("_Test.ppt", "Current User", 62)] [DataRow("test.cfb", "MyStream0", 1048576)] - public void CfbStreamTest(string fileName, string streamName, long length) + public void Read(string fileName, string streamName, long length) { using var rootStorage = RootStorage.OpenRead(fileName); using CfbStream stream = rootStorage.OpenStream(streamName); @@ -16,4 +16,19 @@ public void CfbStreamTest(string fileName, string streamName, long length) stream.CopyTo(memoryStream); Assert.AreEqual(length, memoryStream.Length); } + + [TestMethod] + [DataRow("_Test.ppt")] + [DataRow("test.cfb")] + public void ReadAllStreams(string fileName) + { + using var rootStorage = RootStorage.OpenRead(fileName); + foreach (EntryInfo entryInfo in rootStorage.EnumerateEntries(StorageType.Stream)) + { + using CfbStream stream = rootStorage.OpenStream(entryInfo.Name); + using MemoryStream memoryStream = new(); + stream.CopyTo(memoryStream); + //Assert.AreEqual(entryInfo.Length, memoryStream.Length); + } + } } diff --git a/OpenMcdf3/CfbStream.cs b/OpenMcdf3/CfbStream.cs index 9ca19690..768533d6 100644 --- a/OpenMcdf3/CfbStream.cs +++ b/OpenMcdf3/CfbStream.cs @@ -31,6 +31,13 @@ public override long Position set => position = value; } + protected override void Dispose(bool disposing) + { + chain.Dispose(); + + base.Dispose(disposing); + } + public override void Flush() { //rootStorage.Flush(); @@ -38,18 +45,15 @@ public override void Flush() public override int Read(byte[] buffer, int offset, int count) { - int sectorIndex = (int)Math.DivRem(position, ioContext.Header.SectorSize, out long sectorOffset); - while (sectorIndex > 0 && (chain.Index == SectorType.EndOfChain || chain.Index < sectorIndex)) - { - if (!chain.MoveNext()) - return 0; - } + uint chainIndex = (uint)Math.DivRem(position, ioContext.Header.SectorSize, out long sectorOffset); + if (!chain.MoveTo(chainIndex)) + return 0; int maxCount = (int)Math.Min(Math.Max(length - position, 0), int.MaxValue); int realCount = Math.Min(count, maxCount); int readCount = 0; int remaining = realCount; - while (chain.MoveNext()) + do { Sector sector = chain.Current; long readLength = Math.Min(remaining, sector.Length - sectorOffset); @@ -61,7 +65,7 @@ public override int Read(byte[] buffer, int offset, int count) readCount += read; if (readCount >= realCount) return readCount; - } + } while (chain.MoveNext()); return readCount; } diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index aec02b47..655cbfd1 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -8,7 +8,7 @@ internal sealed class DirectoryEntryEnumerator : IEnumerator private readonly Version version; private readonly int entryCount; private readonly FatSectorChainEnumerator chainEnumerator; - private int entryIndex; + private int entryIndex = -1; private DirectoryEntry? current; public DirectoryEntryEnumerator(IOContext ioContext) @@ -17,11 +17,17 @@ public DirectoryEntryEnumerator(IOContext ioContext) this.version = (Version)ioContext.Header.MajorVersion; this.entryCount = ioContext.Header.SectorSize / DirectoryEntry.Length; this.chainEnumerator = new FatSectorChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorID); - this.entryIndex = -1; - this.current = default; } - public DirectoryEntry Current => current!; + public DirectoryEntry Current + { + get + { + if (current is null) + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + return current; + } + } object IEnumerator.Current => Current; @@ -46,18 +52,9 @@ public bool MoveNext() if (id == StreamId.NoStream) return null; - int sectorIndex = Math.DivRem((int)id, entryCount, out int entryIndex); - if (sectorIndex < chainEnumerator.Index) - { - chainEnumerator.Reset(); - chainEnumerator.MoveNext(); - } - - while (chainEnumerator.Index - 1 < sectorIndex) - { - if (!chainEnumerator.MoveNext()) - return null; - } + uint chainIndex = (uint)Math.DivRem(id, entryCount, out long entryIndex); + if (!chainEnumerator.MoveTo(chainIndex)) + throw new ArgumentException("Invalid directory entry ID"); long position = chainEnumerator.Current.StartOffset + entryIndex * DirectoryEntry.Length; ioContext.Reader.Seek(position); diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index 631cfd15..ab1aed59 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -7,14 +7,13 @@ internal sealed class DirectoryTreeEnumerator : IEnumerator private readonly DirectoryEntry? child; private readonly Stack stack = new(); private readonly DirectoryEntryEnumerator directoryEntryEnumerator; - DirectoryEntry current; + DirectoryEntry? current; internal DirectoryTreeEnumerator(IOContext ioContext, DirectoryEntry root) { directoryEntryEnumerator = new(ioContext); - this.child = directoryEntryEnumerator.Get(root.ChildID); + child = directoryEntryEnumerator.Get(root.ChildID); PushLeft(child); - current = default!; } public void Dispose() @@ -22,14 +21,25 @@ public void Dispose() directoryEntryEnumerator.Dispose(); } - public DirectoryEntry Current => current; + public DirectoryEntry Current + { + get + { + if (current is null) + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + return current; + } + } object IEnumerator.Current => Current; public bool MoveNext() { if (stack.Count == 0) + { + current = null; return false; + } current = stack.Pop(); DirectoryEntry? rightSibling = directoryEntryEnumerator.Get(Current.RightSiblingID); @@ -39,7 +49,7 @@ public bool MoveNext() public void Reset() { - current = default!; + current = null; stack.Clear(); PushLeft(child); } diff --git a/OpenMcdf3/FatSectorChainEnumerator.cs b/OpenMcdf3/FatSectorChainEnumerator.cs index f0a72af6..bad0f6e5 100644 --- a/OpenMcdf3/FatSectorChainEnumerator.cs +++ b/OpenMcdf3/FatSectorChainEnumerator.cs @@ -4,55 +4,80 @@ namespace OpenMcdf3; internal sealed class FatSectorChainEnumerator : IEnumerator { - private readonly FatSectorEnumerator fatEnumerator; private readonly IOContext ioContext; + private readonly FatSectorEnumerator fatEnumerator; private readonly uint startId; - private uint nextId; - private Sector current; + private bool start = true; + private Sector current = Sector.EndOfChain; - public FatSectorChainEnumerator(IOContext ioContext, uint startId) + public FatSectorChainEnumerator(IOContext ioContext, uint startSectorId) { - fatEnumerator = new(ioContext); this.ioContext = ioContext; - Index = SectorType.EndOfChain; - this.startId = startId; - this.nextId = SectorType.Free; - this.current = Sector.EndOfChain; + if (startSectorId is SectorType.EndOfChain) + throw new ArgumentException("Invalid start sector ID", nameof(startSectorId)); + this.startId = startSectorId; + fatEnumerator = new(ioContext); } - // TODO: Fix off-by one error - public uint Index { get; private set; } + public uint Index { get; private set; } = uint.MaxValue; - public Sector Current => current; + public Sector Current + { + get + { + if (current.IsEndOfChain) + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + return current; + } + } object IEnumerator.Current => Current; public bool MoveNext() { - if (nextId is SectorType.Free) + if (start) { + current = new(startId, ioContext.Header.SectorSize); Index = 0; - nextId = startId; + start = false; + } + else if (!current.IsEndOfChain) + { + uint sectorId = fatEnumerator.GetNextFatSectorId(current.Id); + current = new(sectorId, ioContext.Header.SectorSize); + Index++; } - if (nextId is SectorType.EndOfChain) + if (current.IsEndOfChain) { - Index = SectorType.EndOfChain; + current = Sector.EndOfChain; + Index = uint.MaxValue; return false; } - Index++; - current = new Sector(nextId, ioContext.Header.SectorSize); - nextId = fatEnumerator.GetNextFatSectorId(nextId); + return true; + } + + public bool MoveTo(uint index) + { + if (index < Index) + Reset(); + + while (start || Index < index) + { + if (!MoveNext()) + return false; + } + return true; } public void Reset() { - Index = SectorType.EndOfChain; - nextId = SectorType.Free; - current = Sector.EndOfChain; + start = true; fatEnumerator.Reset(); + current = Sector.EndOfChain; + Index = uint.MaxValue; } public void Dispose() diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index 03a70903..97430152 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Generic; using System.Diagnostics.SymbolStore; namespace OpenMcdf3; @@ -6,80 +7,100 @@ namespace OpenMcdf3; internal sealed class FatSectorEnumerator : IEnumerator { private readonly IOContext ioContext; - private uint index = SectorType.EndOfChain; - uint nextDifatSectorId = SectorType.EndOfChain; - uint difatSectorElementIndex = SectorType.EndOfChain; + private bool start = true; + private uint id = SectorType.EndOfChain; + private uint difatSectorId; + private uint difatSectorElementIndex = 0; + private Sector current = Sector.EndOfChain; public FatSectorEnumerator(IOContext ioContext) { this.ioContext = ioContext; - this.index = SectorType.EndOfChain; - this.nextDifatSectorId = ioContext.Header.FirstDifatSectorID; - Current = Sector.EndOfChain; + this.difatSectorId = ioContext.Header.FirstDifatSectorID; } - public Sector Current { get; private set; } + public Sector Current + { + get + { + if (current.IsEndOfChain) + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + return current; + } + } object IEnumerator.Current => Current; public bool MoveNext() { - if (index == SectorType.EndOfChain) + if (start) { - index = 0; + id = uint.MaxValue; + start = false; } - if (index < ioContext.Header.FatSectorCount && index < Header.DifatLength) + id++; + + if (id < ioContext.Header.FatSectorCount && id < Header.DifatLength) { - uint nextId = ioContext.Header.Difat[index]; - Current = new Sector(nextId, ioContext.Header.SectorSize); - index++; + uint id = ioContext.Header.Difat[this.id]; + current = new Sector(id, ioContext.Header.SectorSize); return true; } - if (nextDifatSectorId == SectorType.EndOfChain) + if (difatSectorId == SectorType.EndOfChain) + { + current = Sector.EndOfChain; + id = SectorType.EndOfChain; return false; + } int difatElementCount = ioContext.Header.SectorSize / sizeof(uint) - 1; - Sector difatSector = new(nextDifatSectorId, ioContext.Header.SectorSize); + Sector difatSector = new(difatSectorId, ioContext.Header.SectorSize); + long position = difatSector.StartOffset + difatSectorElementIndex * sizeof(uint); + ioContext.Reader.Seek(position); + uint sectorId = ioContext.Reader.ReadUInt32(); + current = new Sector(sectorId, ioContext.Header.SectorSize); + difatSectorElementIndex++; + id++; + if (difatSectorElementIndex == difatElementCount) { - long nextIdOffset = difatSector.EndOffset - sizeof(uint); - ioContext.Reader.Seek(nextIdOffset); - nextDifatSectorId = ioContext.Reader.ReadUInt32(); + difatSectorId = ioContext.Reader.ReadUInt32(); difatSectorElementIndex = 0; } - if (difatSectorElementIndex < difatElementCount) + return true; + } + + public bool MoveTo(uint sectorId) + { + if (sectorId < id) + Reset(); + + while (start || id < sectorId) { - long position = difatSector.StartOffset + difatSectorElementIndex * sizeof(uint); - ioContext.Reader.Seek(position); - uint nextId = ioContext.Reader.ReadUInt32(); - Current = new Sector(nextId, ioContext.Header.SectorSize); - difatSectorElementIndex++; - index++; - return true; + if (!MoveNext()) + return false; } - return false; + return true; } public void Reset() { - index = SectorType.EndOfChain; + start = true; + id = SectorType.EndOfChain; difatSectorElementIndex = SectorType.EndOfChain; - Current = Sector.EndOfChain; + current = Sector.EndOfChain; } public uint GetNextFatSectorId(uint id) { int elementLength = ioContext.Header.SectorSize / sizeof(uint); - int sectorId = (int)Math.DivRem(id, elementLength, out long sectorOffset); - while (index == SectorType.EndOfChain || index - 1 < sectorId) - { - if (!MoveNext()) - return SectorType.EndOfChain; - } + uint sectorId = (uint)Math.DivRem(id, elementLength, out long sectorOffset); + if (!MoveTo(sectorId)) + throw new ArgumentException("Invalid sector ID"); long position = Current.StartOffset + sectorOffset * sizeof(uint); ioContext.Reader.Seek(position); diff --git a/OpenMcdf3/Sector.cs b/OpenMcdf3/Sector.cs index 4149614f..10c956f9 100644 --- a/OpenMcdf3/Sector.cs +++ b/OpenMcdf3/Sector.cs @@ -1,23 +1,25 @@ namespace OpenMcdf3; -internal record struct Sector(uint Index, int Length) +internal record struct Sector(uint Id, int Length) { public const int MiniSectorSize = 64; public static readonly Sector EndOfChain = new(SectorType.EndOfChain, 0); - readonly void ThrowIfInvalid() - { - if (Index > SectorType.Maximum) - throw new InvalidOperationException($"Invalid sector index: {Index}"); - } + /// + /// Compound File Binary File Format only specifies that ENDOFCHAIN ends the DIFAT chain + /// but some implementations use FREESECT + /// + public readonly bool IsEndOfChain => Id is SectorType.EndOfChain or SectorType.Free; + + public readonly bool IsValid => Id <= SectorType.Maximum; public readonly long StartOffset { get { ThrowIfInvalid(); - return (Index + 1) * Length; + return (Id + 1) * Length; } } @@ -26,9 +28,15 @@ public readonly long EndOffset get { ThrowIfInvalid(); - return (Index + 2) * Length; + return (Id + 2) * Length; } } - public override readonly string ToString() => $"{Index}"; + readonly void ThrowIfInvalid() + { + if (!IsValid) + throw new InvalidOperationException($"Invalid sector index: {Id}"); + } + + public override readonly string ToString() => $"{Id}"; } From 6c13bec8230f1d73a4bbd64ac0d522af90011ac8 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 16 Oct 2024 09:41:18 +1300 Subject: [PATCH 015/134] Read mini FAT sectors, chains and streams --- OpenMcdf3.Benchmarks/InMemory.cs | 2 +- OpenMcdf3.Tests/CfbStreamTests.cs | 5 +- OpenMcdf3/DirectoryEntry.cs | 1 + OpenMcdf3/{CfbStream.cs => FatStream.cs} | 4 +- OpenMcdf3/IOContext.cs | 1 + OpenMcdf3/MiniFatSectorChainEnumerator.cs | 85 ++++++++++++++++++++++ OpenMcdf3/MiniFatSectorEnumerator.cs | 81 +++++++++++++++++++++ OpenMcdf3/MiniFatStream.cs | 89 +++++++++++++++++++++++ OpenMcdf3/MiniSector.cs | 38 ++++++++++ OpenMcdf3/RootStorage.cs | 16 ++-- OpenMcdf3/Sector.cs | 2 - OpenMcdf3/Storage.cs | 7 +- 12 files changed, 314 insertions(+), 17 deletions(-) rename OpenMcdf3/{CfbStream.cs => FatStream.cs} (95%) create mode 100644 OpenMcdf3/MiniFatSectorChainEnumerator.cs create mode 100644 OpenMcdf3/MiniFatSectorEnumerator.cs create mode 100644 OpenMcdf3/MiniFatStream.cs create mode 100644 OpenMcdf3/MiniSector.cs diff --git a/OpenMcdf3.Benchmarks/InMemory.cs b/OpenMcdf3.Benchmarks/InMemory.cs index c33a02f1..96bd180c 100644 --- a/OpenMcdf3.Benchmarks/InMemory.cs +++ b/OpenMcdf3.Benchmarks/InMemory.cs @@ -57,7 +57,7 @@ public void Test() _stream.Seek(0L, SeekOrigin.Begin); // using var compoundFile = RootStorage.Open(_stream); - using CfbStream cfStream = compoundFile.OpenStream(streamName + 0); + using Stream cfStream = compoundFile.OpenStream(streamName + 0); long streamSize = cfStream.Length; long position = 0L; while (true) diff --git a/OpenMcdf3.Tests/CfbStreamTests.cs b/OpenMcdf3.Tests/CfbStreamTests.cs index 1035ac00..ff40b79f 100644 --- a/OpenMcdf3.Tests/CfbStreamTests.cs +++ b/OpenMcdf3.Tests/CfbStreamTests.cs @@ -9,7 +9,7 @@ public sealed class CfbStreamTests public void Read(string fileName, string streamName, long length) { using var rootStorage = RootStorage.OpenRead(fileName); - using CfbStream stream = rootStorage.OpenStream(streamName); + using Stream stream = rootStorage.OpenStream(streamName); Assert.AreEqual(length, stream.Length); using MemoryStream memoryStream = new(); @@ -25,10 +25,9 @@ public void ReadAllStreams(string fileName) using var rootStorage = RootStorage.OpenRead(fileName); foreach (EntryInfo entryInfo in rootStorage.EnumerateEntries(StorageType.Stream)) { - using CfbStream stream = rootStorage.OpenStream(entryInfo.Name); + using Stream stream = rootStorage.OpenStream(entryInfo.Name); using MemoryStream memoryStream = new(); stream.CopyTo(memoryStream); - //Assert.AreEqual(entryInfo.Length, memoryStream.Length); } } } diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index 127bb208..d28b07a2 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -87,6 +87,7 @@ public DateTime ModifiedTime } } + // Also the first sector of the mini-stream for the root storage object public uint StartSectorLocation { get; set; } public long StreamLength { get; set; } diff --git a/OpenMcdf3/CfbStream.cs b/OpenMcdf3/FatStream.cs similarity index 95% rename from OpenMcdf3/CfbStream.cs rename to OpenMcdf3/FatStream.cs index 768533d6..702ff983 100644 --- a/OpenMcdf3/CfbStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -1,13 +1,13 @@ namespace OpenMcdf3; -public class CfbStream : Stream +public class FatStream : Stream { readonly IOContext ioContext; readonly FatSectorChainEnumerator chain; long length; long position; - internal CfbStream(IOContext ioContext, DirectoryEntry directoryEntry) + internal FatStream(IOContext ioContext, DirectoryEntry directoryEntry) { this.ioContext = ioContext; DirectoryEntry = directoryEntry; diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index b59774f3..9be8a72b 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -5,6 +5,7 @@ internal sealed class IOContext : IDisposable public Header Header { get; } public McdfBinaryReader Reader { get; } public McdfBinaryWriter? Writer { get; } + public DirectoryEntry RootEntry { get; set; } public IOContext(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer, bool leaveOpen = false) { diff --git a/OpenMcdf3/MiniFatSectorChainEnumerator.cs b/OpenMcdf3/MiniFatSectorChainEnumerator.cs new file mode 100644 index 00000000..d406e1bc --- /dev/null +++ b/OpenMcdf3/MiniFatSectorChainEnumerator.cs @@ -0,0 +1,85 @@ +using System.Collections; + +namespace OpenMcdf3; + +internal sealed class MiniFatSectorChainEnumerator : IEnumerator +{ + private readonly MiniFatSectorEnumerator miniFatEnumerator; + private readonly uint startId; + private bool start = true; + private MiniSector current = MiniSector.EndOfChain; + + public MiniFatSectorChainEnumerator(IOContext ioContext, uint startSectorId) + { + if (startSectorId is SectorType.EndOfChain) + throw new ArgumentException("Invalid start sector ID", nameof(startSectorId)); + this.startId = startSectorId; + miniFatEnumerator = new(ioContext); + } + + public uint Index { get; private set; } = uint.MaxValue; + + public MiniSector Current + { + get + { + if (current.IsEndOfChain) + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + return current; + } + } + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + if (start) + { + current = new(startId); + Index = 0; + start = false; + } + else if (!current.IsEndOfChain) + { + uint sectorId = miniFatEnumerator.GetNextMiniFatSectorId(current.Id); + current = new(sectorId); + Index++; + } + + if (current.IsEndOfChain) + { + current = MiniSector.EndOfChain; + Index = uint.MaxValue; + return false; + } + + return true; + } + + public bool MoveTo(uint index) + { + if (index < Index) + Reset(); + + while (start || Index < index) + { + if (!MoveNext()) + return false; + } + + return true; + } + + public void Reset() + { + start = true; + miniFatEnumerator.Reset(); + current = MiniSector.EndOfChain; + Index = uint.MaxValue; + } + + public void Dispose() + { + miniFatEnumerator.Dispose(); + } +} diff --git a/OpenMcdf3/MiniFatSectorEnumerator.cs b/OpenMcdf3/MiniFatSectorEnumerator.cs new file mode 100644 index 00000000..9a516cbc --- /dev/null +++ b/OpenMcdf3/MiniFatSectorEnumerator.cs @@ -0,0 +1,81 @@ +using System.Collections; + +namespace OpenMcdf3; + +internal sealed class MiniFatSectorEnumerator : IEnumerator +{ + private readonly IOContext ioContext; + private readonly FatSectorChainEnumerator miniFatChain; + bool start = true; + MiniSector current = MiniSector.EndOfChain; + + public MiniFatSectorEnumerator(IOContext ioContext) + { + this.ioContext = ioContext; + miniFatChain = new(ioContext, ioContext.Header.FirstMiniFatSectorID); + } + + public MiniSector Current + { + get + { + if (current.IsEndOfChain) + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + return current; + } + } + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + if (start) + { + current = new(ioContext.Header.FirstMiniFatSectorID); + start = false; + } + else if (!current.IsEndOfChain) + { + uint sectorId = GetNextMiniFatSectorId(current.Id); + current = new(sectorId); + } + + return !current.IsEndOfChain; + } + + public bool MoveTo(uint id) + { + if (id == SectorType.EndOfChain) + return false; + + while (start || current.Id < id) + { + if (!MoveNext()) + return false; + } + + return true; + } + + public void Reset() + { + current = MiniSector.EndOfChain; + } + + public uint GetNextMiniFatSectorId(uint id) + { + int elementLength = ioContext.Header.SectorSize / sizeof(uint); + uint sectorId = (uint)Math.DivRem(id, elementLength, out long sectorOffset); + if (!miniFatChain.MoveTo(sectorId)) + throw new ArgumentException("Invalid sector ID"); + + long position = miniFatChain.Current.StartOffset + sectorOffset * sizeof(uint); + ioContext.Reader.Seek(position); + uint nextId = ioContext.Reader.ReadUInt32(); + return nextId; + } + + public void Dispose() + { + } +} diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs new file mode 100644 index 00000000..a7a7f26e --- /dev/null +++ b/OpenMcdf3/MiniFatStream.cs @@ -0,0 +1,89 @@ +namespace OpenMcdf3; + +public class MiniFatStream : Stream +{ + readonly IOContext ioContext; + readonly MiniFatSectorChainEnumerator chain; + readonly FatStream fatStream; + long length; + long position; + + internal MiniFatStream(IOContext ioContext, DirectoryEntry directoryEntry) + { + this.ioContext = ioContext; + DirectoryEntry = directoryEntry; + length = directoryEntry.StreamLength; + chain = new(ioContext, directoryEntry.StartSectorLocation); + fatStream = new(ioContext, ioContext.RootEntry); + } + + internal DirectoryEntry DirectoryEntry { get; private set; } + + public override bool CanRead => true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override long Length => length; + + public override long Position + { + get => position; + set => position = value; + } + + protected override void Dispose(bool disposing) + { + chain.Dispose(); + fatStream.Dispose(); + + base.Dispose(disposing); + } + + public override void Flush() + { + fatStream.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + uint chainIndex = (uint)Math.DivRem(position, ioContext.Header.SectorSize, out long sectorOffset); + if (!chain.MoveTo(chainIndex)) + return 0; + + int maxCount = (int)Math.Min(Math.Max(length - position, 0), int.MaxValue); + int realCount = Math.Min(count, maxCount); + int readCount = 0; + do + { + MiniSector sector = chain.Current; + int remaining = realCount - readCount; + long readLength = Math.Min(remaining, MiniSector.Length - sectorOffset); + fatStream.Position = sector.StartOffset + sectorOffset; + int read = fatStream.Read(buffer, offset + readCount, (int)readLength); + if (read == 0) + return 0; + position += read; + readCount += read; + sectorOffset = 0; + if (readCount >= realCount) + return readCount; + } while (chain.MoveNext()); + + return readCount; + } + + public override long Seek(long offset, SeekOrigin origin) + { + position = offset; + return position; + } + + public override void SetLength(long value) + { + length = value; + } + + public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); +} diff --git a/OpenMcdf3/MiniSector.cs b/OpenMcdf3/MiniSector.cs new file mode 100644 index 00000000..bf236065 --- /dev/null +++ b/OpenMcdf3/MiniSector.cs @@ -0,0 +1,38 @@ +namespace OpenMcdf3; + +internal record struct MiniSector(uint Id) +{ + public const int Length = 64; + + public static readonly MiniSector EndOfChain = new(SectorType.EndOfChain); + + public readonly bool IsValid => Id <= SectorType.Maximum; + + public readonly bool IsEndOfChain => Id is SectorType.EndOfChain or SectorType.Free; + + public readonly long StartOffset + { + get + { + ThrowIfInvalid(); + return Id * Length; + } + } + + public readonly long EndOffset + { + get + { + ThrowIfInvalid(); + return (Id + 1) * Length; + } + } + + readonly void ThrowIfInvalid() + { + if (!IsValid) + throw new InvalidOperationException($"Invalid sector index: {Id}"); + } + + public override readonly string ToString() => $"{Id}"; +} diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index d3a4cf4f..8ed3fe38 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -18,9 +18,11 @@ public static RootStorage Create(string fileName, Version version = Version.V3) Header header = new(version); McdfBinaryReader reader = new(stream); McdfBinaryWriter writer = new(stream); - IOContext ioContext = new(header, reader, writer); - DirectoryEntry directoryEntry = new(); - return new RootStorage(ioContext, directoryEntry); + IOContext ioContext = new(header, reader, writer) + { + RootEntry = new() + }; + return new RootStorage(ioContext); } public static RootStorage Open(string fileName, FileMode mode) @@ -41,12 +43,12 @@ public static RootStorage Open(Stream stream, bool leaveOpen = false) McdfBinaryWriter? writer = stream.CanWrite ? new(stream) : null; Header header = reader.ReadHeader(); IOContext ioContext = new(header, reader, writer, leaveOpen); - DirectoryEntry rootDirectoryEntry = ioContext.EnumerateDirectoryEntries().First(); - return new RootStorage(ioContext, rootDirectoryEntry); + ioContext.RootEntry = ioContext.EnumerateDirectoryEntries().First(); + return new RootStorage(ioContext); } - RootStorage(IOContext ioContext, DirectoryEntry rootDirectoryEntry) - : base(ioContext, rootDirectoryEntry) + RootStorage(IOContext ioContext) + : base(ioContext, ioContext.RootEntry) { } diff --git a/OpenMcdf3/Sector.cs b/OpenMcdf3/Sector.cs index 10c956f9..6a22ab8b 100644 --- a/OpenMcdf3/Sector.cs +++ b/OpenMcdf3/Sector.cs @@ -2,8 +2,6 @@ internal record struct Sector(uint Id, int Length) { - public const int MiniSectorSize = 64; - public static readonly Sector EndOfChain = new(SectorType.EndOfChain, 0); /// diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index cee6f926..b00bc73e 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -37,10 +37,13 @@ public Storage OpenStorage(string name) return new Storage(IOContext, entry); } - public CfbStream OpenStream(string name) + public Stream OpenStream(string name) { DirectoryEntry? entry = EnumerateDirectoryEntries(StorageType.Stream) .FirstOrDefault(entry => entry.Name == name) ?? throw new FileNotFoundException("Stream not found", name); - return new CfbStream(IOContext, entry); + if (entry.StreamLength < Header.MiniStreamCutoffSize) + return new MiniFatStream(IOContext, entry); + else + return new FatStream(IOContext, entry); } } From 9a8738b854f1a29b6a8e3e60e484ff1f8e27bf2a Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 16 Oct 2024 13:40:29 +1300 Subject: [PATCH 016/134] Improve stream/storage validation --- OpenMcdf3/EntryInfo.cs | 2 +- OpenMcdf3/FatSectorChainEnumerator.cs | 2 - OpenMcdf3/FatSectorEnumerator.cs | 2 - OpenMcdf3/FatStream.cs | 75 ++++++++++++++++++----- OpenMcdf3/IOContext.cs | 29 +++++++-- OpenMcdf3/MiniFatSectorChainEnumerator.cs | 2 - OpenMcdf3/MiniFatStream.cs | 72 ++++++++++++++++++---- OpenMcdf3/RootStorage.cs | 24 ++------ OpenMcdf3/Storage.cs | 36 ++++++++--- OpenMcdf3/ThrowHelper.cs | 16 +++++ 10 files changed, 191 insertions(+), 69 deletions(-) create mode 100644 OpenMcdf3/ThrowHelper.cs diff --git a/OpenMcdf3/EntryInfo.cs b/OpenMcdf3/EntryInfo.cs index 8904164a..c22a137b 100644 --- a/OpenMcdf3/EntryInfo.cs +++ b/OpenMcdf3/EntryInfo.cs @@ -2,7 +2,7 @@ public class EntryInfo { - public string Name { get; internal set; } + public string Name { get; internal set; } = string.Empty; public override string ToString() => Name; } diff --git a/OpenMcdf3/FatSectorChainEnumerator.cs b/OpenMcdf3/FatSectorChainEnumerator.cs index bad0f6e5..371c0016 100644 --- a/OpenMcdf3/FatSectorChainEnumerator.cs +++ b/OpenMcdf3/FatSectorChainEnumerator.cs @@ -13,8 +13,6 @@ internal sealed class FatSectorChainEnumerator : IEnumerator public FatSectorChainEnumerator(IOContext ioContext, uint startSectorId) { this.ioContext = ioContext; - if (startSectorId is SectorType.EndOfChain) - throw new ArgumentException("Invalid start sector ID", nameof(startSectorId)); this.startId = startSectorId; fatEnumerator = new(ioContext); } diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index 97430152..72258cf0 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -1,6 +1,4 @@ using System.Collections; -using System.Collections.Generic; -using System.Diagnostics.SymbolStore; namespace OpenMcdf3; diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index 702ff983..b47b4754 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -1,11 +1,12 @@ namespace OpenMcdf3; -public class FatStream : Stream +internal class FatStream : Stream { readonly IOContext ioContext; readonly FatSectorChainEnumerator chain; - long length; + readonly long length; long position; + bool disposed; internal FatStream(IOContext ioContext, DirectoryEntry directoryEntry) { @@ -28,41 +29,61 @@ internal FatStream(IOContext ioContext, DirectoryEntry directoryEntry) public override long Position { get => position; - set => position = value; + set => Seek(value, SeekOrigin.Begin); } protected override void Dispose(bool disposing) { - chain.Dispose(); + if (!disposed) + { + chain.Dispose(); + disposed = true; + } base.Dispose(disposing); } - public override void Flush() - { - //rootStorage.Flush(); - } + public override void Flush() => this.ThrowIfDisposed(disposed); public override int Read(byte[] buffer, int offset, int count) { + if (buffer is null) + throw new ArgumentNullException(nameof(buffer)); + + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), "Offset must be a non-negative number"); + + if ((uint)count > buffer.Length - offset) + throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection"); + + this.ThrowIfDisposed(disposed); + + if (count == 0) + return 0; + uint chainIndex = (uint)Math.DivRem(position, ioContext.Header.SectorSize, out long sectorOffset); if (!chain.MoveTo(chainIndex)) return 0; int maxCount = (int)Math.Min(Math.Max(length - position, 0), int.MaxValue); + if (maxCount == 0) + return 0; + int realCount = Math.Min(count, maxCount); int readCount = 0; - int remaining = realCount; do { Sector sector = chain.Current; + int remaining = realCount - readCount; long readLength = Math.Min(remaining, sector.Length - sectorOffset); ioContext.Reader.Seek(sector.StartOffset + sectorOffset); - int read = ioContext.Reader.Read(buffer, offset, (int)readLength); + int localOffset = offset + readCount; + int read = ioContext.Reader.Read(buffer, localOffset, (int)readLength); if (read == 0) - return 0; + return readCount; position += read; readCount += read; + sectorOffset = 0; if (readCount >= realCount) return readCount; } while (chain.MoveNext()); @@ -72,14 +93,36 @@ public override int Read(byte[] buffer, int offset, int count) public override long Seek(long offset, SeekOrigin origin) { - position = offset; + this.ThrowIfDisposed(disposed); + + switch (origin) + { + case SeekOrigin.Begin: + if (offset < 0) + throw new IOException("Seek before origin"); + position = offset; + break; + + case SeekOrigin.Current: + if (position + offset < 0) + throw new IOException("Seek before origin"); + position += offset; + break; + + case SeekOrigin.End: + if (Length - offset < 0) + throw new IOException("Seek before origin"); + position = Length - offset; + break; + + default: + throw new ArgumentException(nameof(origin), "Invalid seek origin"); + } + return position; } - public override void SetLength(long value) - { - length = value; - } + public override void SetLength(long value) => throw new NotSupportedException(); public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); } diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index 9be8a72b..e70f4f15 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -1,27 +1,48 @@ namespace OpenMcdf3; +enum IOContextFlags +{ + None = 0, + Create = 1, + LeaveOpen = 2 +} + internal sealed class IOContext : IDisposable { public Header Header { get; } + public McdfBinaryReader Reader { get; } + public McdfBinaryWriter? Writer { get; } - public DirectoryEntry RootEntry { get; set; } - public IOContext(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer, bool leaveOpen = false) + public DirectoryEntry RootEntry { get; } + + public bool IsDisposed { get; private set; } + + public IOContext(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer, IOContextFlags contextFlags = IOContextFlags.None) { Header = header; Reader = reader; Writer = writer; + RootEntry = contextFlags.HasFlag(IOContextFlags.Create) + ? new DirectoryEntry() + : EnumerateDirectoryEntries().First(); } public void Dispose() { - Reader.Dispose(); - Writer?.Dispose(); + if (!IsDisposed) + { + Reader.Dispose(); + Writer?.Dispose(); + IsDisposed = true; + } } public IEnumerable EnumerateDirectoryEntries() { + this.ThrowIfDisposed(IsDisposed); + using DirectoryEntryEnumerator directoryEntriesEnumerator = new(this); while (directoryEntriesEnumerator.MoveNext()) yield return directoryEntriesEnumerator.Current; diff --git a/OpenMcdf3/MiniFatSectorChainEnumerator.cs b/OpenMcdf3/MiniFatSectorChainEnumerator.cs index d406e1bc..b1296c16 100644 --- a/OpenMcdf3/MiniFatSectorChainEnumerator.cs +++ b/OpenMcdf3/MiniFatSectorChainEnumerator.cs @@ -11,8 +11,6 @@ internal sealed class MiniFatSectorChainEnumerator : IEnumerator public MiniFatSectorChainEnumerator(IOContext ioContext, uint startSectorId) { - if (startSectorId is SectorType.EndOfChain) - throw new ArgumentException("Invalid start sector ID", nameof(startSectorId)); this.startId = startSectorId; miniFatEnumerator = new(ioContext); } diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index a7a7f26e..ce081f8a 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -1,12 +1,13 @@ namespace OpenMcdf3; -public class MiniFatStream : Stream +internal class MiniFatStream : Stream { readonly IOContext ioContext; readonly MiniFatSectorChainEnumerator chain; readonly FatStream fatStream; - long length; + readonly long length; long position; + bool disposed; internal MiniFatStream(IOContext ioContext, DirectoryEntry directoryEntry) { @@ -30,40 +31,63 @@ internal MiniFatStream(IOContext ioContext, DirectoryEntry directoryEntry) public override long Position { get => position; - set => position = value; + set => Seek(value, SeekOrigin.Begin); } protected override void Dispose(bool disposing) { - chain.Dispose(); - fatStream.Dispose(); + if (!disposed) + { + chain.Dispose(); + fatStream.Dispose(); + disposed = true; + } base.Dispose(disposing); } public override void Flush() { + this.ThrowIfDisposed(disposed); fatStream.Flush(); } public override int Read(byte[] buffer, int offset, int count) { + if (buffer is null) + throw new ArgumentNullException(nameof(buffer)); + + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), "Offset must be a non-negative number"); + + if ((uint)count > buffer.Length - offset) + throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection"); + + this.ThrowIfDisposed(disposed); + + if (count == 0) + return 0; + uint chainIndex = (uint)Math.DivRem(position, ioContext.Header.SectorSize, out long sectorOffset); if (!chain.MoveTo(chainIndex)) return 0; int maxCount = (int)Math.Min(Math.Max(length - position, 0), int.MaxValue); + if (maxCount == 0) + return 0; + int realCount = Math.Min(count, maxCount); int readCount = 0; do { MiniSector sector = chain.Current; int remaining = realCount - readCount; - long readLength = Math.Min(remaining, MiniSector.Length - sectorOffset); + long readLength = Math.Min(remaining, buffer.Length); fatStream.Position = sector.StartOffset + sectorOffset; - int read = fatStream.Read(buffer, offset + readCount, (int)readLength); + int localOffset = offset + readCount; + int read = fatStream.Read(buffer, localOffset, (int)readLength); if (read == 0) - return 0; + return readCount; position += read; readCount += read; sectorOffset = 0; @@ -76,14 +100,36 @@ public override int Read(byte[] buffer, int offset, int count) public override long Seek(long offset, SeekOrigin origin) { - position = offset; + this.ThrowIfDisposed(disposed); + + switch (origin) + { + case SeekOrigin.Begin: + if (offset < 0) + throw new IOException("Seek before origin"); + position = offset; + break; + + case SeekOrigin.Current: + if (position + offset < 0) + throw new IOException("Seek before origin"); + position += offset; + break; + + case SeekOrigin.End: + if (Length - offset < 0) + throw new IOException("Seek before origin"); + position = Length - offset; + break; + + default: + throw new ArgumentException(nameof(origin), "Invalid seek origin"); + } + return position; } - public override void SetLength(long value) - { - length = value; - } + public override void SetLength(long value) => throw new NotSupportedException(); public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); } diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index 8ed3fe38..fd621eb0 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -1,6 +1,4 @@ -using System.Diagnostics; - -namespace OpenMcdf3; +namespace OpenMcdf3; public enum Version : ushort { @@ -10,18 +8,13 @@ public enum Version : ushort public sealed class RootStorage : Storage, IDisposable { - bool disposed; - public static RootStorage Create(string fileName, Version version = Version.V3) { FileStream stream = File.Create(fileName); Header header = new(version); McdfBinaryReader reader = new(stream); McdfBinaryWriter writer = new(stream); - IOContext ioContext = new(header, reader, writer) - { - RootEntry = new() - }; + IOContext ioContext = new(header, reader, writer, IOContextFlags.Create); return new RootStorage(ioContext); } @@ -42,8 +35,8 @@ public static RootStorage Open(Stream stream, bool leaveOpen = false) McdfBinaryReader reader = new(stream); McdfBinaryWriter? writer = stream.CanWrite ? new(stream) : null; Header header = reader.ReadHeader(); - IOContext ioContext = new(header, reader, writer, leaveOpen); - ioContext.RootEntry = ioContext.EnumerateDirectoryEntries().First(); + IOContextFlags contextFlags = leaveOpen ? IOContextFlags.LeaveOpen : IOContextFlags.None; + IOContext ioContext = new(header, reader, writer, contextFlags); return new RootStorage(ioContext); } @@ -52,12 +45,5 @@ public static RootStorage Open(Stream stream, bool leaveOpen = false) { } - public void Dispose() - { - if (disposed) - return; - - IOContext?.Dispose(); - disposed = true; - } + public void Dispose() => ioContext?.Dispose(); } diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index b00bc73e..aea953db 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -2,25 +2,37 @@ public class Storage { - internal IOContext IOContext { get; } + internal readonly IOContext ioContext; internal DirectoryEntry DirectoryEntry { get; } internal Storage(IOContext ioContext, DirectoryEntry directoryEntry) { - IOContext = ioContext; + this.ioContext = ioContext; DirectoryEntry = directoryEntry; } - public IEnumerable EnumerateEntries() => EnumerateDirectoryEntries() - .Select(e => e.ToEntryInfo()); + public IEnumerable EnumerateEntries() + { + this.ThrowIfDisposed(ioContext); - public IEnumerable EnumerateEntries(StorageType type) => EnumerateDirectoryEntries(type) - .Select(e => e.ToEntryInfo()); + return EnumerateDirectoryEntries() + .Select(e => e.ToEntryInfo()); + } + + public IEnumerable EnumerateEntries(StorageType type) + { + this.ThrowIfDisposed(ioContext); + + return EnumerateDirectoryEntries(type) + .Select(e => e.ToEntryInfo()); + } IEnumerable EnumerateDirectoryEntries() { - using DirectoryTreeEnumerator treeEnumerator = new(IOContext, DirectoryEntry); + this.ThrowIfDisposed(ioContext); + + using DirectoryTreeEnumerator treeEnumerator = new(ioContext, DirectoryEntry); while (treeEnumerator.MoveNext()) { yield return treeEnumerator.Current; @@ -32,18 +44,22 @@ IEnumerable EnumerateDirectoryEntries(StorageType type) => Enume public Storage OpenStorage(string name) { + this.ThrowIfDisposed(ioContext); + DirectoryEntry? entry = EnumerateDirectoryEntries(StorageType.Storage) .FirstOrDefault(entry => entry.Name == name) ?? throw new DirectoryNotFoundException($"Directory not found {name}"); - return new Storage(IOContext, entry); + return new Storage(ioContext, entry); } public Stream OpenStream(string name) { + this.ThrowIfDisposed(ioContext); + DirectoryEntry? entry = EnumerateDirectoryEntries(StorageType.Stream) .FirstOrDefault(entry => entry.Name == name) ?? throw new FileNotFoundException("Stream not found", name); if (entry.StreamLength < Header.MiniStreamCutoffSize) - return new MiniFatStream(IOContext, entry); + return new MiniFatStream(ioContext, entry); else - return new FatStream(IOContext, entry); + return new FatStream(ioContext, entry); } } diff --git a/OpenMcdf3/ThrowHelper.cs b/OpenMcdf3/ThrowHelper.cs new file mode 100644 index 00000000..2ee7af58 --- /dev/null +++ b/OpenMcdf3/ThrowHelper.cs @@ -0,0 +1,16 @@ +namespace OpenMcdf3; + +internal static class ThrowHelper +{ + public static void ThrowIfDisposed(this object instance, bool disposed) + { + if (disposed) + throw new ObjectDisposedException(instance.GetType().FullName); + } + + public static void ThrowIfDisposed(this object instance, IOContext context) + { + if (context.IsDisposed) + throw new InvalidOperationException("Root storage has been disposed"); + } +} From fd8f692fd0ea6b0811a8606bc83337658d3c415b Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 16 Oct 2024 14:42:53 +1300 Subject: [PATCH 017/134] Add stream unit tests --- OpenMcdf3.Tests/BinaryReaderTests.cs | 6 ++- OpenMcdf3.Tests/CfbStreamTests.cs | 33 -------------- OpenMcdf3.Tests/EntryInfoTests.cs | 5 ++- OpenMcdf3.Tests/OpenMcdf3.Tests.csproj | 58 ++++++++++++++++++++++++- OpenMcdf3.Tests/StorageTests.cs | 7 +-- OpenMcdf3.Tests/StreamAssert.cs | 27 ++++++++++++ OpenMcdf3.Tests/StreamTests.cs | 44 +++++++++++++++++++ OpenMcdf3.Tests/TestStream_v3_0.cfs | Bin 0 -> 1536 bytes OpenMcdf3.Tests/TestStream_v3_4095.cfs | Bin 0 -> 6144 bytes OpenMcdf3.Tests/TestStream_v3_4096.cfs | Bin 0 -> 5632 bytes OpenMcdf3.Tests/TestStream_v3_4097.cfs | Bin 0 -> 6144 bytes OpenMcdf3.Tests/TestStream_v3_511.cfs | Bin 0 -> 2560 bytes OpenMcdf3.Tests/TestStream_v3_512.cfs | Bin 0 -> 2560 bytes OpenMcdf3.Tests/TestStream_v3_513.cfs | Bin 0 -> 3072 bytes OpenMcdf3.Tests/TestStream_v3_63.cfs | Bin 0 -> 2560 bytes OpenMcdf3.Tests/TestStream_v3_64.cfs | Bin 0 -> 2560 bytes OpenMcdf3.Tests/TestStream_v3_65.cfs | Bin 0 -> 2560 bytes OpenMcdf3.Tests/TestStream_v4_0.cfs | Bin 0 -> 1536 bytes OpenMcdf3.Tests/TestStream_v4_4095.cfs | Bin 0 -> 6144 bytes OpenMcdf3.Tests/TestStream_v4_4096.cfs | Bin 0 -> 5632 bytes OpenMcdf3.Tests/TestStream_v4_4097.cfs | Bin 0 -> 6144 bytes OpenMcdf3.Tests/TestStream_v4_511.cfs | Bin 0 -> 2560 bytes OpenMcdf3.Tests/TestStream_v4_512.cfs | Bin 0 -> 2560 bytes OpenMcdf3.Tests/TestStream_v4_513.cfs | Bin 0 -> 3072 bytes OpenMcdf3.Tests/TestStream_v4_63.cfs | Bin 0 -> 2560 bytes OpenMcdf3.Tests/TestStream_v4_64.cfs | Bin 0 -> 2560 bytes OpenMcdf3.Tests/TestStream_v4_65.cfs | Bin 0 -> 2560 bytes OpenMcdf3.Tests/_Test.ppt | Bin 174592 -> 0 bytes OpenMcdf3.Tests/test.cfb | Bin 1058304 -> 0 bytes 29 files changed, 135 insertions(+), 45 deletions(-) delete mode 100644 OpenMcdf3.Tests/CfbStreamTests.cs create mode 100644 OpenMcdf3.Tests/StreamAssert.cs create mode 100644 OpenMcdf3.Tests/StreamTests.cs create mode 100644 OpenMcdf3.Tests/TestStream_v3_0.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v3_4095.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v3_4096.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v3_4097.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v3_511.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v3_512.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v3_513.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v3_63.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v3_64.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v3_65.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v4_0.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v4_4095.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v4_4096.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v4_4097.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v4_511.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v4_512.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v4_513.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v4_63.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v4_64.cfs create mode 100644 OpenMcdf3.Tests/TestStream_v4_65.cfs delete mode 100644 OpenMcdf3.Tests/_Test.ppt delete mode 100644 OpenMcdf3.Tests/test.cfb diff --git a/OpenMcdf3.Tests/BinaryReaderTests.cs b/OpenMcdf3.Tests/BinaryReaderTests.cs index 4e570624..46eff130 100644 --- a/OpenMcdf3.Tests/BinaryReaderTests.cs +++ b/OpenMcdf3.Tests/BinaryReaderTests.cs @@ -24,9 +24,11 @@ public void ReadFileTime() } [TestMethod] - public void ReadHeader() + [DataRow("TestStream_v3_0.cfs")] + [DataRow("TestStream_v4_0.cfs")] + public void ReadHeader(string fileName) { - using FileStream stream = File.OpenRead("_Test.ppt"); + using FileStream stream = File.OpenRead(fileName); using McdfBinaryReader reader = new(stream); Header header = reader.ReadHeader(); } diff --git a/OpenMcdf3.Tests/CfbStreamTests.cs b/OpenMcdf3.Tests/CfbStreamTests.cs deleted file mode 100644 index ff40b79f..00000000 --- a/OpenMcdf3.Tests/CfbStreamTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace OpenMcdf3.Tests; - -[TestClass] -public sealed class CfbStreamTests -{ - [TestMethod] - [DataRow("_Test.ppt", "Current User", 62)] - [DataRow("test.cfb", "MyStream0", 1048576)] - public void Read(string fileName, string streamName, long length) - { - using var rootStorage = RootStorage.OpenRead(fileName); - using Stream stream = rootStorage.OpenStream(streamName); - Assert.AreEqual(length, stream.Length); - - using MemoryStream memoryStream = new(); - stream.CopyTo(memoryStream); - Assert.AreEqual(length, memoryStream.Length); - } - - [TestMethod] - [DataRow("_Test.ppt")] - [DataRow("test.cfb")] - public void ReadAllStreams(string fileName) - { - using var rootStorage = RootStorage.OpenRead(fileName); - foreach (EntryInfo entryInfo in rootStorage.EnumerateEntries(StorageType.Stream)) - { - using Stream stream = rootStorage.OpenStream(entryInfo.Name); - using MemoryStream memoryStream = new(); - stream.CopyTo(memoryStream); - } - } -} diff --git a/OpenMcdf3.Tests/EntryInfoTests.cs b/OpenMcdf3.Tests/EntryInfoTests.cs index 290aec8e..60d6a320 100644 --- a/OpenMcdf3.Tests/EntryInfoTests.cs +++ b/OpenMcdf3.Tests/EntryInfoTests.cs @@ -4,8 +4,9 @@ public sealed class EntryInfoTests { [TestMethod] - [DataRow("_Test.ppt", 4)] - [DataRow("test.cfb", 1)] + [DataRow("MultipleStorage.cfs", 1)] + [DataRow("TestStream_v3_0.cfs", 1)] + [DataRow("TestStream_v4_0.cfs", 1)] public void EnumerateEntryInfos(string fileName, int count) { using var rootStorage = RootStorage.OpenRead(fileName); diff --git a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj index bad7483e..0a325a95 100644 --- a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj +++ b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj @@ -38,10 +38,64 @@ PreserveNewest - + PreserveNewest - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + PreserveNewest diff --git a/OpenMcdf3.Tests/StorageTests.cs b/OpenMcdf3.Tests/StorageTests.cs index 68695a5c..67e8c9d2 100644 --- a/OpenMcdf3.Tests/StorageTests.cs +++ b/OpenMcdf3.Tests/StorageTests.cs @@ -1,12 +1,9 @@ -using System.Diagnostics; - -namespace OpenMcdf3.Tests; +namespace OpenMcdf3.Tests; [TestClass] public sealed class StorageTests { [TestMethod] - [DataRow("_Test.ppt", 0)] [DataRow("MultipleStorage.cfs", 1)] [DataRow("MultipleStorage2.cfs", 1)] [DataRow("MultipleStorage3.cfs", 1)] @@ -15,8 +12,6 @@ public void Read(string fileName, long storageCount) { using var rootStorage = RootStorage.OpenRead(fileName); IEnumerable storageEntries = rootStorage.EnumerateEntries(StorageType.Storage); - //foreach (var entryInfo in storageEntries) - // Trace.WriteLine($"{entryInfo}"); Assert.AreEqual(storageCount, storageEntries.Count()); } } diff --git a/OpenMcdf3.Tests/StreamAssert.cs b/OpenMcdf3.Tests/StreamAssert.cs new file mode 100644 index 00000000..328cbe86 --- /dev/null +++ b/OpenMcdf3.Tests/StreamAssert.cs @@ -0,0 +1,27 @@ +namespace OpenMcdf3.Tests; + +internal static class StreamAssert +{ + public static void AreEqual(Stream expected, Stream actual, int bufferLength = 4096) + { + Assert.AreEqual(expected.Length, actual.Length); + + expected.Position = 0; + actual.Position = 0; + + byte[] expectedBuffer = new byte[bufferLength]; + byte[] actualBuffer = new byte[bufferLength]; + while (expected.Position < expected.Length) + { + int expectedRead = expected.Read(expectedBuffer, 0, expectedBuffer.Length); + int actualRead = actual.Read(actualBuffer, 0, actualBuffer.Length); + + if (expectedRead == bufferLength && actualRead == bufferLength) + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + else + CollectionAssert.AreEqual(expectedBuffer.Take(expectedRead).ToList(), actualBuffer.Take(actualRead).ToList()); + } + + Assert.AreEqual(expected.Position, actual.Position); + } +} diff --git a/OpenMcdf3.Tests/StreamTests.cs b/OpenMcdf3.Tests/StreamTests.cs new file mode 100644 index 00000000..01d1e369 --- /dev/null +++ b/OpenMcdf3.Tests/StreamTests.cs @@ -0,0 +1,44 @@ +namespace OpenMcdf3.Tests; + +[TestClass] +public sealed class StreamTests +{ + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 63)] + [DataRow(Version.V3, 64)] + [DataRow(Version.V3, 65)] + [DataRow(Version.V3, 511)] + [DataRow(Version.V3, 512)] + [DataRow(Version.V3, 513)] + [DataRow(Version.V3, 4095)] + [DataRow(Version.V3, 4096)] + [DataRow(Version.V3, 4097)] + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 63)] + [DataRow(Version.V4, 64)] + [DataRow(Version.V4, 65)] + [DataRow(Version.V4, 511)] + [DataRow(Version.V4, 512)] + [DataRow(Version.V4, 513)] + [DataRow(Version.V4, 4095)] + [DataRow(Version.V4, 4096)] + [DataRow(Version.V4, 4097)] + public void Read(Version version, int length) + { + string fileName = $"TestStream_v{(int)version}_{length}.cfs"; + using var rootStorage = RootStorage.OpenRead(fileName); + using Stream stream = rootStorage.OpenStream("TestStream"); + Assert.AreEqual(length, stream.Length); + + // Test files are filled with bytes equal to their position modulo 256 + using MemoryStream expectedStream = new(length); + for (int i = 0; i < length; i++) + expectedStream.WriteByte((byte)i); + + using MemoryStream actualStream = new(); + stream.CopyTo(actualStream); + + StreamAssert.AreEqual(expectedStream, actualStream); + } +} diff --git a/OpenMcdf3.Tests/TestStream_v3_0.cfs b/OpenMcdf3.Tests/TestStream_v3_0.cfs new file mode 100644 index 0000000000000000000000000000000000000000..c3ee5ef6ddebb0ee8bec31dcf1ce2ac4d219681b GIT binary patch literal 1536 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3aa1_1`3{Qv(TAs7vk2MUdXArt~Z4EaD< z!l1z5%8&=7ix?`AW0@dZjDeLA=3SKZfk!7Sy?~U2Fr+dR1MLk4+nvgg$dHRiC#IAb s0~6eSkUKC{V2U8yk10#K2+aS)+E2O-_;pZXKPW8zA{#Ku9#J6x0JpaHj{pDw literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v3_4095.cfs b/OpenMcdf3.Tests/TestStream_v3_4095.cfs new file mode 100644 index 0000000000000000000000000000000000000000..effaf33da4b905593f2ce7e00c9f7b23284978bb GIT binary patch literal 6144 zcmeI$XHXMC7zW@?LV(ai2@rbky|;h`dj;%@q6RCVfCU>W_TGEL-W7Yr-W9NS1$*yZ z$@y-aIF2*U#F0Phewin?*_&i@w|jTHUrDjJG`~PvMt%wz5fB9@BAP$c^HA1NeKsOQ zgfgvDa2!XgwE;yIf6*BrJib6fsHvqb($Uq^H!w638=IJ#Nz5%QrB>E9ws!Uoj!w=l zu5Rugo?hNQzJC4zfkD9`p<&?>kx|hxve>vvm8(>ZuU5TA&04kV)U8*)LBoW^q(+UK zG;P+rMax#nt=puuZP#9&+M#2o&Rx2urDt^Oo|%>1qi3()Ieq%}>pviO;Gn^ILxv6; zK4Rpk(PPGr8$V&hhTfSoDs?}@Ou3KNQVdJLF zTefc7zGLUE-GzJh?%RLh;Gx4ujvhOH;^e8K;*!&6&YnAe;o_ysSFT>We&c3o*{$1m z?%uoq;NhdkPo6$|{^I4U*Kgjwd;j6%r_W!?6&yQ$*8gn$v+JMT|JePXJ^$GApUr>R z{Ey9l+5Dfaf7tr(KUn`N^EZ6tZ3LhJLZCSxEueXwa-ds^#lLq3X#Vb3{ZI3GG{;AC ze!8Fs`d|QtfD|ARgE5$ZDVTu-%)tUIK?+u24K`p4c3=+<;0R9O3@+dbZr~0c;0a#f z4L;xte&7!Q5C}mK3?UE-VGs@x5D8Hb4KW~tSYTtC;}S>)UfJl2c#@87Cdt8>Q9mR^ z$WvVvbS$YIbnqaOh@51h-9%;Ea*{&QzU@%+T|)TZ+o%6S&94!T4&Vw3)$#vM`xVr* J@@M{}{jZe^({KO) literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v3_4096.cfs b/OpenMcdf3.Tests/TestStream_v3_4096.cfs new file mode 100644 index 0000000000000000000000000000000000000000..068b6bc5510de9c42745c5afcb2f1e2dd691c225 GIT binary patch literal 5632 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3BxCyXz^0F?j#AH;>x96&ZuXcP>g5MW?r zVrF4wW9Q)H;^yJy;};MV5*85^6PJ*bl9rK`lUGnwQdUt_Q`gYc($>+{(>E|QGBzQN=`{lOV7y6 z%FfBn%P%M_DlRE4E3c@ms;;T6t8Zv*YHn$5Ywzgn>h9_7>z^=j(&Q;qr%j(RbJpxR zbLY)puyE1hB}NRWEt>3V5)8;K(w{73CbJy-Yd-v@>aPZLKBS()NKXLNZ z=`&}~oxgDL(&Z~xuU)@!^VaPo;%Tz5np>)8{W=zkUDl z^Vjb`fB*d-_5W!6kEZ|8{6AX$jh6qT_1|dyKid8oZU2q7e@EN@qy3-J{@+OG|ANMJ zn1GlWh*^M`6^KD&KI}jY8rS&?#2|VU49^exoIo~EXcP>g5MW?r zVrF4wW9Q)H;^yJy;};MV5*85^6PJ*bl9rK`lUGnwQdUt_Q`gYc($>+{(>E|QGBzQN=`{lOV7y6 z%FfBn%P%M_DlRE4E3c@ms;;T6t8Zv*YHn$5Ywzgn>h9_7>z^=j(&Q;qr%j(RbJpxR zbLY)puyE1hB}NRWEt>3V5)8;K(w{73CbJy-Yd-v@>aPZLKBS()NKXLNZ z=`&}~oxgDL(&Z~xuU)@!^VaPo;%Tz5np>)8{W=zkUDl z^Vjb`fB*d-_5W!6kEZ|8{6AX$jh6qT_1|dyKid8oZU2q7e@EN@qy3-J{@;k`|H8&| zM(Gh50-!NHCLm@8Viq7~1!B;cA3G3p05NE6?=KL8=ut5ILLi7C9|%hr6c}6?@_=*^ zLnScQi3ef~tchm7y4DZ!p;IRE9)`Tw;uXs}W;hg4+*r2V8;x XgAq7>O)UWPKPmQ8%WjZ4l-Lgd)osjj literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v3_511.cfs b/OpenMcdf3.Tests/TestStream_v3_511.cfs new file mode 100644 index 0000000000000000000000000000000000000000..7f51115edc26d39f1f4d41dc11fe9dea5332b8ec GIT binary patch literal 2560 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3BxGmJ05z`z7#gT(&*|NkE(3}OSBqhJVy z00ScvGYcylI|nBhHxDl#zkr~Su!yLbxP+vXw2Z8ryn>>VvWlvjx`w8fwvMizzJZ~U zv5BdfxrL>bwT-Qvy@R8Zvx}>nyN9Qjw~w!%e?VYRa7buactm7WbWChqd_rPUa!P7i zdPZhec1~_yenDYTaY<=ec|~PabxmzueM4hYb4zPmdq-zicTaC$|AdK?CQq3EN?fQ+Iw{G9Ld++{(hmRgVdHU@6i<{9 literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v3_512.cfs b/OpenMcdf3.Tests/TestStream_v3_512.cfs new file mode 100644 index 0000000000000000000000000000000000000000..440560b20e37dc86d8af8ffdf89d1674b0dac0aa GIT binary patch literal 2560 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3BxGmJ05z`z7#gT(&*|NkE(3}OSBqhJVy z00ScvGYcylI|nBhHxDl#zkr~Su!yLbxP+vXw2Z8ryn>>VvWlvjx`w8fwvMizzJZ~U zv5BdfxrL>bwT-Qvy@R8Zvx}>nyN9Qjw~w!%e?VYRa7buactm7WbWChqd_rPUa!P7i zdPZhec1~_yenDYTaY<=ec|~PabxmzueM4hYb4zPmdq-zicTaC$|AdK?CQq3EN?fQ+Iw{G9Ld++{(hmRgVdHU@6ifWQ72z%m$U?pmLoBh*^OcROYh-@o0J&{%IkI zAs+}!7!(*>8S;R15kn;~=7|Sl46KZ>Py(eTq5&w;0YL~uDnl{Q-e9oZsSJq>xey5= iNihZ{xcwk^5UCB4=!sU0Um?u@B-xMONRnhJu^#}v$IS`= literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v3_513.cfs b/OpenMcdf3.Tests/TestStream_v3_513.cfs new file mode 100644 index 0000000000000000000000000000000000000000..8c1f999a50206dd0836c92ae94b5fcbd3412ea57 GIT binary patch literal 3072 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3Bx3yd$o093)i022H6|NnoGFcT01nWJC` zg#ZI16Eh1d8#@Ol7dHqnoxOvjle3Gfo4bdnm$#3vpMOALP;f|SSa?KaRCG*iTzo=eQgTXa zT6#uiR(4KqUVcGgQE^FWS$RceRdr2mU427iQ*%peTYE=mS9ecuU;l)OlO|7@I&J!l znX_iknLBU(f`y9~FIl>5`HGdRRKIRx^4T8ox67L*}HH5frEz*A31vL z_=%IJPM`HPpYUcY(!?)`_4pFV&2 z`tAFVpTBSg=PuNuARZwC-0C+gXUjP6A literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v3_63.cfs b/OpenMcdf3.Tests/TestStream_v3_63.cfs new file mode 100644 index 0000000000000000000000000000000000000000..eb2839c0790d255b8640c8031ecf739b41e53bba GIT binary patch literal 2560 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3BxGmJ05z`z7#gT(&*|NkE(3}OSBqhJVy z00ScvGYcylI|nBhHxDl#zkr~Su!yLbxP+vXw2Z8ryn>>VvWlvjx`w8fwvMizzJZ~U zv5BdfxrL>bwT-PEDB4HC5Do!Q-u(;2AR2^6!N>}M(KsJj@r~}=AclM(EMZV!aAn8? z(nSoFB$Z}j46KYWGePw~vA_YUFoYqMp%`dyFxc)?hD3&3VvPW+6Jub4+YfRFv6}6P URY-(7nEy$!p9o`#RzivW0G+q--v9sr literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v3_64.cfs b/OpenMcdf3.Tests/TestStream_v3_64.cfs new file mode 100644 index 0000000000000000000000000000000000000000..313d56d5c4b101302277e3a360fd33d3ebf934bd GIT binary patch literal 2560 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3BxGmJ05z`z7#gT(&*|NkE(3}OSBqhJVy z00ScvGYcylI|nBhHxDl#zkr~Su!yLbxP+vXw2Z8ryn>>VvWlvjx`w8fwvMizzJZ~U zv5BdfxrL>bwT-QvJt*Es!O#r>P~QCu#2^}kN5RMnfzdc0S@DhT+aQK~AS_`}U~px~ z1JXqdl_V8rVhpT|Ff&2*Ke508sxX8hm7y4DZ!p;IRE9)`Tw;v?s}o~jg4+*r2eFzR Vh*e00I+*`Sv7ZQIiB>|1{Q!qt@*e;I literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v3_65.cfs b/OpenMcdf3.Tests/TestStream_v3_65.cfs new file mode 100644 index 0000000000000000000000000000000000000000..42c7962f567c7e71e13a75add032cb2b1744337b GIT binary patch literal 2560 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3BxGmJ05z`z7#gT(&*|NkE(3}OSBqhJVy z00ScvGYcylI|nBhHxDl#zkr~Su!yLbxP+vXw2Z8ryn>>VvWlvjx`w8fwvMizzJZ~U zv5BdfxrL>bwT-Qvy#pxXN5PN{0Z`uk3&bEAgh#>13ISO8J+gc~>RVU{1To|TVF`l* zgDXQGkS=1VB&jSDV_;>3nFUHq#DWH>!Vrd3hGL+-!C<>n84?+Ci8TVOPK<#GZa>H! Z#AW_TGEL-W7Yr-W9NS1$*yZ z$@y-aIF2*U#F0Phewin?*_&i@w|jTHUrDjJG`~PvMt%wz5fB9@BAP$c^HA1NeKsOQ zgfgvDa2!XgwE;yIf6*BrJib6fsHvqb($Uq^H!w638=IJ#Nz5%QrB>E9ws!Uoj!w=l zu5Rugo?hNQzJC4zfkD9`p<&?>kx|hxve>vvm8(>ZuU5TA&04kV)U8*)LBoW^q(+UK zG;P+rMax#nt=puuZP#9&+M#2o&Rx2urDt^Oo|%>1qi3()Ieq%}>pviO;Gn^ILxv6; zK4Rpk(PPGr8$V&hhTfSoDs?}@Ou3KNQVdJLF zTefc7zGLUE-GzJh?%RLh;Gx4ujvhOH;^e8K;*!&6&YnAe;o_ysSFT>We&c3o*{$1m z?%uoq;NhdkPo6$|{^I4U*Kgjwd;j6%r_W!?6&yQ$*8gn$v+JMT|JePXJ^$GApUr>R z{Ey9l+5Dfaf7tr(KUn`N^EZ6tZ3LhJLZCSxEueXwa-ds^#lLq3X#Vb3{ZI3GG{;AC ze!8Fs`d|QtfD|ARgE5$ZDVTu-%)tUIK?+u24K`p4c3=+<;0R9O3@+dbZr~0c;0a#f z4L;xte&7!Q5C}mK3?UE-VGs@x5D8Hb4KW~tSYTtC;}S>)UfJl2c#@87Cdt8>Q9mR^ z$WvVvbS$YIbnqaOh@51h-9%;Ea*{&QzU@%+T|)TZ+o%6S&94!T4&Vw3)$#vM`xVr* J@@M{}{jZe^({KO) literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v4_4096.cfs b/OpenMcdf3.Tests/TestStream_v4_4096.cfs new file mode 100644 index 0000000000000000000000000000000000000000..068b6bc5510de9c42745c5afcb2f1e2dd691c225 GIT binary patch literal 5632 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3BxCyXz^0F?j#AH;>x96&ZuXcP>g5MW?r zVrF4wW9Q)H;^yJy;};MV5*85^6PJ*bl9rK`lUGnwQdUt_Q`gYc($>+{(>E|QGBzQN=`{lOV7y6 z%FfBn%P%M_DlRE4E3c@ms;;T6t8Zv*YHn$5Ywzgn>h9_7>z^=j(&Q;qr%j(RbJpxR zbLY)puyE1hB}NRWEt>3V5)8;K(w{73CbJy-Yd-v@>aPZLKBS()NKXLNZ z=`&}~oxgDL(&Z~xuU)@!^VaPo;%Tz5np>)8{W=zkUDl z^Vjb`fB*d-_5W!6kEZ|8{6AX$jh6qT_1|dyKid8oZU2q7e@EN@qy3-J{@+OG|ANMJ zn1GlWh*^M`6^KD&KI}jY8rS&?#2|VU49^exoIo~EXcP>g5MW?r zVrF4wW9Q)H;^yJy;};MV5*85^6PJ*bl9rK`lUGnwQdUt_Q`gYc($>+{(>E|QGBzQN=`{lOV7y6 z%FfBn%P%M_DlRE4E3c@ms;;T6t8Zv*YHn$5Ywzgn>h9_7>z^=j(&Q;qr%j(RbJpxR zbLY)puyE1hB}NRWEt>3V5)8;K(w{73CbJy-Yd-v@>aPZLKBS()NKXLNZ z=`&}~oxgDL(&Z~xuU)@!^VaPo;%Tz5np>)8{W=zkUDl z^Vjb`fB*d-_5W!6kEZ|8{6AX$jh6qT_1|dyKid8oZU2q7e@EN@qy3-J{@;k`|H8&| zM(Gh50-!NHCLm@8Viq7~1!B;cA3G3p05NE6?=KL8=ut5ILLi7C9|%hr6c}6?@_=*^ zLnScQi3ef~tchm7y4DZ!p;IRE9)`Tw;uXs}W;hg4+*r2V8;x XgAq7>O)UWPKPmQ8%WjZ4l-Lgd)osjj literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v4_511.cfs b/OpenMcdf3.Tests/TestStream_v4_511.cfs new file mode 100644 index 0000000000000000000000000000000000000000..7f51115edc26d39f1f4d41dc11fe9dea5332b8ec GIT binary patch literal 2560 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3BxGmJ05z`z7#gT(&*|NkE(3}OSBqhJVy z00ScvGYcylI|nBhHxDl#zkr~Su!yLbxP+vXw2Z8ryn>>VvWlvjx`w8fwvMizzJZ~U zv5BdfxrL>bwT-Qvy@R8Zvx}>nyN9Qjw~w!%e?VYRa7buactm7WbWChqd_rPUa!P7i zdPZhec1~_yenDYTaY<=ec|~PabxmzueM4hYb4zPmdq-zicTaC$|AdK?CQq3EN?fQ+Iw{G9Ld++{(hmRgVdHU@6i<{9 literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v4_512.cfs b/OpenMcdf3.Tests/TestStream_v4_512.cfs new file mode 100644 index 0000000000000000000000000000000000000000..440560b20e37dc86d8af8ffdf89d1674b0dac0aa GIT binary patch literal 2560 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3BxGmJ05z`z7#gT(&*|NkE(3}OSBqhJVy z00ScvGYcylI|nBhHxDl#zkr~Su!yLbxP+vXw2Z8ryn>>VvWlvjx`w8fwvMizzJZ~U zv5BdfxrL>bwT-Qvy@R8Zvx}>nyN9Qjw~w!%e?VYRa7buactm7WbWChqd_rPUa!P7i zdPZhec1~_yenDYTaY<=ec|~PabxmzueM4hYb4zPmdq-zicTaC$|AdK?CQq3EN?fQ+Iw{G9Ld++{(hmRgVdHU@6ifWQ72z%m$U?pmLoBh*^OcROYh-@o0J&{%IkI zAs+}!7!(*>8S;R15kn;~=7|Sl46KZ>Py(eTq5&w;0YL~uDnl{Q-e9oZsSJq>xey5= iNihZ{xcwk^5UCB4=!sU0Um?u@B-xMONRnhJu^#}v$IS`= literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v4_513.cfs b/OpenMcdf3.Tests/TestStream_v4_513.cfs new file mode 100644 index 0000000000000000000000000000000000000000..8c1f999a50206dd0836c92ae94b5fcbd3412ea57 GIT binary patch literal 3072 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3Bx3yd$o093)i022H6|NnoGFcT01nWJC` zg#ZI16Eh1d8#@Ol7dHqnoxOvjle3Gfo4bdnm$#3vpMOALP;f|SSa?KaRCG*iTzo=eQgTXa zT6#uiR(4KqUVcGgQE^FWS$RceRdr2mU427iQ*%peTYE=mS9ecuU;l)OlO|7@I&J!l znX_iknLBU(f`y9~FIl>5`HGdRRKIRx^4T8ox67L*}HH5frEz*A31vL z_=%IJPM`HPpYUcY(!?)`_4pFV&2 z`tAFVpTBSg=PuNuARZwC-0C+gXUjP6A literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v4_63.cfs b/OpenMcdf3.Tests/TestStream_v4_63.cfs new file mode 100644 index 0000000000000000000000000000000000000000..eb2839c0790d255b8640c8031ecf739b41e53bba GIT binary patch literal 2560 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3BxGmJ05z`z7#gT(&*|NkE(3}OSBqhJVy z00ScvGYcylI|nBhHxDl#zkr~Su!yLbxP+vXw2Z8ryn>>VvWlvjx`w8fwvMizzJZ~U zv5BdfxrL>bwT-PEDB4HC5Do!Q-u(;2AR2^6!N>}M(KsJj@r~}=AclM(EMZV!aAn8? z(nSoFB$Z}j46KYWGePw~vA_YUFoYqMp%`dyFxc)?hD3&3VvPW+6Jub4+YfRFv6}6P URY-(7nEy$!p9o`#RzivW0G+q--v9sr literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v4_64.cfs b/OpenMcdf3.Tests/TestStream_v4_64.cfs new file mode 100644 index 0000000000000000000000000000000000000000..313d56d5c4b101302277e3a360fd33d3ebf934bd GIT binary patch literal 2560 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3BxGmJ05z`z7#gT(&*|NkE(3}OSBqhJVy z00ScvGYcylI|nBhHxDl#zkr~Su!yLbxP+vXw2Z8ryn>>VvWlvjx`w8fwvMizzJZ~U zv5BdfxrL>bwT-QvJt*Es!O#r>P~QCu#2^}kN5RMnfzdc0S@DhT+aQK~AS_`}U~px~ z1JXqdl_V8rVhpT|Ff&2*Ke508sxX8hm7y4DZ!p;IRE9)`Tw;v?s}o~jg4+*r2eFzR Vh*e00I+*`Sv7ZQIiB>|1{Q!qt@*e;I literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v4_65.cfs b/OpenMcdf3.Tests/TestStream_v4_65.cfs new file mode 100644 index 0000000000000000000000000000000000000000..42c7962f567c7e71e13a75add032cb2b1744337b GIT binary patch literal 2560 zcmca`Uhu)fjZzO8(10BSGsD0CoD6J8;*3BxGmJ05z`z7#gT(&*|NkE(3}OSBqhJVy z00ScvGYcylI|nBhHxDl#zkr~Su!yLbxP+vXw2Z8ryn>>VvWlvjx`w8fwvMizzJZ~U zv5BdfxrL>bwT-Qvy#pxXN5PN{0Z`uk3&bEAgh#>13ISO8J+gc~>RVU{1To|TVF`l* zgDXQGkS=1VB&jSDV_;>3nFUHq#DWH>!Vrd3hGL+-!C<>n84?+Ci8TVOPK<#GZa>H! Z#AfN}x~@OHV8Z}vjoXgGLw#8! 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 sTd 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 diff --git a/OpenMcdf3.Tests/test.cfb b/OpenMcdf3.Tests/test.cfb deleted file mode 100644 index 861c57cbcc2b12a208cdab4262854f362298ca98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1058304 zcmeI&RSa|2p`g*fVPN8SCu<|Hn4>fPn}6{>P952K%@BN9_8DL;SBhMi|ie zAAkJo)_?2=H1?+&s0OY7}8m@+~5voJl2oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ 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 R_vZhvAOAml Date: Wed, 16 Oct 2024 16:30:31 +1300 Subject: [PATCH 018/134] Refactoring and bug fixes --- OpenMcdf3/DirectoryEntryEnumerator.cs | 2 +- OpenMcdf3/FatSectorChainEnumerator.cs | 2 +- OpenMcdf3/FatSectorEnumerator.cs | 20 ++++++++++++-------- OpenMcdf3/Header.cs | 6 +++--- OpenMcdf3/McdfBinaryReader.cs | 6 +++--- OpenMcdf3/McdfBinaryWriter.cs | 6 +++--- OpenMcdf3/MiniFatSectorEnumerator.cs | 8 ++++++-- OpenMcdf3/MiniSector.cs | 2 +- OpenMcdf3/Sector.cs | 2 +- OpenMcdf3/Storage.cs | 9 +++++---- 10 files changed, 36 insertions(+), 27 deletions(-) diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index 655cbfd1..22982ea2 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -16,7 +16,7 @@ public DirectoryEntryEnumerator(IOContext ioContext) this.ioContext = ioContext; this.version = (Version)ioContext.Header.MajorVersion; this.entryCount = ioContext.Header.SectorSize / DirectoryEntry.Length; - this.chainEnumerator = new FatSectorChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorID); + this.chainEnumerator = new FatSectorChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); } public DirectoryEntry Current diff --git a/OpenMcdf3/FatSectorChainEnumerator.cs b/OpenMcdf3/FatSectorChainEnumerator.cs index 371c0016..b07493f2 100644 --- a/OpenMcdf3/FatSectorChainEnumerator.cs +++ b/OpenMcdf3/FatSectorChainEnumerator.cs @@ -72,8 +72,8 @@ public bool MoveTo(uint index) public void Reset() { - start = true; fatEnumerator.Reset(); + start = true; current = Sector.EndOfChain; Index = uint.MaxValue; } diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index 72258cf0..75a579ae 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -14,7 +14,7 @@ internal sealed class FatSectorEnumerator : IEnumerator public FatSectorEnumerator(IOContext ioContext) { this.ioContext = ioContext; - this.difatSectorId = ioContext.Header.FirstDifatSectorID; + this.difatSectorId = ioContext.Header.FirstDifatSectorId; } public Sector Current @@ -29,6 +29,10 @@ public Sector Current object IEnumerator.Current => Current; + public void Dispose() + { + } + public bool MoveNext() { if (start) @@ -89,14 +93,18 @@ public void Reset() { start = true; id = SectorType.EndOfChain; - difatSectorElementIndex = SectorType.EndOfChain; + difatSectorId = ioContext.Header.FirstDifatSectorId; + difatSectorElementIndex = 0; current = Sector.EndOfChain; } public uint GetNextFatSectorId(uint id) { - int elementLength = ioContext.Header.SectorSize / sizeof(uint); - uint sectorId = (uint)Math.DivRem(id, elementLength, out long sectorOffset); + if (id > SectorType.Maximum) + throw new ArgumentException("Invalid sector ID"); + + int elementCount = ioContext.Header.SectorSize / sizeof(uint); + uint sectorId = (uint)Math.DivRem(id, elementCount, out long sectorOffset); if (!MoveTo(sectorId)) throw new ArgumentException("Invalid sector ID"); @@ -105,8 +113,4 @@ public uint GetNextFatSectorId(uint id) uint nextId = ioContext.Reader.ReadUInt32(); return nextId; } - - public void Dispose() - { - } } diff --git a/OpenMcdf3/Header.cs b/OpenMcdf3/Header.cs index 95e19d94..ef3b0ce0 100644 --- a/OpenMcdf3/Header.cs +++ b/OpenMcdf3/Header.cs @@ -47,18 +47,18 @@ public ushort SectorShift public uint FatSectorCount { get; set; } - public uint FirstDirectorySectorID { get; set; } = SectorType.EndOfChain; + public uint FirstDirectorySectorId { get; set; } = SectorType.EndOfChain; public uint TransactionSignature { get; set; } ///

/// This integer field contains the starting sector number for the mini FAT /// - public uint FirstMiniFatSectorID { get; set; } = SectorType.EndOfChain; + public uint FirstMiniFatSectorId { get; set; } = SectorType.EndOfChain; public uint MiniFatSectorCount { get; set; } - public uint FirstDifatSectorID { get; set; } = SectorType.EndOfChain; + public uint FirstDifatSectorId { get; set; } = SectorType.EndOfChain; public uint DifatSectorCount { get; set; } diff --git a/OpenMcdf3/McdfBinaryReader.cs b/OpenMcdf3/McdfBinaryReader.cs index 441445bf..a6ccfed4 100644 --- a/OpenMcdf3/McdfBinaryReader.cs +++ b/OpenMcdf3/McdfBinaryReader.cs @@ -40,14 +40,14 @@ public Header ReadHeader() this.FillBuffer(6); header.DirectorySectorCount = ReadUInt32(); header.FatSectorCount = ReadUInt32(); - header.FirstDirectorySectorID = ReadUInt32(); + header.FirstDirectorySectorId = ReadUInt32(); this.FillBuffer(4); uint miniStreamCutoffSize = ReadUInt32(); if (miniStreamCutoffSize != Header.MiniStreamCutoffSize) throw new FormatException("Mini stream cutoff size must be 4096 byte"); - header.FirstMiniFatSectorID = ReadUInt32(); + header.FirstMiniFatSectorId = ReadUInt32(); header.MiniFatSectorCount = ReadUInt32(); - header.FirstDifatSectorID = ReadUInt32(); + header.FirstDifatSectorId = ReadUInt32(); header.DifatSectorCount = ReadUInt32(); for (int i = 0; i < Header.DifatLength; i++) diff --git a/OpenMcdf3/McdfBinaryWriter.cs b/OpenMcdf3/McdfBinaryWriter.cs index 067036f1..6ebc343d 100644 --- a/OpenMcdf3/McdfBinaryWriter.cs +++ b/OpenMcdf3/McdfBinaryWriter.cs @@ -38,12 +38,12 @@ public void Write(Header header) WriteBytes(Header.Unused); Write(header.DirectorySectorCount); Write(header.FatSectorCount); - Write(header.FirstDirectorySectorID); + Write(header.FirstDirectorySectorId); Write((uint)0); Write(Header.MiniStreamCutoffSize); - Write(header.FirstMiniFatSectorID); + Write(header.FirstMiniFatSectorId); Write(header.MiniFatSectorCount); - Write(header.FirstDifatSectorID); + Write(header.FirstDifatSectorId); Write(header.DifatSectorCount); } diff --git a/OpenMcdf3/MiniFatSectorEnumerator.cs b/OpenMcdf3/MiniFatSectorEnumerator.cs index 9a516cbc..d2c17156 100644 --- a/OpenMcdf3/MiniFatSectorEnumerator.cs +++ b/OpenMcdf3/MiniFatSectorEnumerator.cs @@ -12,7 +12,7 @@ internal sealed class MiniFatSectorEnumerator : IEnumerator public MiniFatSectorEnumerator(IOContext ioContext) { this.ioContext = ioContext; - miniFatChain = new(ioContext, ioContext.Header.FirstMiniFatSectorID); + miniFatChain = new(ioContext, ioContext.Header.FirstMiniFatSectorId); } public MiniSector Current @@ -31,7 +31,7 @@ public bool MoveNext() { if (start) { - current = new(ioContext.Header.FirstMiniFatSectorID); + current = new(ioContext.Header.FirstMiniFatSectorId); start = false; } else if (!current.IsEndOfChain) @@ -59,11 +59,15 @@ public bool MoveTo(uint id) public void Reset() { + start = true; current = MiniSector.EndOfChain; } public uint GetNextMiniFatSectorId(uint id) { + if (id > SectorType.Maximum) + throw new ArgumentException("Invalid sector ID"); + int elementLength = ioContext.Header.SectorSize / sizeof(uint); uint sectorId = (uint)Math.DivRem(id, elementLength, out long sectorOffset); if (!miniFatChain.MoveTo(sectorId)) diff --git a/OpenMcdf3/MiniSector.cs b/OpenMcdf3/MiniSector.cs index bf236065..0e5940f4 100644 --- a/OpenMcdf3/MiniSector.cs +++ b/OpenMcdf3/MiniSector.cs @@ -31,7 +31,7 @@ public readonly long EndOffset readonly void ThrowIfInvalid() { if (!IsValid) - throw new InvalidOperationException($"Invalid sector index: {Id}"); + throw new InvalidOperationException($"Invalid sector ID: {Id}"); } public override readonly string ToString() => $"{Id}"; diff --git a/OpenMcdf3/Sector.cs b/OpenMcdf3/Sector.cs index 6a22ab8b..8aa10cff 100644 --- a/OpenMcdf3/Sector.cs +++ b/OpenMcdf3/Sector.cs @@ -33,7 +33,7 @@ public readonly long EndOffset readonly void ThrowIfInvalid() { if (!IsValid) - throw new InvalidOperationException($"Invalid sector index: {Id}"); + throw new InvalidOperationException($"Invalid sector ID: {Id}"); } public override readonly string ToString() => $"{Id}"; diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index aea953db..044407b5 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -57,9 +57,10 @@ public Stream OpenStream(string name) DirectoryEntry? entry = EnumerateDirectoryEntries(StorageType.Stream) .FirstOrDefault(entry => entry.Name == name) ?? throw new FileNotFoundException("Stream not found", name); - if (entry.StreamLength < Header.MiniStreamCutoffSize) - return new MiniFatStream(ioContext, entry); - else - return new FatStream(ioContext, entry); + return entry.StreamLength switch + { + < Header.MiniStreamCutoffSize => new MiniFatStream(ioContext, entry), + _ => new FatStream(ioContext, entry) + }; } } From 64f3b07a3637ae03270e6a2e9b2897211d0a2837 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 17 Oct 2024 09:46:29 +1300 Subject: [PATCH 019/134] Refactor and add comments --- OpenMcdf3/DirectoryEntry.cs | 52 +++++++++++++++++++++++---- OpenMcdf3/DirectoryEntryEnumerator.cs | 9 +++-- OpenMcdf3/DirectoryTreeEnumerator.cs | 6 ++-- OpenMcdf3/FatSectorChainEnumerator.cs | 10 +++--- OpenMcdf3/FatSectorEnumerator.cs | 9 ++--- OpenMcdf3/FatStream.cs | 2 +- OpenMcdf3/McdfBinaryReader.cs | 10 +++--- OpenMcdf3/McdfBinaryWriter.cs | 8 ++--- OpenMcdf3/MiniFatStream.cs | 2 +- 9 files changed, 73 insertions(+), 35 deletions(-) diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index d28b07a2..47e95bfb 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -2,6 +2,9 @@ namespace OpenMcdf3; +/// +/// The storage type of a DirectoryEntry +/// public enum StorageType { Unallocated = 0, @@ -10,18 +13,27 @@ public enum StorageType Root = 5 } -enum Color +/// +/// Red-black node color +/// +enum NodeColor { Red = 0, Black = 1 } +/// +/// Stream ID constants for DirectoryEntry +/// internal static class StreamId { public const uint Maximum = 0xFFFFFFFA; public const uint NoStream = 0xFFFFFFFF; } +/// +/// Encapsulates data about a storage or stream +/// internal sealed class DirectoryEntry { internal const int Length = 128; @@ -51,18 +63,36 @@ public string Name public StorageType Type { get; set; } = StorageType.Unallocated; - public Color Color { get; set; } + public NodeColor Color { get; set; } - public uint LeftSiblingID { get; set; } + /// + /// Stream ID of the left sibling + /// + public uint LeftSiblingId { get; set; } - public uint RightSiblingID { get; set; } + /// + /// Stream ID of the right sibling + /// + public uint RightSiblingId { get; set; } - public uint ChildID { get; set; } + /// + /// Stream ID of the child + /// + public uint ChildId { get; set; } + /// + /// GUID for storage objects + /// public Guid CLSID { get; set; } + /// + /// User defined flags for storage objects + /// public uint StateBits { get; set; } + /// + /// The creation time of the storage object + /// public DateTime CreationTime { get => creationTime; @@ -75,6 +105,9 @@ public DateTime CreationTime } } + /// + /// The modified time of the storage object + /// public DateTime ModifiedTime { get => modifiedTime; @@ -87,9 +120,14 @@ public DateTime ModifiedTime } } - // Also the first sector of the mini-stream for the root storage object - public uint StartSectorLocation { get; set; } + /// + /// The starting sector location for a stream or the first sector of the mini-stream for the root storage object + /// + public uint StartSectorId { get; set; } + /// + /// The length of the stream + /// public long StreamLength { get; set; } public EntryInfo ToEntryInfo() => new() { Name = Name }; diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index 22982ea2..848e38c8 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -18,6 +18,10 @@ public DirectoryEntryEnumerator(IOContext ioContext) this.entryCount = ioContext.Header.SectorSize / DirectoryEntry.Length; this.chainEnumerator = new FatSectorChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); } + public void Dispose() + { + chainEnumerator.Dispose(); + } public DirectoryEntry Current { @@ -68,9 +72,4 @@ public void Reset() entryIndex = -1; current = default!; } - - public void Dispose() - { - chainEnumerator.Dispose(); - } } diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index ab1aed59..45a1809a 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -12,7 +12,7 @@ internal sealed class DirectoryTreeEnumerator : IEnumerator internal DirectoryTreeEnumerator(IOContext ioContext, DirectoryEntry root) { directoryEntryEnumerator = new(ioContext); - child = directoryEntryEnumerator.Get(root.ChildID); + child = directoryEntryEnumerator.Get(root.ChildId); PushLeft(child); } @@ -42,7 +42,7 @@ public bool MoveNext() } current = stack.Pop(); - DirectoryEntry? rightSibling = directoryEntryEnumerator.Get(Current.RightSiblingID); + DirectoryEntry? rightSibling = directoryEntryEnumerator.Get(Current.RightSiblingId); PushLeft(rightSibling); return true; } @@ -59,7 +59,7 @@ private void PushLeft(DirectoryEntry? node) while (node is not null) { stack.Push(node); - node = directoryEntryEnumerator.Get(node.LeftSiblingID); + node = directoryEntryEnumerator.Get(node.LeftSiblingId); } } } diff --git a/OpenMcdf3/FatSectorChainEnumerator.cs b/OpenMcdf3/FatSectorChainEnumerator.cs index b07493f2..46c0841c 100644 --- a/OpenMcdf3/FatSectorChainEnumerator.cs +++ b/OpenMcdf3/FatSectorChainEnumerator.cs @@ -17,6 +17,11 @@ public FatSectorChainEnumerator(IOContext ioContext, uint startSectorId) fatEnumerator = new(ioContext); } + public void Dispose() + { + fatEnumerator.Dispose(); + } + public uint Index { get; private set; } = uint.MaxValue; public Sector Current @@ -77,9 +82,4 @@ public void Reset() current = Sector.EndOfChain; Index = uint.MaxValue; } - - public void Dispose() - { - fatEnumerator.Dispose(); - } } diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index 75a579ae..4853a46a 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -17,6 +17,11 @@ public FatSectorEnumerator(IOContext ioContext) this.difatSectorId = ioContext.Header.FirstDifatSectorId; } + public void Dispose() + { + // IOContext is owned by a parent + } + public Sector Current { get @@ -29,10 +34,6 @@ public Sector Current object IEnumerator.Current => Current; - public void Dispose() - { - } - public bool MoveNext() { if (start) diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index b47b4754..a9e66e17 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -13,7 +13,7 @@ internal FatStream(IOContext ioContext, DirectoryEntry directoryEntry) this.ioContext = ioContext; DirectoryEntry = directoryEntry; length = directoryEntry.StreamLength; - chain = new(ioContext, directoryEntry.StartSectorLocation); + chain = new(ioContext, directoryEntry.StartSectorId); } internal DirectoryEntry DirectoryEntry { get; private set; } diff --git a/OpenMcdf3/McdfBinaryReader.cs b/OpenMcdf3/McdfBinaryReader.cs index a6ccfed4..3c789304 100644 --- a/OpenMcdf3/McdfBinaryReader.cs +++ b/OpenMcdf3/McdfBinaryReader.cs @@ -60,7 +60,7 @@ public Header ReadHeader() public StorageType ReadStorageType() => (StorageType)ReadByte(); - public Color ReadColor() => (Color)ReadByte(); + public NodeColor ReadColor() => (NodeColor)ReadByte(); public DirectoryEntry ReadDirectoryEntry(Version version) { @@ -73,14 +73,14 @@ public DirectoryEntry ReadDirectoryEntry(Version version) entry.Name = Encoding.Unicode.GetString(buffer, 0, nameLength); entry.Type = ReadStorageType(); entry.Color = ReadColor(); - entry.LeftSiblingID = ReadUInt32(); - entry.RightSiblingID = ReadUInt32(); - entry.ChildID = ReadUInt32(); + entry.LeftSiblingId = ReadUInt32(); + entry.RightSiblingId = ReadUInt32(); + entry.ChildId = ReadUInt32(); entry.CLSID = ReadGuid(); entry.StateBits = ReadUInt32(); entry.CreationTime = ReadFileTime(); entry.ModifiedTime = ReadFileTime(); - entry.StartSectorLocation = ReadUInt32(); + entry.StartSectorId = ReadUInt32(); if (version == Version.V3) { diff --git a/OpenMcdf3/McdfBinaryWriter.cs b/OpenMcdf3/McdfBinaryWriter.cs index 6ebc343d..b3300ddc 100644 --- a/OpenMcdf3/McdfBinaryWriter.cs +++ b/OpenMcdf3/McdfBinaryWriter.cs @@ -54,14 +54,14 @@ public void Write(DirectoryEntry entry) Write(buffer, 0, DirectoryEntry.NameFieldLength); Write((byte)entry.Type); Write((byte)entry.Color); - Write(entry.LeftSiblingID); - Write(entry.RightSiblingID); - Write(entry.ChildID); + Write(entry.LeftSiblingId); + Write(entry.RightSiblingId); + Write(entry.ChildId); Write(entry.CLSID); Write(entry.StateBits); Write(entry.CreationTime); Write(entry.ModifiedTime); - Write(entry.StartSectorLocation); + Write(entry.StartSectorId); Write(entry.StreamLength); } } diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index ce081f8a..af531d1f 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -14,7 +14,7 @@ internal MiniFatStream(IOContext ioContext, DirectoryEntry directoryEntry) this.ioContext = ioContext; DirectoryEntry = directoryEntry; length = directoryEntry.StreamLength; - chain = new(ioContext, directoryEntry.StartSectorLocation); + chain = new(ioContext, directoryEntry.StartSectorId); fatStream = new(ioContext, ioContext.RootEntry); } From 4c55bd410faf0f772419b41d976ba06659dbc299 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 17 Oct 2024 09:49:03 +1300 Subject: [PATCH 020/134] Improve header validation --- OpenMcdf3/Header.cs | 1 + OpenMcdf3/McdfBinaryReader.cs | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/OpenMcdf3/Header.cs b/OpenMcdf3/Header.cs index ef3b0ce0..57585061 100644 --- a/OpenMcdf3/Header.cs +++ b/OpenMcdf3/Header.cs @@ -4,6 +4,7 @@ internal sealed class Header { internal const int DifatLength = 109; + internal const ushort ExpectedMinorVersion = 0x003E; internal const ushort LittleEndian = 0xFFFE; internal const ushort SectorShiftV3 = 0x0009; internal const ushort SectorShiftV4 = 0x000C; diff --git a/OpenMcdf3/McdfBinaryReader.cs b/OpenMcdf3/McdfBinaryReader.cs index 3c789304..cfe47da0 100644 --- a/OpenMcdf3/McdfBinaryReader.cs +++ b/OpenMcdf3/McdfBinaryReader.cs @@ -26,10 +26,16 @@ public Header ReadHeader() Header header = new(); Read(buffer, 0, Header.Signature.Length); if (!buffer.Take(Header.Signature.Length).SequenceEqual(Header.Signature)) - throw new FormatException("Invalid signature"); + throw new FormatException("Invalid header signature"); header.CLSID = ReadGuid(); + if (header.CLSID != Guid.Empty) + throw new FormatException($"Invalid header CLSID: {header.CLSID}"); header.MinorVersion = ReadUInt16(); header.MajorVersion = ReadUInt16(); + if (header.MajorVersion is not (ushort)Version.V3 and not (ushort)Version.V4) + throw new FormatException($"Unsupported major version: {header.MajorVersion}"); + else if (header.MinorVersion is not Header.ExpectedMinorVersion) + throw new FormatException($"Unsupported minor version: {header.MinorVersion}"); ushort byteOrder = ReadUInt16(); if (byteOrder != Header.LittleEndian) throw new FormatException($"Unsupported byte order: {byteOrder:X4}. Only little-endian is supported ({Header.LittleEndian:X4})"); @@ -58,9 +64,21 @@ public Header ReadHeader() return header; } - public StorageType ReadStorageType() => (StorageType)ReadByte(); + public StorageType ReadStorageType() + { + var type = (StorageType)ReadByte(); + if (type is not StorageType.Storage and not StorageType.Stream and not StorageType.Root and not StorageType.Unallocated) + throw new FormatException($"Invalid storage type: {type}"); + return type; + } - public NodeColor ReadColor() => (NodeColor)ReadByte(); + public NodeColor ReadColor() + { + var color = (NodeColor)ReadByte(); + if (color is not NodeColor.Black and not NodeColor.Red) + throw new FormatException($"Invalid node color: {color}"); + return color; + } public DirectoryEntry ReadDirectoryEntry(Version version) { From 8ebe41a0a54a7df07b270a11f406363865a28754 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 17 Oct 2024 10:09:39 +1300 Subject: [PATCH 021/134] Additional comments --- OpenMcdf3/DirectoryEntry.cs | 29 +++++++++++++++------------ OpenMcdf3/DirectoryEntryEnumerator.cs | 13 +++++++++--- OpenMcdf3/DirectoryTreeEnumerator.cs | 9 ++++++--- OpenMcdf3/EntryInfo.cs | 3 +++ OpenMcdf3/FatSectorChainEnumerator.cs | 13 ++++++++++++ OpenMcdf3/FatSectorEnumerator.cs | 3 +++ OpenMcdf3/FatStream.cs | 3 +++ OpenMcdf3/Header.cs | 10 ++++++++- 8 files changed, 63 insertions(+), 20 deletions(-) diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index 47e95bfb..2159e8a6 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -3,7 +3,7 @@ namespace OpenMcdf3; /// -/// The storage type of a DirectoryEntry +/// The storage type of a . /// public enum StorageType { @@ -14,7 +14,7 @@ public enum StorageType } /// -/// Red-black node color +/// Red-black node color. /// enum NodeColor { @@ -23,7 +23,7 @@ enum NodeColor } /// -/// Stream ID constants for DirectoryEntry +/// Stream ID constants for . /// internal static class StreamId { @@ -32,7 +32,7 @@ internal static class StreamId } /// -/// Encapsulates data about a storage or stream +/// Encapsulates data about a or Stream. /// internal sealed class DirectoryEntry { @@ -61,37 +61,40 @@ public string Name } } + /// + /// The type of the storage object. + /// public StorageType Type { get; set; } = StorageType.Unallocated; public NodeColor Color { get; set; } /// - /// Stream ID of the left sibling + /// Stream ID of the left sibling. /// public uint LeftSiblingId { get; set; } /// - /// Stream ID of the right sibling + /// Stream ID of the right sibling. /// public uint RightSiblingId { get; set; } /// - /// Stream ID of the child + /// Stream ID of the child. /// public uint ChildId { get; set; } /// - /// GUID for storage objects + /// GUID for storage objects. /// public Guid CLSID { get; set; } /// - /// User defined flags for storage objects + /// User defined flags for storage objects. /// public uint StateBits { get; set; } /// - /// The creation time of the storage object + /// The creation time of the storage object. /// public DateTime CreationTime { @@ -106,7 +109,7 @@ public DateTime CreationTime } /// - /// The modified time of the storage object + /// The modified time of the storage object. /// public DateTime ModifiedTime { @@ -121,12 +124,12 @@ public DateTime ModifiedTime } /// - /// The starting sector location for a stream or the first sector of the mini-stream for the root storage object + /// The starting sector location for a stream or the first sector of the mini-stream for the root storage object. /// public uint StartSectorId { get; set; } /// - /// The length of the stream + /// The length of the stream. /// public long StreamLength { get; set; } diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index 848e38c8..c4e26bcc 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -2,6 +2,9 @@ namespace OpenMcdf3; +/// +/// Enumerates instances from a . +/// internal sealed class DirectoryEntryEnumerator : IEnumerator { private readonly IOContext ioContext; @@ -18,6 +21,7 @@ public DirectoryEntryEnumerator(IOContext ioContext) this.entryCount = ioContext.Header.SectorSize / DirectoryEntry.Length; this.chainEnumerator = new FatSectorChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); } + public void Dispose() { chainEnumerator.Dispose(); @@ -51,12 +55,15 @@ public bool MoveNext() return current.Type != StorageType.Unallocated; } - public DirectoryEntry? Get(uint id) + /// + /// Gets the for the specified stream ID. + /// + public DirectoryEntry? GetDictionaryEntry(uint streamId) { - if (id == StreamId.NoStream) + if (streamId == StreamId.NoStream) return null; - uint chainIndex = (uint)Math.DivRem(id, entryCount, out long entryIndex); + uint chainIndex = (uint)Math.DivRem(streamId, entryCount, out long entryIndex); if (!chainEnumerator.MoveTo(chainIndex)) throw new ArgumentException("Invalid directory entry ID"); diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index 45a1809a..cf21a605 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -2,6 +2,9 @@ namespace OpenMcdf3; +/// +/// Enumerates the children of a . +/// internal sealed class DirectoryTreeEnumerator : IEnumerator { private readonly DirectoryEntry? child; @@ -12,7 +15,7 @@ internal sealed class DirectoryTreeEnumerator : IEnumerator internal DirectoryTreeEnumerator(IOContext ioContext, DirectoryEntry root) { directoryEntryEnumerator = new(ioContext); - child = directoryEntryEnumerator.Get(root.ChildId); + child = directoryEntryEnumerator.GetDictionaryEntry(root.ChildId); PushLeft(child); } @@ -42,7 +45,7 @@ public bool MoveNext() } current = stack.Pop(); - DirectoryEntry? rightSibling = directoryEntryEnumerator.Get(Current.RightSiblingId); + DirectoryEntry? rightSibling = directoryEntryEnumerator.GetDictionaryEntry(Current.RightSiblingId); PushLeft(rightSibling); return true; } @@ -59,7 +62,7 @@ private void PushLeft(DirectoryEntry? node) while (node is not null) { stack.Push(node); - node = directoryEntryEnumerator.Get(node.LeftSiblingId); + node = directoryEntryEnumerator.GetDictionaryEntry(node.LeftSiblingId); } } } diff --git a/OpenMcdf3/EntryInfo.cs b/OpenMcdf3/EntryInfo.cs index c22a137b..b457ad3b 100644 --- a/OpenMcdf3/EntryInfo.cs +++ b/OpenMcdf3/EntryInfo.cs @@ -1,5 +1,8 @@ namespace OpenMcdf3; +/// +/// Encapsulates information about an entry in a . +/// public class EntryInfo { public string Name { get; internal set; } = string.Empty; diff --git a/OpenMcdf3/FatSectorChainEnumerator.cs b/OpenMcdf3/FatSectorChainEnumerator.cs index 46c0841c..4a214399 100644 --- a/OpenMcdf3/FatSectorChainEnumerator.cs +++ b/OpenMcdf3/FatSectorChainEnumerator.cs @@ -2,6 +2,9 @@ namespace OpenMcdf3; +/// +/// Enumerates the s in a FAT sector chain. +/// internal sealed class FatSectorChainEnumerator : IEnumerator { private readonly IOContext ioContext; @@ -22,6 +25,9 @@ public void Dispose() fatEnumerator.Dispose(); } + /// + /// The index within the FAT sector chain, or if the enumeration has not started. + /// public uint Index { get; private set; } = uint.MaxValue; public Sector Current @@ -36,6 +42,7 @@ public Sector Current object IEnumerator.Current => Current; + /// public bool MoveNext() { if (start) @@ -61,6 +68,11 @@ public bool MoveNext() return true; } + /// + /// Moves to the specified index within the FAT sector chain. + /// + /// + /// true if the enumerator was successfully advanced to the given index public bool MoveTo(uint index) { if (index < Index) @@ -75,6 +87,7 @@ public bool MoveTo(uint index) return true; } + /// public void Reset() { fatEnumerator.Reset(); diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index 4853a46a..e5701be5 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -2,6 +2,9 @@ namespace OpenMcdf3; +/// +/// Enumerates the FAT sectors of a compound file. +/// internal sealed class FatSectorEnumerator : IEnumerator { private readonly IOContext ioContext; diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index a9e66e17..44159c1d 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -1,5 +1,8 @@ namespace OpenMcdf3; +/// +/// Provides a for a stream object in a compound file./> +/// internal class FatStream : Stream { readonly IOContext ioContext; diff --git a/OpenMcdf3/Header.cs b/OpenMcdf3/Header.cs index 57585061..1bcbbf64 100644 --- a/OpenMcdf3/Header.cs +++ b/OpenMcdf3/Header.cs @@ -1,6 +1,8 @@ namespace OpenMcdf3; - +/// +/// The structure at the beginning of a compound file +/// internal sealed class Header { internal const int DifatLength = 109; @@ -11,12 +13,18 @@ internal sealed class Header internal const short MiniSectorShift = 6; internal const uint MiniStreamCutoffSize = 4096; + /// + /// Identification signature for the compound file structure + /// internal static readonly byte[] Signature = new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; internal static readonly byte[] Unused = new byte[6]; private ushort majorVersion; private ushort sectorShift = SectorShiftV3; + /// + /// Reserved and unused class ID + /// public Guid CLSID { get; set; } public ushort MinorVersion { get; set; } From 4d8600116d496135dbd76de1ccfb256b94b0e5e7 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 17 Oct 2024 10:16:09 +1300 Subject: [PATCH 022/134] Seal all the things --- OpenMcdf3/McdfBinaryReader.cs | 2 +- OpenMcdf3/McdfBinaryWriter.cs | 2 +- OpenMcdf3/MiniFatStream.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenMcdf3/McdfBinaryReader.cs b/OpenMcdf3/McdfBinaryReader.cs index cfe47da0..85549c6f 100644 --- a/OpenMcdf3/McdfBinaryReader.cs +++ b/OpenMcdf3/McdfBinaryReader.cs @@ -2,7 +2,7 @@ namespace OpenMcdf3; -internal class McdfBinaryReader : BinaryReader +internal sealed class McdfBinaryReader : BinaryReader { readonly byte[] buffer = new byte[DirectoryEntry.NameFieldLength]; diff --git a/OpenMcdf3/McdfBinaryWriter.cs b/OpenMcdf3/McdfBinaryWriter.cs index b3300ddc..d456f590 100644 --- a/OpenMcdf3/McdfBinaryWriter.cs +++ b/OpenMcdf3/McdfBinaryWriter.cs @@ -2,7 +2,7 @@ namespace OpenMcdf3; -internal class McdfBinaryWriter : BinaryWriter +internal sealed class McdfBinaryWriter : BinaryWriter { readonly byte[] buffer = new byte[DirectoryEntry.NameFieldLength]; diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index af531d1f..9854a907 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -1,6 +1,6 @@ namespace OpenMcdf3; -internal class MiniFatStream : Stream +internal sealed class MiniFatStream : Stream { readonly IOContext ioContext; readonly MiniFatSectorChainEnumerator chain; From 1a47cefbbd3167f1bb5783b6b349083cfe6e3a92 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 17 Oct 2024 10:35:49 +1300 Subject: [PATCH 023/134] Add comments and refactor --- OpenMcdf3.Tests/BinaryReaderTests.cs | 6 +-- ...McdfBinaryReader.cs => CfbBinaryReader.cs} | 9 ++-- ...McdfBinaryWriter.cs => CfbBinaryWriter.cs} | 7 ++- OpenMcdf3/DirectoryEntryEnumerator.cs | 5 ++ OpenMcdf3/DirectoryTreeEnumerator.cs | 5 ++ OpenMcdf3/FatSectorChainEnumerator.cs | 3 ++ OpenMcdf3/FatSectorEnumerator.cs | 9 +++- OpenMcdf3/FatStream.cs | 12 +++++ OpenMcdf3/Header.cs | 49 ++++++++++++++++--- OpenMcdf3/IOContext.cs | 12 +++-- OpenMcdf3/MiniFatSectorChainEnumerator.cs | 16 ++++++ OpenMcdf3/MiniFatSectorEnumerator.cs | 7 +++ OpenMcdf3/MiniFatStream.cs | 3 ++ OpenMcdf3/MiniSector.cs | 4 ++ OpenMcdf3/RootStorage.cs | 11 +++-- OpenMcdf3/Sector.cs | 5 ++ OpenMcdf3/SectorType.cs | 3 ++ OpenMcdf3/Storage.cs | 3 ++ OpenMcdf3/ThrowHelper.cs | 3 ++ 19 files changed, 150 insertions(+), 22 deletions(-) rename OpenMcdf3/{McdfBinaryReader.cs => CfbBinaryReader.cs} (95%) rename OpenMcdf3/{McdfBinaryWriter.cs => CfbBinaryWriter.cs} (91%) diff --git a/OpenMcdf3.Tests/BinaryReaderTests.cs b/OpenMcdf3.Tests/BinaryReaderTests.cs index 46eff130..e5bfa095 100644 --- a/OpenMcdf3.Tests/BinaryReaderTests.cs +++ b/OpenMcdf3.Tests/BinaryReaderTests.cs @@ -8,7 +8,7 @@ public void ReadGuid() { byte[] bytes = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 }; using MemoryStream stream = new(bytes); - using McdfBinaryReader reader = new(stream); + using CfbBinaryReader reader = new(stream); Guid guid = reader.ReadGuid(); Assert.AreEqual(new Guid(bytes), guid); } @@ -18,7 +18,7 @@ public void ReadFileTime() { byte[] bytes = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; using MemoryStream stream = new(bytes); - using McdfBinaryReader reader = new(stream); + using CfbBinaryReader reader = new(stream); DateTime actual = reader.ReadFileTime(); Assert.AreEqual(DirectoryEntry.ZeroFileTime, actual); } @@ -29,7 +29,7 @@ public void ReadFileTime() public void ReadHeader(string fileName) { using FileStream stream = File.OpenRead(fileName); - using McdfBinaryReader reader = new(stream); + using CfbBinaryReader reader = new(stream); Header header = reader.ReadHeader(); } } diff --git a/OpenMcdf3/McdfBinaryReader.cs b/OpenMcdf3/CfbBinaryReader.cs similarity index 95% rename from OpenMcdf3/McdfBinaryReader.cs rename to OpenMcdf3/CfbBinaryReader.cs index 85549c6f..7ddb13b2 100644 --- a/OpenMcdf3/McdfBinaryReader.cs +++ b/OpenMcdf3/CfbBinaryReader.cs @@ -2,11 +2,14 @@ namespace OpenMcdf3; -internal sealed class McdfBinaryReader : BinaryReader +/// +/// Reads CFB data types from a stream. +/// +internal sealed class CfbBinaryReader : BinaryReader { readonly byte[] buffer = new byte[DirectoryEntry.NameFieldLength]; - public McdfBinaryReader(Stream input) + public CfbBinaryReader(Stream input) : base(input, Encoding.Unicode, true) { } @@ -56,7 +59,7 @@ public Header ReadHeader() header.FirstDifatSectorId = ReadUInt32(); header.DifatSectorCount = ReadUInt32(); - for (int i = 0; i < Header.DifatLength; i++) + for (int i = 0; i < Header.DifatArrayLength; i++) { header.Difat[i] = ReadUInt32(); } diff --git a/OpenMcdf3/McdfBinaryWriter.cs b/OpenMcdf3/CfbBinaryWriter.cs similarity index 91% rename from OpenMcdf3/McdfBinaryWriter.cs rename to OpenMcdf3/CfbBinaryWriter.cs index d456f590..f8f08e0a 100644 --- a/OpenMcdf3/McdfBinaryWriter.cs +++ b/OpenMcdf3/CfbBinaryWriter.cs @@ -2,11 +2,14 @@ namespace OpenMcdf3; -internal sealed class McdfBinaryWriter : BinaryWriter +/// +/// Writes CFB data types to a stream. +/// +internal sealed class CfbBinaryWriter : BinaryWriter { readonly byte[] buffer = new byte[DirectoryEntry.NameFieldLength]; - public McdfBinaryWriter(Stream input) + public CfbBinaryWriter(Stream input) : base(input, Encoding.Unicode, true) { } diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index c4e26bcc..9fb62f42 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -22,11 +22,13 @@ public DirectoryEntryEnumerator(IOContext ioContext) this.chainEnumerator = new FatSectorChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); } + /// public void Dispose() { chainEnumerator.Dispose(); } + /// public DirectoryEntry Current { get @@ -37,8 +39,10 @@ public DirectoryEntry Current } } + /// object IEnumerator.Current => Current; + /// public bool MoveNext() { if (entryIndex == -1 || entryIndex >= entryCount) @@ -73,6 +77,7 @@ public bool MoveNext() return current; } + /// public void Reset() { chainEnumerator.Reset(); diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index cf21a605..42d786ad 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -19,11 +19,13 @@ internal DirectoryTreeEnumerator(IOContext ioContext, DirectoryEntry root) PushLeft(child); } + /// public void Dispose() { directoryEntryEnumerator.Dispose(); } + /// public DirectoryEntry Current { get @@ -34,8 +36,10 @@ public DirectoryEntry Current } } + /// object IEnumerator.Current => Current; + /// public bool MoveNext() { if (stack.Count == 0) @@ -50,6 +54,7 @@ public bool MoveNext() return true; } + /// public void Reset() { current = null; diff --git a/OpenMcdf3/FatSectorChainEnumerator.cs b/OpenMcdf3/FatSectorChainEnumerator.cs index 4a214399..f06757a1 100644 --- a/OpenMcdf3/FatSectorChainEnumerator.cs +++ b/OpenMcdf3/FatSectorChainEnumerator.cs @@ -20,6 +20,7 @@ public FatSectorChainEnumerator(IOContext ioContext, uint startSectorId) fatEnumerator = new(ioContext); } + /// public void Dispose() { fatEnumerator.Dispose(); @@ -30,6 +31,7 @@ public void Dispose() ///
public uint Index { get; private set; } = uint.MaxValue; + /// public Sector Current { get @@ -40,6 +42,7 @@ public Sector Current } } + /// object IEnumerator.Current => Current; /// diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index e5701be5..03cd07bb 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -20,11 +20,13 @@ public FatSectorEnumerator(IOContext ioContext) this.difatSectorId = ioContext.Header.FirstDifatSectorId; } + /// public void Dispose() { // IOContext is owned by a parent } + /// public Sector Current { get @@ -35,8 +37,10 @@ public Sector Current } } + /// object IEnumerator.Current => Current; + /// public bool MoveNext() { if (start) @@ -47,7 +51,7 @@ public bool MoveNext() id++; - if (id < ioContext.Header.FatSectorCount && id < Header.DifatLength) + if (id < ioContext.Header.FatSectorCount && id < Header.DifatArrayLength) { uint id = ioContext.Header.Difat[this.id]; current = new Sector(id, ioContext.Header.SectorSize); @@ -79,6 +83,7 @@ public bool MoveNext() return true; } + /// public bool MoveTo(uint sectorId) { if (sectorId < id) @@ -93,6 +98,7 @@ public bool MoveTo(uint sectorId) return true; } + /// public void Reset() { start = true; @@ -102,6 +108,7 @@ public void Reset() current = Sector.EndOfChain; } + /// public uint GetNextFatSectorId(uint id) { if (id > SectorType.Maximum) diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index 44159c1d..eb526f73 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -19,22 +19,29 @@ internal FatStream(IOContext ioContext, DirectoryEntry directoryEntry) chain = new(ioContext, directoryEntry.StartSectorId); } + /// internal DirectoryEntry DirectoryEntry { get; private set; } + /// public override bool CanRead => true; + /// public override bool CanSeek => true; + /// public override bool CanWrite => false; + /// public override long Length => length; + /// public override long Position { get => position; set => Seek(value, SeekOrigin.Begin); } + /// protected override void Dispose(bool disposing) { if (!disposed) @@ -46,8 +53,10 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + /// public override void Flush() => this.ThrowIfDisposed(disposed); + /// public override int Read(byte[] buffer, int offset, int count) { if (buffer is null) @@ -94,6 +103,7 @@ public override int Read(byte[] buffer, int offset, int count) return readCount; } + /// public override long Seek(long offset, SeekOrigin origin) { this.ThrowIfDisposed(disposed); @@ -125,7 +135,9 @@ public override long Seek(long offset, SeekOrigin origin) return position; } + /// public override void SetLength(long value) => throw new NotSupportedException(); + /// public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); } diff --git a/OpenMcdf3/Header.cs b/OpenMcdf3/Header.cs index 1bcbbf64..93febf4f 100644 --- a/OpenMcdf3/Header.cs +++ b/OpenMcdf3/Header.cs @@ -1,11 +1,11 @@ namespace OpenMcdf3; /// -/// The structure at the beginning of a compound file +/// The structure at the beginning of a compound file. /// internal sealed class Header { - internal const int DifatLength = 109; + internal const int DifatArrayLength = 109; internal const ushort ExpectedMinorVersion = 0x003E; internal const ushort LittleEndian = 0xFFFE; internal const ushort SectorShiftV3 = 0x0009; @@ -14,21 +14,28 @@ internal sealed class Header internal const uint MiniStreamCutoffSize = 4096; /// - /// Identification signature for the compound file structure + /// Identification signature for the compound file structure. /// internal static readonly byte[] Signature = new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; internal static readonly byte[] Unused = new byte[6]; + private ushort majorVersion; private ushort sectorShift = SectorShiftV3; /// - /// Reserved and unused class ID + /// Reserved and unused class ID. /// public Guid CLSID { get; set; } + /// + /// Version number for non-breaking changes. + /// public ushort MinorVersion { get; set; } + /// + /// Version number for breaking changes. + /// public ushort MajorVersion { get => majorVersion; set @@ -39,6 +46,9 @@ public ushort MajorVersion } } + /// + /// Specifies the sector size of the compound file. + /// public ushort SectorShift { get => sectorShift; set @@ -52,27 +62,54 @@ public ushort SectorShift } } + /// + /// The number of directory sectors in the compound file. + /// public uint DirectorySectorCount { get; set; } + /// + /// The number of FAT sectors in the compound file. + /// public uint FatSectorCount { get; set; } + /// + /// The starting sector ID of the directory stream. + /// public uint FirstDirectorySectorId { get; set; } = SectorType.EndOfChain; + /// + /// A sequence number that is incremented every time the compound file is saved by an implementation that supports file transactions. + /// public uint TransactionSignature { get; set; } /// - /// This integer field contains the starting sector number for the mini FAT + /// This integer field contains the starting sector ID of the mini FAT. /// public uint FirstMiniFatSectorId { get; set; } = SectorType.EndOfChain; + /// + /// The number of sectors in the mini FAT. + /// public uint MiniFatSectorCount { get; set; } + /// + /// The starting sector ID of the DIFAT. + /// public uint FirstDifatSectorId { get; set; } = SectorType.EndOfChain; + /// + /// The number of DIFACT sectors in the compound file. + /// public uint DifatSectorCount { get; set; } - public uint[] Difat { get; } = new uint[DifatLength]; + /// + /// An array of the first FAT sector IDs. + /// + public uint[] Difat { get; } = new uint[DifatArrayLength]; + /// + /// The size of a regular sector. + /// public int SectorSize => 1 << SectorShift; public Header(Version version = Version.V3) diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index e70f4f15..44b8de09 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -7,19 +7,22 @@ enum IOContextFlags LeaveOpen = 2 } +/// +/// Encapsulates the objects required to read and write data to and from a compound file. +/// internal sealed class IOContext : IDisposable { public Header Header { get; } - public McdfBinaryReader Reader { get; } + public CfbBinaryReader Reader { get; } - public McdfBinaryWriter? Writer { get; } + public CfbBinaryWriter? Writer { get; } public DirectoryEntry RootEntry { get; } public bool IsDisposed { get; private set; } - public IOContext(Header header, McdfBinaryReader reader, McdfBinaryWriter? writer, IOContextFlags contextFlags = IOContextFlags.None) + public IOContext(Header header, CfbBinaryReader reader, CfbBinaryWriter? writer, IOContextFlags contextFlags = IOContextFlags.None) { Header = header; Reader = reader; @@ -39,6 +42,9 @@ public void Dispose() } } + /// + /// Enumerates all the instances in the compound file. + /// public IEnumerable EnumerateDirectoryEntries() { this.ThrowIfDisposed(IsDisposed); diff --git a/OpenMcdf3/MiniFatSectorChainEnumerator.cs b/OpenMcdf3/MiniFatSectorChainEnumerator.cs index b1296c16..dc838d7d 100644 --- a/OpenMcdf3/MiniFatSectorChainEnumerator.cs +++ b/OpenMcdf3/MiniFatSectorChainEnumerator.cs @@ -2,6 +2,9 @@ namespace OpenMcdf3; +/// +/// Enumerates the s in a Mini FAT sector chain. +/// internal sealed class MiniFatSectorChainEnumerator : IEnumerator { private readonly MiniFatSectorEnumerator miniFatEnumerator; @@ -15,8 +18,12 @@ public MiniFatSectorChainEnumerator(IOContext ioContext, uint startSectorId) miniFatEnumerator = new(ioContext); } + /// + /// The index within the Mini FAT sector chain, or if the enumeration has not started. + /// public uint Index { get; private set; } = uint.MaxValue; + /// public MiniSector Current { get @@ -27,8 +34,10 @@ public MiniSector Current } } + /// object IEnumerator.Current => Current; + /// public bool MoveNext() { if (start) @@ -54,6 +63,11 @@ public bool MoveNext() return true; } + /// + /// Moves to the specified index within the mini FAT sector chain. + /// + /// + /// true if the enumerator was successfully advanced to the given index public bool MoveTo(uint index) { if (index < Index) @@ -68,6 +82,7 @@ public bool MoveTo(uint index) return true; } + /// public void Reset() { start = true; @@ -76,6 +91,7 @@ public void Reset() Index = uint.MaxValue; } + /// public void Dispose() { miniFatEnumerator.Dispose(); diff --git a/OpenMcdf3/MiniFatSectorEnumerator.cs b/OpenMcdf3/MiniFatSectorEnumerator.cs index d2c17156..277e9a38 100644 --- a/OpenMcdf3/MiniFatSectorEnumerator.cs +++ b/OpenMcdf3/MiniFatSectorEnumerator.cs @@ -2,6 +2,9 @@ namespace OpenMcdf3; +/// +/// Enumerates the s in a FAT sector chain. +/// internal sealed class MiniFatSectorEnumerator : IEnumerator { private readonly IOContext ioContext; @@ -15,6 +18,7 @@ public MiniFatSectorEnumerator(IOContext ioContext) miniFatChain = new(ioContext, ioContext.Header.FirstMiniFatSectorId); } + /// public MiniSector Current { get @@ -25,8 +29,10 @@ public MiniSector Current } } + /// object IEnumerator.Current => Current; + /// public bool MoveNext() { if (start) @@ -57,6 +63,7 @@ public bool MoveTo(uint id) return true; } + /// public void Reset() { start = true; diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index 9854a907..b7df0ac6 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -1,5 +1,8 @@ namespace OpenMcdf3; +/// +/// Provides a for reading a from the mini FAT stream. +/// internal sealed class MiniFatStream : Stream { readonly IOContext ioContext; diff --git a/OpenMcdf3/MiniSector.cs b/OpenMcdf3/MiniSector.cs index 0e5940f4..38331d93 100644 --- a/OpenMcdf3/MiniSector.cs +++ b/OpenMcdf3/MiniSector.cs @@ -1,5 +1,9 @@ namespace OpenMcdf3; +/// +/// Encapsulates information about a mini sector in a compound file. +/// +/// The ID of the mini sector internal record struct MiniSector(uint Id) { public const int Length = 64; diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index fd621eb0..3df2d7ff 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -6,14 +6,17 @@ public enum Version : ushort V4 = 4 } +/// +/// Encapsulates the root of a compound file. +/// public sealed class RootStorage : Storage, IDisposable { public static RootStorage Create(string fileName, Version version = Version.V3) { FileStream stream = File.Create(fileName); Header header = new(version); - McdfBinaryReader reader = new(stream); - McdfBinaryWriter writer = new(stream); + CfbBinaryReader reader = new(stream); + CfbBinaryWriter writer = new(stream); IOContext ioContext = new(header, reader, writer, IOContextFlags.Create); return new RootStorage(ioContext); } @@ -32,8 +35,8 @@ public static RootStorage OpenRead(string fileName) public static RootStorage Open(Stream stream, bool leaveOpen = false) { - McdfBinaryReader reader = new(stream); - McdfBinaryWriter? writer = stream.CanWrite ? new(stream) : null; + CfbBinaryReader reader = new(stream); + CfbBinaryWriter? writer = stream.CanWrite ? new(stream) : null; Header header = reader.ReadHeader(); IOContextFlags contextFlags = leaveOpen ? IOContextFlags.LeaveOpen : IOContextFlags.None; IOContext ioContext = new(header, reader, writer, contextFlags); diff --git a/OpenMcdf3/Sector.cs b/OpenMcdf3/Sector.cs index 8aa10cff..f3f64af1 100644 --- a/OpenMcdf3/Sector.cs +++ b/OpenMcdf3/Sector.cs @@ -1,5 +1,10 @@ namespace OpenMcdf3; +/// +/// Encapsulates information about a sector in a compound file. +/// +/// The sector ID +/// The sector length internal record struct Sector(uint Id, int Length) { public static readonly Sector EndOfChain = new(SectorType.EndOfChain, 0); diff --git a/OpenMcdf3/SectorType.cs b/OpenMcdf3/SectorType.cs index f4820042..83098373 100644 --- a/OpenMcdf3/SectorType.cs +++ b/OpenMcdf3/SectorType.cs @@ -1,5 +1,8 @@ namespace OpenMcdf3; +/// +/// Defines the types of sectors in a compound file. +/// internal static class SectorType { public const uint Maximum = 0xFFFFFFFA; diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index 044407b5..444ddbdc 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -1,5 +1,8 @@ namespace OpenMcdf3; +/// +/// An object in a compound file that is analogous to a file system directory. +/// public class Storage { internal readonly IOContext ioContext; diff --git a/OpenMcdf3/ThrowHelper.cs b/OpenMcdf3/ThrowHelper.cs index 2ee7af58..aed8877a 100644 --- a/OpenMcdf3/ThrowHelper.cs +++ b/OpenMcdf3/ThrowHelper.cs @@ -1,5 +1,8 @@ namespace OpenMcdf3; +/// +/// Extensions to consistently throw exceptions. +/// internal static class ThrowHelper { public static void ThrowIfDisposed(this object instance, bool disposed) From 344120558f02af1cac773f57156eda28928c3bb6 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 17 Oct 2024 10:55:32 +1300 Subject: [PATCH 024/134] Improve DirectoryEntryEnumerator validation --- OpenMcdf3/DirectoryEntryEnumerator.cs | 14 +++++++++----- OpenMcdf3/DirectoryTreeEnumerator.cs | 13 +++++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index 9fb62f42..e671723a 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -48,7 +48,11 @@ public bool MoveNext() if (entryIndex == -1 || entryIndex >= entryCount) { if (!chainEnumerator.MoveNext()) + { + entryIndex = int.MaxValue; + current = null; return false; + } ioContext.Reader.Seek(chainEnumerator.Current.StartOffset); entryIndex = 0; @@ -62,14 +66,14 @@ public bool MoveNext() /// /// Gets the for the specified stream ID. /// - public DirectoryEntry? GetDictionaryEntry(uint streamId) + public DirectoryEntry GetDictionaryEntry(uint streamId) { - if (streamId == StreamId.NoStream) - return null; + if (streamId > StreamId.Maximum) + throw new ArgumentException($"Invalid directory entry stream ID: ${streamId:X8}"); uint chainIndex = (uint)Math.DivRem(streamId, entryCount, out long entryIndex); if (!chainEnumerator.MoveTo(chainIndex)) - throw new ArgumentException("Invalid directory entry ID"); + throw new KeyNotFoundException($"Directory entry {streamId} was not found"); long position = chainEnumerator.Current.StartOffset + entryIndex * DirectoryEntry.Length; ioContext.Reader.Seek(position); @@ -82,6 +86,6 @@ public void Reset() { chainEnumerator.Reset(); entryIndex = -1; - current = default!; + current = null; } } diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index 42d786ad..73c8ce2a 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -15,7 +15,8 @@ internal sealed class DirectoryTreeEnumerator : IEnumerator internal DirectoryTreeEnumerator(IOContext ioContext, DirectoryEntry root) { directoryEntryEnumerator = new(ioContext); - child = directoryEntryEnumerator.GetDictionaryEntry(root.ChildId); + if (root.ChildId != StreamId.NoStream) + child = directoryEntryEnumerator.GetDictionaryEntry(root.ChildId); PushLeft(child); } @@ -49,8 +50,12 @@ public bool MoveNext() } current = stack.Pop(); - DirectoryEntry? rightSibling = directoryEntryEnumerator.GetDictionaryEntry(Current.RightSiblingId); - PushLeft(rightSibling); + if (current.RightSiblingId != StreamId.NoStream) + { + DirectoryEntry rightSibling = directoryEntryEnumerator.GetDictionaryEntry(current.RightSiblingId); + PushLeft(rightSibling); + } + return true; } @@ -67,7 +72,7 @@ private void PushLeft(DirectoryEntry? node) while (node is not null) { stack.Push(node); - node = directoryEntryEnumerator.GetDictionaryEntry(node.LeftSiblingId); + node = node.LeftSiblingId == StreamId.NoStream ? null : directoryEntryEnumerator.GetDictionaryEntry(node.LeftSiblingId); } } } From 124d52792af457ccf5f2be88d4380f50a4d0c8c2 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 17 Oct 2024 11:12:09 +1300 Subject: [PATCH 025/134] Improve MiniFatSectorEnumerator validation --- OpenMcdf3/MiniFatSectorEnumerator.cs | 48 ++++++++++++---------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/OpenMcdf3/MiniFatSectorEnumerator.cs b/OpenMcdf3/MiniFatSectorEnumerator.cs index 277e9a38..f4be2b2c 100644 --- a/OpenMcdf3/MiniFatSectorEnumerator.cs +++ b/OpenMcdf3/MiniFatSectorEnumerator.cs @@ -8,14 +8,20 @@ namespace OpenMcdf3; internal sealed class MiniFatSectorEnumerator : IEnumerator { private readonly IOContext ioContext; - private readonly FatSectorChainEnumerator miniFatChain; + private readonly FatSectorChainEnumerator fatChain; bool start = true; MiniSector current = MiniSector.EndOfChain; public MiniFatSectorEnumerator(IOContext ioContext) { this.ioContext = ioContext; - miniFatChain = new(ioContext, ioContext.Header.FirstMiniFatSectorId); + fatChain = new(ioContext, ioContext.Header.FirstMiniFatSectorId); + } + + /// + public void Dispose() + { + fatChain.Dispose(); } /// @@ -49,20 +55,6 @@ public bool MoveNext() return !current.IsEndOfChain; } - public bool MoveTo(uint id) - { - if (id == SectorType.EndOfChain) - return false; - - while (start || current.Id < id) - { - if (!MoveNext()) - return false; - } - - return true; - } - /// public void Reset() { @@ -70,23 +62,25 @@ public void Reset() current = MiniSector.EndOfChain; } - public uint GetNextMiniFatSectorId(uint id) + /// + /// Gets the next mini FAT sector ID. + /// + /// + /// + /// + public uint GetNextMiniFatSectorId(uint sectorId) { - if (id > SectorType.Maximum) - throw new ArgumentException("Invalid sector ID"); + if (sectorId > SectorType.Maximum) + throw new ArgumentException($"Invalid sector ID: {sectorId}", nameof(sectorId)); int elementLength = ioContext.Header.SectorSize / sizeof(uint); - uint sectorId = (uint)Math.DivRem(id, elementLength, out long sectorOffset); - if (!miniFatChain.MoveTo(sectorId)) - throw new ArgumentException("Invalid sector ID"); + uint fatSectorId = (uint)Math.DivRem(sectorId, elementLength, out long sectorOffset); + if (!fatChain.MoveTo(fatSectorId)) + throw new ArgumentException($"Invalid sector ID: {sectorId}", nameof(sectorId)); - long position = miniFatChain.Current.StartOffset + sectorOffset * sizeof(uint); + long position = fatChain.Current.StartOffset + sectorOffset * sizeof(uint); ioContext.Reader.Seek(position); uint nextId = ioContext.Reader.ReadUInt32(); return nextId; } - - public void Dispose() - { - } } From 5f09487efafb82af01ed5d6b9b08c0548266d967 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 17 Oct 2024 10:10:11 +1300 Subject: [PATCH 026/134] Improve exception detail --- OpenMcdf3/CfbBinaryReader.cs | 2 +- OpenMcdf3/DirectoryEntry.cs | 8 ++++---- OpenMcdf3/FatSectorEnumerator.cs | 4 ++-- OpenMcdf3/Storage.cs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/OpenMcdf3/CfbBinaryReader.cs b/OpenMcdf3/CfbBinaryReader.cs index 7ddb13b2..6a931e2d 100644 --- a/OpenMcdf3/CfbBinaryReader.cs +++ b/OpenMcdf3/CfbBinaryReader.cs @@ -86,7 +86,7 @@ public NodeColor ReadColor() public DirectoryEntry ReadDirectoryEntry(Version version) { if (version is not Version.V3 and not Version.V4) - throw new ArgumentException($"Unsupported version: {version}"); + throw new ArgumentException($"Unsupported version: {version}", nameof(version)); DirectoryEntry entry = new(); Read(buffer, 0, DirectoryEntry.NameFieldLength); diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index 2159e8a6..122e1a45 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -52,10 +52,10 @@ public string Name set { if (value.Contains(@"\") || value.Contains(@"/") || value.Contains(@":") || value.Contains(@"!")) - throw new ArgumentException("Name cannot contain any of the following characters: '\\', '/', ':','!'"); + throw new ArgumentException("Name cannot contain any of the following characters: '\\', '/', ':','!'", nameof(value)); if (Encoding.Unicode.GetByteCount(value) + 2 > NameFieldLength) - throw new ArgumentException($"{value} exceeds maximum encoded length of {NameFieldLength} bytes"); + throw new ArgumentException($"{value} exceeds maximum encoded length of {NameFieldLength} bytes", nameof(value)); name = value; } @@ -102,7 +102,7 @@ public DateTime CreationTime set { if (Type is StorageType.Stream or StorageType.Root && value != ZeroFileTime) - throw new ArgumentException("Creation time must be zero for streams and root"); + throw new ArgumentException("Creation time must be zero for streams and root", nameof(value)); creationTime = value; } @@ -117,7 +117,7 @@ public DateTime ModifiedTime set { if (Type is StorageType.Stream && value != ZeroFileTime) - throw new ArgumentException("Modified time must be zero for streams"); + throw new ArgumentException("Modified time must be zero for streams", nameof(value)); modifiedTime = value; } diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index 03cd07bb..c63f49d0 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -112,12 +112,12 @@ public void Reset() public uint GetNextFatSectorId(uint id) { if (id > SectorType.Maximum) - throw new ArgumentException("Invalid sector ID"); + throw new ArgumentException("Invalid sector ID", nameof(id)); int elementCount = ioContext.Header.SectorSize / sizeof(uint); uint sectorId = (uint)Math.DivRem(id, elementCount, out long sectorOffset); if (!MoveTo(sectorId)) - throw new ArgumentException("Invalid sector ID"); + throw new ArgumentException("Invalid sector ID", nameof(id)); long position = Current.StartOffset + sectorOffset * sizeof(uint); ioContext.Reader.Seek(position); diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index 444ddbdc..ac428018 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -50,7 +50,7 @@ public Storage OpenStorage(string name) this.ThrowIfDisposed(ioContext); DirectoryEntry? entry = EnumerateDirectoryEntries(StorageType.Storage) - .FirstOrDefault(entry => entry.Name == name) ?? throw new DirectoryNotFoundException($"Directory not found {name}"); + .FirstOrDefault(entry => entry.Name == name) ?? throw new DirectoryNotFoundException($"Storage not found: {name}"); return new Storage(ioContext, entry); } @@ -59,7 +59,7 @@ public Stream OpenStream(string name) this.ThrowIfDisposed(ioContext); DirectoryEntry? entry = EnumerateDirectoryEntries(StorageType.Stream) - .FirstOrDefault(entry => entry.Name == name) ?? throw new FileNotFoundException("Stream not found", name); + .FirstOrDefault(entry => entry.Name == name) ?? throw new FileNotFoundException($"Stream not found: {name}", name); return entry.StreamLength switch { < Header.MiniStreamCutoffSize => new MiniFatStream(ioContext, entry), From b3d2995ea4c3c4c0f358c1659775b2c6199f391c Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 17 Oct 2024 11:30:45 +1300 Subject: [PATCH 027/134] Improve sector position terminology --- OpenMcdf3/CfbBinaryReader.cs | 2 +- OpenMcdf3/DirectoryEntryEnumerator.cs | 4 ++-- OpenMcdf3/FatSectorEnumerator.cs | 4 ++-- OpenMcdf3/FatStream.cs | 2 +- OpenMcdf3/IOContext.cs | 1 + OpenMcdf3/MiniFatSectorEnumerator.cs | 2 +- OpenMcdf3/MiniFatStream.cs | 2 +- OpenMcdf3/MiniSector.cs | 10 ++++++++-- OpenMcdf3/Sector.cs | 10 ++++++++-- 9 files changed, 25 insertions(+), 12 deletions(-) diff --git a/OpenMcdf3/CfbBinaryReader.cs b/OpenMcdf3/CfbBinaryReader.cs index 6a931e2d..7dc58662 100644 --- a/OpenMcdf3/CfbBinaryReader.cs +++ b/OpenMcdf3/CfbBinaryReader.cs @@ -118,5 +118,5 @@ public DirectoryEntry ReadDirectoryEntry(Version version) return entry; } - public void Seek(long offset) => BaseStream.Seek(offset, SeekOrigin.Begin); + public void Seek(long position) => BaseStream.Position = position; } diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index e671723a..c84a41af 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -54,7 +54,7 @@ public bool MoveNext() return false; } - ioContext.Reader.Seek(chainEnumerator.Current.StartOffset); + ioContext.Reader.Seek(chainEnumerator.Current.Position); entryIndex = 0; } @@ -75,7 +75,7 @@ public DirectoryEntry GetDictionaryEntry(uint streamId) if (!chainEnumerator.MoveTo(chainIndex)) throw new KeyNotFoundException($"Directory entry {streamId} was not found"); - long position = chainEnumerator.Current.StartOffset + entryIndex * DirectoryEntry.Length; + long position = chainEnumerator.Current.Position + entryIndex * DirectoryEntry.Length; ioContext.Reader.Seek(position); current = ioContext.Reader.ReadDirectoryEntry(version); return current; diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index c63f49d0..e98e8d12 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -67,7 +67,7 @@ public bool MoveNext() int difatElementCount = ioContext.Header.SectorSize / sizeof(uint) - 1; Sector difatSector = new(difatSectorId, ioContext.Header.SectorSize); - long position = difatSector.StartOffset + difatSectorElementIndex * sizeof(uint); + long position = difatSector.Position + difatSectorElementIndex * sizeof(uint); ioContext.Reader.Seek(position); uint sectorId = ioContext.Reader.ReadUInt32(); current = new Sector(sectorId, ioContext.Header.SectorSize); @@ -119,7 +119,7 @@ public uint GetNextFatSectorId(uint id) if (!MoveTo(sectorId)) throw new ArgumentException("Invalid sector ID", nameof(id)); - long position = Current.StartOffset + sectorOffset * sizeof(uint); + long position = Current.Position + sectorOffset * sizeof(uint); ioContext.Reader.Seek(position); uint nextId = ioContext.Reader.ReadUInt32(); return nextId; diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index eb526f73..f0270898 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -88,7 +88,7 @@ public override int Read(byte[] buffer, int offset, int count) Sector sector = chain.Current; int remaining = realCount - readCount; long readLength = Math.Min(remaining, sector.Length - sectorOffset); - ioContext.Reader.Seek(sector.StartOffset + sectorOffset); + ioContext.Reader.Seek(sector.Position + sectorOffset); int localOffset = offset + readCount; int read = ioContext.Reader.Read(buffer, localOffset, (int)readLength); if (read == 0) diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index 44b8de09..c9619a00 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -30,6 +30,7 @@ public IOContext(Header header, CfbBinaryReader reader, CfbBinaryWriter? writer, RootEntry = contextFlags.HasFlag(IOContextFlags.Create) ? new DirectoryEntry() : EnumerateDirectoryEntries().First(); + // TODO: Improve root directory entry validation } public void Dispose() diff --git a/OpenMcdf3/MiniFatSectorEnumerator.cs b/OpenMcdf3/MiniFatSectorEnumerator.cs index f4be2b2c..2e0a1fd8 100644 --- a/OpenMcdf3/MiniFatSectorEnumerator.cs +++ b/OpenMcdf3/MiniFatSectorEnumerator.cs @@ -78,7 +78,7 @@ public uint GetNextMiniFatSectorId(uint sectorId) if (!fatChain.MoveTo(fatSectorId)) throw new ArgumentException($"Invalid sector ID: {sectorId}", nameof(sectorId)); - long position = fatChain.Current.StartOffset + sectorOffset * sizeof(uint); + long position = fatChain.Current.Position + sectorOffset * sizeof(uint); ioContext.Reader.Seek(position); uint nextId = ioContext.Reader.ReadUInt32(); return nextId; diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index b7df0ac6..d51dd5c5 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -86,7 +86,7 @@ public override int Read(byte[] buffer, int offset, int count) MiniSector sector = chain.Current; int remaining = realCount - readCount; long readLength = Math.Min(remaining, buffer.Length); - fatStream.Position = sector.StartOffset + sectorOffset; + fatStream.Position = sector.Position + sectorOffset; int localOffset = offset + readCount; int read = fatStream.Read(buffer, localOffset, (int)readLength); if (read == 0) diff --git a/OpenMcdf3/MiniSector.cs b/OpenMcdf3/MiniSector.cs index 38331d93..e30b6e81 100644 --- a/OpenMcdf3/MiniSector.cs +++ b/OpenMcdf3/MiniSector.cs @@ -14,7 +14,10 @@ internal record struct MiniSector(uint Id) public readonly bool IsEndOfChain => Id is SectorType.EndOfChain or SectorType.Free; - public readonly long StartOffset + /// + /// The position of the mini sector in the mini FAT stream. + /// + public readonly long Position { get { @@ -23,7 +26,10 @@ public readonly long StartOffset } } - public readonly long EndOffset + /// + /// The end position of the mini sector in the mini FAT stream. + /// + public readonly long EndPosition { get { diff --git a/OpenMcdf3/Sector.cs b/OpenMcdf3/Sector.cs index f3f64af1..94a9ae9a 100644 --- a/OpenMcdf3/Sector.cs +++ b/OpenMcdf3/Sector.cs @@ -17,7 +17,10 @@ internal record struct Sector(uint Id, int Length) public readonly bool IsValid => Id <= SectorType.Maximum; - public readonly long StartOffset + /// + /// The position of the mini sector in the compound file stream. + /// + public readonly long Position { get { @@ -26,7 +29,10 @@ public readonly long StartOffset } } - public readonly long EndOffset + /// + /// The end position of the mini sector in the compound file stream. + /// + public readonly long EndPosition { get { From 96ac4a5b4e09af4a9e38fe5bf0752aad8ea959db Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 17 Oct 2024 22:05:39 +1300 Subject: [PATCH 028/134] Improve stream read validation --- OpenMcdf3/FatStream.cs | 8 ++++---- OpenMcdf3/MiniFatStream.cs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index f0270898..7c86cda8 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -73,14 +73,14 @@ public override int Read(byte[] buffer, int offset, int count) if (count == 0) return 0; - uint chainIndex = (uint)Math.DivRem(position, ioContext.Header.SectorSize, out long sectorOffset); - if (!chain.MoveTo(chainIndex)) - return 0; - int maxCount = (int)Math.Min(Math.Max(length - position, 0), int.MaxValue); if (maxCount == 0) return 0; + uint chainIndex = (uint)Math.DivRem(position, ioContext.Header.SectorSize, out long sectorOffset); + if (!chain.MoveTo(chainIndex)) + return 0; + int realCount = Math.Min(count, maxCount); int readCount = 0; do diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index d51dd5c5..5044e953 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -71,14 +71,14 @@ public override int Read(byte[] buffer, int offset, int count) if (count == 0) return 0; - uint chainIndex = (uint)Math.DivRem(position, ioContext.Header.SectorSize, out long sectorOffset); - if (!chain.MoveTo(chainIndex)) - return 0; - int maxCount = (int)Math.Min(Math.Max(length - position, 0), int.MaxValue); if (maxCount == 0) return 0; + uint chainIndex = (uint)Math.DivRem(position, ioContext.Header.SectorSize, out long sectorOffset); + if (!chain.MoveTo(chainIndex)) + return 0; + int realCount = Math.Min(count, maxCount); int readCount = 0; do From 305761f7899a562202c685379547194d73309ab6 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Sat, 19 Oct 2024 20:35:51 +1300 Subject: [PATCH 029/134] Improve ReadGuid validation --- OpenMcdf3/CfbBinaryReader.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/OpenMcdf3/CfbBinaryReader.cs b/OpenMcdf3/CfbBinaryReader.cs index 7dc58662..d237a598 100644 --- a/OpenMcdf3/CfbBinaryReader.cs +++ b/OpenMcdf3/CfbBinaryReader.cs @@ -7,6 +7,7 @@ namespace OpenMcdf3; /// internal sealed class CfbBinaryReader : BinaryReader { + readonly byte[] guidBuffer = new byte[16]; readonly byte[] buffer = new byte[DirectoryEntry.NameFieldLength]; public CfbBinaryReader(Stream input) @@ -14,7 +15,19 @@ public CfbBinaryReader(Stream input) { } - public Guid ReadGuid() => new(ReadBytes(16)); + public Guid ReadGuid() + { + int bytesRead = 0; + do + { + int n = Read(guidBuffer, bytesRead, guidBuffer.Length - bytesRead); + if (n == 0) + throw new EndOfStreamException(); + bytesRead += n; + } while (bytesRead < guidBuffer.Length); + + return new Guid(guidBuffer); + } public DateTime ReadFileTime() { @@ -22,8 +35,6 @@ public DateTime ReadFileTime() return DateTime.FromFileTimeUtc(fileTime); } - private void ReadBytes(byte[] buffer) => Read(buffer, 0, buffer.Length); - public Header ReadHeader() { Header header = new(); From 30de2e424f997d18d80e5a743783156a06f9a6be Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Sat, 19 Oct 2024 22:36:22 +1300 Subject: [PATCH 030/134] Improved DirectoryEntry validation --- OpenMcdf3/CfbBinaryReader.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/OpenMcdf3/CfbBinaryReader.cs b/OpenMcdf3/CfbBinaryReader.cs index d237a598..ad365038 100644 --- a/OpenMcdf3/CfbBinaryReader.cs +++ b/OpenMcdf3/CfbBinaryReader.cs @@ -101,8 +101,9 @@ public DirectoryEntry ReadDirectoryEntry(Version version) DirectoryEntry entry = new(); Read(buffer, 0, DirectoryEntry.NameFieldLength); - int nameLength = Math.Max(0, ReadUInt16() - 2); - entry.Name = Encoding.Unicode.GetString(buffer, 0, nameLength); + ushort nameLength = ReadUInt16(); + int clampedNameLength = Math.Max(0, Math.Min(ushort.MaxValue, nameLength - 2)); + entry.Name = Encoding.Unicode.GetString(buffer, 0, clampedNameLength); entry.Type = ReadStorageType(); entry.Color = ReadColor(); entry.LeftSiblingId = ReadUInt32(); From 812d58296c77149a87609924f8c927b1d9823700 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Sun, 20 Oct 2024 16:01:55 +1300 Subject: [PATCH 031/134] Refactor chain enumeration --- OpenMcdf3/FatSectorChainEnumerator.cs | 21 ++++++++++++++++++++- OpenMcdf3/FatSectorEnumerator.cs | 21 +++------------------ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/OpenMcdf3/FatSectorChainEnumerator.cs b/OpenMcdf3/FatSectorChainEnumerator.cs index f06757a1..09117b00 100644 --- a/OpenMcdf3/FatSectorChainEnumerator.cs +++ b/OpenMcdf3/FatSectorChainEnumerator.cs @@ -56,7 +56,7 @@ public bool MoveNext() } else if (!current.IsEndOfChain) { - uint sectorId = fatEnumerator.GetNextFatSectorId(current.Id); + uint sectorId = GetNextFatSectorId(current.Id); current = new(sectorId, ioContext.Header.SectorSize); Index++; } @@ -98,4 +98,23 @@ public void Reset() current = Sector.EndOfChain; Index = uint.MaxValue; } + + /// + /// Gets the next sector ID in the FAT chain. + /// + uint GetNextFatSectorId(uint id) + { + if (id > SectorType.Maximum) + throw new ArgumentException("Invalid sector ID", nameof(id)); + + int elementCount = ioContext.Header.SectorSize / sizeof(uint); + uint sectorId = (uint)Math.DivRem(id, elementCount, out long sectorOffset); + if (!fatEnumerator.MoveTo(sectorId)) + throw new ArgumentException("Invalid sector ID", nameof(id)); + + long position = fatEnumerator.Current.Position + sectorOffset * sizeof(uint); + ioContext.Reader.Seek(position); + uint nextId = ioContext.Reader.ReadUInt32(); + return nextId; + } } diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index e98e8d12..21bbf81b 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -83,7 +83,9 @@ public bool MoveNext() return true; } - /// + /// + /// Moves the enumerator to the specified sector. + /// public bool MoveTo(uint sectorId) { if (sectorId < id) @@ -107,21 +109,4 @@ public void Reset() difatSectorElementIndex = 0; current = Sector.EndOfChain; } - - /// - public uint GetNextFatSectorId(uint id) - { - if (id > SectorType.Maximum) - throw new ArgumentException("Invalid sector ID", nameof(id)); - - int elementCount = ioContext.Header.SectorSize / sizeof(uint); - uint sectorId = (uint)Math.DivRem(id, elementCount, out long sectorOffset); - if (!MoveTo(sectorId)) - throw new ArgumentException("Invalid sector ID", nameof(id)); - - long position = Current.Position + sectorOffset * sizeof(uint); - ioContext.Reader.Seek(position); - uint nextId = ioContext.Reader.ReadUInt32(); - return nextId; - } } From ad09603ef5dc49defac010f5e520b5ab5b614296 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Sun, 20 Oct 2024 20:49:04 +1300 Subject: [PATCH 032/134] Fix solution paths Seems VS Pro somehow added absolute paths to the projects --- OpenMcdf3.sln | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenMcdf3.sln b/OpenMcdf3.sln index 4f19d749..fbea60de 100644 --- a/OpenMcdf3.sln +++ b/OpenMcdf3.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.11.35327.3 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3", "D:\OpenMcdf3\OpenMcdf3\OpenMcdf3.csproj", "{B90DDE7E-803A-4890-82F0-09DAD0FF66D8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3", "OpenMcdf3\OpenMcdf3.csproj", "{B90DDE7E-803A-4890-82F0-09DAD0FF66D8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3.Tests", "D:\OpenMcdf3\OpenMcdf3.Tests\OpenMcdf3.Tests.csproj", "{96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3.Tests", "OpenMcdf3.Tests\OpenMcdf3.Tests.csproj", "{96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{34030FA7-0A06-43D1-85DD-ADD39D502C3C}" ProjectSection(SolutionItems) = preProject From a2ba07c87b78db3568d6e850c379dd5ca59c420c Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 21 Oct 2024 10:15:23 +1300 Subject: [PATCH 033/134] Improve FatSectorEnumerator validation --- OpenMcdf3/FatSectorEnumerator.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index 21bbf81b..f9530a12 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -88,6 +88,9 @@ public bool MoveNext() /// public bool MoveTo(uint sectorId) { + if (sectorId > SectorType.Maximum) + throw new ArgumentOutOfRangeException(nameof(sectorId)); + if (sectorId < id) Reset(); From 7391580afa634091bec3e0facd4a7e8c508527ac Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 21 Oct 2024 10:11:24 +1300 Subject: [PATCH 034/134] Rename chain enumerators --- OpenMcdf3/DirectoryEntryEnumerator.cs | 6 +++--- .../{FatSectorChainEnumerator.cs => FatChainEnumerator.cs} | 4 ++-- OpenMcdf3/FatStream.cs | 2 +- ...atSectorChainEnumerator.cs => MiniFatChainEnumerator.cs} | 4 ++-- OpenMcdf3/MiniFatSectorEnumerator.cs | 2 +- OpenMcdf3/MiniFatStream.cs | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) rename OpenMcdf3/{FatSectorChainEnumerator.cs => FatChainEnumerator.cs} (95%) rename OpenMcdf3/{MiniFatSectorChainEnumerator.cs => MiniFatChainEnumerator.cs} (93%) diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index c84a41af..596c49ff 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -3,14 +3,14 @@ namespace OpenMcdf3; /// -/// Enumerates instances from a . +/// Enumerates instances from a . /// internal sealed class DirectoryEntryEnumerator : IEnumerator { private readonly IOContext ioContext; private readonly Version version; private readonly int entryCount; - private readonly FatSectorChainEnumerator chainEnumerator; + private readonly FatChainEnumerator chainEnumerator; private int entryIndex = -1; private DirectoryEntry? current; @@ -19,7 +19,7 @@ public DirectoryEntryEnumerator(IOContext ioContext) this.ioContext = ioContext; this.version = (Version)ioContext.Header.MajorVersion; this.entryCount = ioContext.Header.SectorSize / DirectoryEntry.Length; - this.chainEnumerator = new FatSectorChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); + this.chainEnumerator = new FatChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); } /// diff --git a/OpenMcdf3/FatSectorChainEnumerator.cs b/OpenMcdf3/FatChainEnumerator.cs similarity index 95% rename from OpenMcdf3/FatSectorChainEnumerator.cs rename to OpenMcdf3/FatChainEnumerator.cs index 09117b00..567b2cdb 100644 --- a/OpenMcdf3/FatSectorChainEnumerator.cs +++ b/OpenMcdf3/FatChainEnumerator.cs @@ -5,7 +5,7 @@ namespace OpenMcdf3; /// /// Enumerates the s in a FAT sector chain. /// -internal sealed class FatSectorChainEnumerator : IEnumerator +internal sealed class FatChainEnumerator : IEnumerator { private readonly IOContext ioContext; private readonly FatSectorEnumerator fatEnumerator; @@ -13,7 +13,7 @@ internal sealed class FatSectorChainEnumerator : IEnumerator private bool start = true; private Sector current = Sector.EndOfChain; - public FatSectorChainEnumerator(IOContext ioContext, uint startSectorId) + public FatChainEnumerator(IOContext ioContext, uint startSectorId) { this.ioContext = ioContext; this.startId = startSectorId; diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index 7c86cda8..547b52fd 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -6,7 +6,7 @@ internal class FatStream : Stream { readonly IOContext ioContext; - readonly FatSectorChainEnumerator chain; + readonly FatChainEnumerator chain; readonly long length; long position; bool disposed; diff --git a/OpenMcdf3/MiniFatSectorChainEnumerator.cs b/OpenMcdf3/MiniFatChainEnumerator.cs similarity index 93% rename from OpenMcdf3/MiniFatSectorChainEnumerator.cs rename to OpenMcdf3/MiniFatChainEnumerator.cs index dc838d7d..b090cccc 100644 --- a/OpenMcdf3/MiniFatSectorChainEnumerator.cs +++ b/OpenMcdf3/MiniFatChainEnumerator.cs @@ -5,14 +5,14 @@ namespace OpenMcdf3; /// /// Enumerates the s in a Mini FAT sector chain. /// -internal sealed class MiniFatSectorChainEnumerator : IEnumerator +internal sealed class MiniFatChainEnumerator : IEnumerator { private readonly MiniFatSectorEnumerator miniFatEnumerator; private readonly uint startId; private bool start = true; private MiniSector current = MiniSector.EndOfChain; - public MiniFatSectorChainEnumerator(IOContext ioContext, uint startSectorId) + public MiniFatChainEnumerator(IOContext ioContext, uint startSectorId) { this.startId = startSectorId; miniFatEnumerator = new(ioContext); diff --git a/OpenMcdf3/MiniFatSectorEnumerator.cs b/OpenMcdf3/MiniFatSectorEnumerator.cs index 2e0a1fd8..ff41b421 100644 --- a/OpenMcdf3/MiniFatSectorEnumerator.cs +++ b/OpenMcdf3/MiniFatSectorEnumerator.cs @@ -8,7 +8,7 @@ namespace OpenMcdf3; internal sealed class MiniFatSectorEnumerator : IEnumerator { private readonly IOContext ioContext; - private readonly FatSectorChainEnumerator fatChain; + private readonly FatChainEnumerator fatChain; bool start = true; MiniSector current = MiniSector.EndOfChain; diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index 5044e953..915d5e88 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -6,7 +6,7 @@ internal sealed class MiniFatStream : Stream { readonly IOContext ioContext; - readonly MiniFatSectorChainEnumerator chain; + readonly MiniFatChainEnumerator chain; readonly FatStream fatStream; readonly long length; long position; From 3b68891316503def5cdab3352ccd67f071a9b2ec Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 21 Oct 2024 10:26:59 +1300 Subject: [PATCH 035/134] Add MiniFatEnumerator --- OpenMcdf3/MiniFatChainEnumerator.cs | 19 +++-- OpenMcdf3/MiniFatEnumerator.cs | 102 +++++++++++++++++++++++++++ OpenMcdf3/MiniFatSectorEnumerator.cs | 20 ++++++ 3 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 OpenMcdf3/MiniFatEnumerator.cs diff --git a/OpenMcdf3/MiniFatChainEnumerator.cs b/OpenMcdf3/MiniFatChainEnumerator.cs index b090cccc..a5a8e407 100644 --- a/OpenMcdf3/MiniFatChainEnumerator.cs +++ b/OpenMcdf3/MiniFatChainEnumerator.cs @@ -7,7 +7,7 @@ namespace OpenMcdf3; /// internal sealed class MiniFatChainEnumerator : IEnumerator { - private readonly MiniFatSectorEnumerator miniFatEnumerator; + private readonly MiniFatEnumerator miniFatEnumerator; private readonly uint startId; private bool start = true; private MiniSector current = MiniSector.EndOfChain; @@ -18,6 +18,12 @@ public MiniFatChainEnumerator(IOContext ioContext, uint startSectorId) miniFatEnumerator = new(ioContext); } + /// + public void Dispose() + { + miniFatEnumerator.Dispose(); + } + /// /// The index within the Mini FAT sector chain, or if the enumeration has not started. /// @@ -48,7 +54,7 @@ public bool MoveNext() } else if (!current.IsEndOfChain) { - uint sectorId = miniFatEnumerator.GetNextMiniFatSectorId(current.Id); + uint sectorId = GetNextMiniFatSectorId(current.Id); current = new(sectorId); Index++; } @@ -91,9 +97,12 @@ public void Reset() Index = uint.MaxValue; } - /// - public void Dispose() + /// + /// Gets the next sector ID in the FAT chain. + /// + uint GetNextMiniFatSectorId(uint id) { - miniFatEnumerator.Dispose(); + miniFatEnumerator.MoveTo(id); + return miniFatEnumerator.Current.Id; } } diff --git a/OpenMcdf3/MiniFatEnumerator.cs b/OpenMcdf3/MiniFatEnumerator.cs new file mode 100644 index 00000000..d1aa852c --- /dev/null +++ b/OpenMcdf3/MiniFatEnumerator.cs @@ -0,0 +1,102 @@ +using System.Collections; + +namespace OpenMcdf3; + +/// +/// Enumerates the s from the Mini FAT. +/// +internal sealed class MiniFatEnumerator : IEnumerator +{ + private readonly IOContext ioContext; + private readonly MiniFatSectorEnumerator miniFatSectorEnumerator; + private bool start = true; + private int index = int.MaxValue; + private MiniSector current = MiniSector.EndOfChain; + + public MiniFatEnumerator(IOContext ioContext) + { + miniFatSectorEnumerator = new(ioContext); + this.ioContext = ioContext; + } + + /// + public MiniSector Current + { + get + { + if (index == int.MaxValue) + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + return current; + } + } + + /// + object IEnumerator.Current => Current; + + /// + public bool MoveNext() + { + if (start) + { + if (!miniFatSectorEnumerator.MoveNext()) + { + index = int.MaxValue; + return false; + } + + index = -1; + start = false; + } + + index++; + int elementCount = MiniSector.Length / sizeof(uint); + if (index > elementCount) + { + if (!miniFatSectorEnumerator.MoveNext()) + { + index = int.MaxValue; + return false; + } + + index = 0; + } + + long position = miniFatSectorEnumerator.Current.Position + index * sizeof(uint); + ioContext.Reader.Seek(position); + uint sectorId = ioContext.Reader.ReadUInt32(); + current = new(sectorId); + return true; + } + + public void MoveTo(uint id) + { + if (id > SectorType.Maximum) + throw new ArgumentException("Invalid sector ID", nameof(id)); + + int elementCount = ioContext.Header.SectorSize / sizeof(uint); + uint sectorId = (uint)Math.DivRem(id, elementCount, out long index); + + miniFatSectorEnumerator.MoveTo(sectorId); + long position = miniFatSectorEnumerator.Current.Position + index * sizeof(uint); + ioContext.Reader.Seek(position); + uint value = ioContext.Reader.ReadUInt32(); + this.index = (int)index; + start = false; + current = new(value); + } + + /// + public void Reset() + { + miniFatSectorEnumerator.Reset(); + start = true; + current = MiniSector.EndOfChain; + index = int.MaxValue; + } + + /// + public void Dispose() + { + miniFatSectorEnumerator.Dispose(); + } +} diff --git a/OpenMcdf3/MiniFatSectorEnumerator.cs b/OpenMcdf3/MiniFatSectorEnumerator.cs index ff41b421..f9552d42 100644 --- a/OpenMcdf3/MiniFatSectorEnumerator.cs +++ b/OpenMcdf3/MiniFatSectorEnumerator.cs @@ -55,6 +55,26 @@ public bool MoveNext() return !current.IsEndOfChain; } + /// + /// Moves the enumerator to the specified sector. + /// + public bool MoveTo(uint sectorId) + { + if (sectorId > SectorType.Maximum) + throw new ArgumentOutOfRangeException(nameof(sectorId)); + + if (sectorId < current.Id) + Reset(); + + while (start || current.Id < sectorId) + { + if (!MoveNext()) + return false; + } + + return true; + } + /// public void Reset() { From e1b78accbd7caf63637da8857946a51e085777a7 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 21 Oct 2024 11:11:38 +1300 Subject: [PATCH 036/134] Allow enumerating unallocated DirectoryEntries --- OpenMcdf3/DirectoryEntryEnumerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index 596c49ff..19c8adba 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -60,7 +60,7 @@ public bool MoveNext() current = ioContext.Reader.ReadDirectoryEntry(version); entryIndex++; - return current.Type != StorageType.Unallocated; + return true; } /// From 5e19c7ff65db8ab56ebd3666d8a4d1fdbf4f4c54 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Sat, 19 Oct 2024 21:12:06 +1300 Subject: [PATCH 037/134] Allow writing CFB streams --- OpenMcdf3.Benchmarks/InMemory.cs | 74 ++-- .../OpenMcdf3.Benchmarks.csproj | 3 +- OpenMcdf3.Benchmarks/Program.cs | 2 +- OpenMcdf3.Perf/OpenMcdf3.Perf.csproj | 14 + OpenMcdf3.Perf/Program.cs | 32 ++ OpenMcdf3.Tests/AssemblyInfo.cs | 19 + OpenMcdf3.Tests/BinaryWriterTests.cs | 71 ++++ OpenMcdf3.Tests/DebugWriter.cs | 15 + OpenMcdf3.Tests/OpenMcdf3.Tests.csproj | 7 +- OpenMcdf3.Tests/StorageTests.cs | 37 ++ OpenMcdf3.Tests/StreamTests.cs | 396 +++++++++++++++++- OpenMcdf3.Tests/TestStream_v3_0.cfs | Bin 1536 -> 1536 bytes OpenMcdf3.Tests/TestStream_v3_4095.cfs | Bin 6144 -> 6144 bytes OpenMcdf3.Tests/TestStream_v3_4096.cfs | Bin 5632 -> 10240 bytes OpenMcdf3.Tests/TestStream_v3_4097.cfs | Bin 6144 -> 10240 bytes OpenMcdf3.Tests/TestStream_v3_511.cfs | Bin 2560 -> 2560 bytes OpenMcdf3.Tests/TestStream_v3_512.cfs | Bin 2560 -> 2560 bytes OpenMcdf3.Tests/TestStream_v3_513.cfs | Bin 3072 -> 3072 bytes OpenMcdf3.Tests/TestStream_v3_63.cfs | Bin 2560 -> 2560 bytes OpenMcdf3.Tests/TestStream_v3_64.cfs | Bin 2560 -> 2560 bytes OpenMcdf3.Tests/TestStream_v3_65.cfs | Bin 2560 -> 2560 bytes OpenMcdf3.Tests/TestStream_v3_65536.cfs | Bin 0 -> 68096 bytes OpenMcdf3.Tests/TestStream_v4_0.cfs | Bin 1536 -> 1536 bytes OpenMcdf3.Tests/TestStream_v4_4095.cfs | Bin 6144 -> 6144 bytes OpenMcdf3.Tests/TestStream_v4_4096.cfs | Bin 5632 -> 10240 bytes OpenMcdf3.Tests/TestStream_v4_4097.cfs | Bin 6144 -> 10240 bytes OpenMcdf3.Tests/TestStream_v4_511.cfs | Bin 2560 -> 2560 bytes OpenMcdf3.Tests/TestStream_v4_512.cfs | Bin 2560 -> 2560 bytes OpenMcdf3.Tests/TestStream_v4_513.cfs | Bin 3072 -> 3072 bytes OpenMcdf3.Tests/TestStream_v4_63.cfs | Bin 2560 -> 2560 bytes OpenMcdf3.Tests/TestStream_v4_64.cfs | Bin 2560 -> 2560 bytes OpenMcdf3.Tests/TestStream_v4_65.cfs | Bin 2560 -> 2560 bytes OpenMcdf3.sln | 8 +- OpenMcdf3/CfbBinaryReader.cs | 67 +-- OpenMcdf3/CfbBinaryWriter.cs | 30 +- OpenMcdf3/CfbStream.cs | 96 +++++ OpenMcdf3/DirectoryEntry.cs | 101 ++++- OpenMcdf3/DirectoryEntryEnumerator.cs | 105 +++-- OpenMcdf3/DirectoryTreeEnumerator.cs | 65 ++- OpenMcdf3/Fat.cs | 121 ++++++ OpenMcdf3/FatChainEntry.cs | 10 + OpenMcdf3/FatChainEnumerator.cs | 195 +++++++-- OpenMcdf3/FatEntry.cs | 13 + OpenMcdf3/FatEnumerator.cs | 132 ++++++ OpenMcdf3/FatSectorEnumerator.cs | 139 ++++-- OpenMcdf3/FatStream.cs | 93 +++- OpenMcdf3/Header.cs | 65 ++- OpenMcdf3/IOContext.cs | 107 ++++- OpenMcdf3/MiniFat.cs | 106 +++++ OpenMcdf3/MiniFatChainEnumerator.cs | 160 +++++-- OpenMcdf3/MiniFatEnumerator.cs | 114 ++--- OpenMcdf3/MiniFatSectorEnumerator.cs | 106 ----- OpenMcdf3/MiniFatStream.cs | 116 +++-- OpenMcdf3/MiniSector.cs | 8 +- OpenMcdf3/OpenMcdf3.csproj | 6 +- OpenMcdf3/RootStorage.cs | 22 +- OpenMcdf3/Sector.cs | 6 +- OpenMcdf3/SectorDataCache.cs | 25 ++ OpenMcdf3/SectorType.cs | 2 + OpenMcdf3/Storage.cs | 65 ++- OpenMcdf3/StreamExtensions.cs | 40 ++ OpenMcdf3/ThrowHelper.cs | 42 +- global.json | 7 + 63 files changed, 2342 insertions(+), 500 deletions(-) create mode 100644 OpenMcdf3.Perf/OpenMcdf3.Perf.csproj create mode 100644 OpenMcdf3.Perf/Program.cs create mode 100644 OpenMcdf3.Tests/AssemblyInfo.cs create mode 100644 OpenMcdf3.Tests/BinaryWriterTests.cs create mode 100644 OpenMcdf3.Tests/DebugWriter.cs create mode 100644 OpenMcdf3.Tests/TestStream_v3_65536.cfs create mode 100644 OpenMcdf3/CfbStream.cs create mode 100644 OpenMcdf3/Fat.cs create mode 100644 OpenMcdf3/FatChainEntry.cs create mode 100644 OpenMcdf3/FatEntry.cs create mode 100644 OpenMcdf3/FatEnumerator.cs create mode 100644 OpenMcdf3/MiniFat.cs delete mode 100644 OpenMcdf3/MiniFatSectorEnumerator.cs create mode 100644 OpenMcdf3/SectorDataCache.cs create mode 100644 OpenMcdf3/StreamExtensions.cs create mode 100644 global.json diff --git a/OpenMcdf3.Benchmarks/InMemory.cs b/OpenMcdf3.Benchmarks/InMemory.cs index 96bd180c..4d3297e5 100644 --- a/OpenMcdf3.Benchmarks/InMemory.cs +++ b/OpenMcdf3.Benchmarks/InMemory.cs @@ -18,11 +18,12 @@ public class InMemory : IDisposable private const string storageName = "MyStorage"; private const string streamName = "MyStream"; - private byte[] _readBuffer; + private byte[] readBuffer; - private MemoryStream _stream; + private readonly MemoryStream readStream = new(); + private readonly MemoryStream writeStream = new(); - [Params(Kb / 2, Kb, 4 * Kb, 128 * Kb, 256 * Kb, 512 * Kb, Kb * Kb)] + [Params(512, Mb /*Kb, 4 * Kb, 128 * Kb, 256 * Kb, 512 * Kb,*/)] public int BufferSize { get; set; } [Params(Mb /*, 8 * Mb, 64 * Mb, 128 * Mb*/)] @@ -30,68 +31,65 @@ public class InMemory : IDisposable public void Dispose() { - _stream?.Dispose(); + readStream?.Dispose(); + writeStream?.Dispose(); } [GlobalSetup] public void GlobalSetup() { - _stream = new MemoryStream(); - //_stream = File.Create("D:\\test.cfb"); - _readBuffer = new byte[BufferSize]; + readBuffer = new byte[BufferSize]; CreateFile(1); } - [GlobalCleanup] - public void GlobalCleanup() - { - _stream.Dispose(); - _stream = null; - _readBuffer = null; - } - [Benchmark] - public void Test() + public void Read() { - // - _stream.Seek(0L, SeekOrigin.Begin); - // - using var compoundFile = RootStorage.Open(_stream); - using Stream cfStream = compoundFile.OpenStream(streamName + 0); + using var compoundFile = RootStorage.Open(readStream); + using CfbStream cfStream = compoundFile.OpenStream(streamName + 0); long streamSize = cfStream.Length; long position = 0L; while (true) { if (position >= streamSize) break; - int read = cfStream.Read(_readBuffer, 0, _readBuffer.Length); + int read = cfStream.Read(readBuffer, 0, readBuffer.Length); position += read; if (read <= 0) break; } + } + + [Benchmark] + public void Write() + { + MemoryStream memoryStream = writeStream; + using var storage = RootStorage.Create(memoryStream); + Storage subStorage = storage.CreateStorage(storageName); + CfbStream stream = subStorage.CreateStream(streamName + 0); - //compoundFile.Close(); + while (stream.Length < TotalStreamSize) + { + stream.Write(readBuffer, 0, readBuffer.Length); + } } private void CreateFile(int streamCount) { - var iterationCount = TotalStreamSize / BufferSize; + int iterationCount = TotalStreamSize / BufferSize; - var buffer = new byte[BufferSize]; - Array.Fill(buffer, byte.MaxValue); + byte[] buffer = new byte[BufferSize]; + buffer.AsSpan().Fill(byte.MaxValue); const CFSConfiguration flags = CFSConfiguration.Default | CFSConfiguration.LeaveOpen; - using (var compoundFile = new CompoundFile(CFSVersion.Ver_3, flags)) + using var compoundFile = new CompoundFile(CFSVersion.Ver_3, flags); + CFStorage st = compoundFile.RootStorage; + for (int streamId = 0; streamId < streamCount; ++streamId) { - //var st = compoundFile.RootStorage.AddStorage(storageName); - var st = compoundFile.RootStorage; - for (var streamId = 0; streamId < streamCount; ++streamId) - { - var sm = st.AddStream(streamName + streamId); - - for (var iteration = 0; iteration < iterationCount; ++iteration) sm.Append(buffer); - } - - compoundFile.Save(_stream); - compoundFile.Close(); + CFStream sm = st.AddStream(streamName + streamId); + for (int iteration = 0; iteration < iterationCount; ++iteration) + sm.Append(buffer); } + + compoundFile.Save(readStream); + compoundFile.Close(); } } diff --git a/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj b/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj index 94e38e90..8d732776 100644 --- a/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj +++ b/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj @@ -1,8 +1,9 @@  + net8.0 + 11.0 Exe - net8.0 enable enable diff --git a/OpenMcdf3.Benchmarks/Program.cs b/OpenMcdf3.Benchmarks/Program.cs index f00ccb3e..2707ca37 100644 --- a/OpenMcdf3.Benchmarks/Program.cs +++ b/OpenMcdf3.Benchmarks/Program.cs @@ -2,7 +2,7 @@ namespace OpenMcdf3.Benchmarks; -internal class Program +internal static class Program { private static void Main(string[] args) { diff --git a/OpenMcdf3.Perf/OpenMcdf3.Perf.csproj b/OpenMcdf3.Perf/OpenMcdf3.Perf.csproj new file mode 100644 index 00000000..9d82e145 --- /dev/null +++ b/OpenMcdf3.Perf/OpenMcdf3.Perf.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/OpenMcdf3.Perf/Program.cs b/OpenMcdf3.Perf/Program.cs new file mode 100644 index 00000000..63bb11d7 --- /dev/null +++ b/OpenMcdf3.Perf/Program.cs @@ -0,0 +1,32 @@ +using System.Diagnostics; + +namespace OpenMcdf3.Perf; + +internal sealed class Program +{ + static void Main(string[] args) + { + var stopwatch = Stopwatch.StartNew(); + Write(Version.V3, 512, 1024); + Console.WriteLine($"Elapsed: {stopwatch.Elapsed}"); + } + + static void Write(Version version, int length, int iterations) + { + // Fill with bytes equal to their position modulo 256 + byte[] expectedBuffer = new byte[length]; + for (int i = 0; i < length; i++) + expectedBuffer[i] = (byte)i; + + //byte[] actualBuffer = new byte[length]; + + using MemoryStream memoryStream = new(2 * length); + using var rootStorage = RootStorage.Create(memoryStream, version); + using Stream stream = rootStorage.CreateStream("TestStream"); + + for (int i = 0; i < iterations; i++) + { + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + } + } +} diff --git a/OpenMcdf3.Tests/AssemblyInfo.cs b/OpenMcdf3.Tests/AssemblyInfo.cs new file mode 100644 index 00000000..6df06f30 --- /dev/null +++ b/OpenMcdf3.Tests/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +// In SDK-style projects such as this one, several assembly attributes that were historically +// defined in this file are now automatically added during build and populated with +// values defined in project properties. For details of which attributes are included +// and how to customise this process see: https://aka.ms/assembly-info-properties + + +// Setting ComVisible to false makes the types in this assembly not visible to COM +// components. If you need to access a type in this assembly from COM, set the ComVisible +// attribute to true on that type. + +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM. + +[assembly: Guid("38e3c8a7-44c7-4d13-9c30-7aa75b038c83")] + +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/OpenMcdf3.Tests/BinaryWriterTests.cs b/OpenMcdf3.Tests/BinaryWriterTests.cs new file mode 100644 index 00000000..6e5f4722 --- /dev/null +++ b/OpenMcdf3.Tests/BinaryWriterTests.cs @@ -0,0 +1,71 @@ +namespace OpenMcdf3.Tests; + +[TestClass] +public sealed class BinaryWriterTests +{ + [TestMethod] + public void WriteGuid() + { + byte[] bytes = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 }; + Guid expectedGuid = new(bytes); + using MemoryStream stream = new(bytes); + using (CfbBinaryWriter writer = new(stream)) + writer.Write(expectedGuid); + + stream.Position = 0; + using CfbBinaryReader reader = new(stream); + Guid actualGuid = reader.ReadGuid(); + + Assert.AreEqual(expectedGuid, actualGuid); + } + + [TestMethod] + [DataRow("TestStream_v3_0.cfs")] + [DataRow("TestStream_v4_0.cfs")] + public void WriteHeader(string fileName) + { + using FileStream stream = File.OpenRead(fileName); + using CfbBinaryReader reader = new(stream); + Header header = reader.ReadHeader(); + + using MemoryStream memoryStream = new(); + using CfbBinaryWriter writer = new(memoryStream); + writer.Write(header); + + memoryStream.Position = 0; + using CfbBinaryReader reader2 = new(memoryStream); + Header actualHeader = reader2.ReadHeader(); + + Assert.AreEqual(header, actualHeader); + } + + [TestMethod] + public void WriteDirectoryEntry() + { + DirectoryEntry expected = new() + { + Name = "Root Entry", + Type = StorageType.Storage, + Color = NodeColor.Red, + LeftSiblingId = 2, + RightSiblingId = 3, + ChildId = 4, + CLSID = Guid.NewGuid(), + StateBits = 5, + CreationTime = DateTime.UtcNow, + ModifiedTime = DateTime.UtcNow, + StartSectorId = 6, + StreamLength = 7 + }; + + using MemoryStream stream = new(); + using CfbBinaryWriter writer = new(stream); + writer.Write(expected); + + stream.Position = 0; + using CfbBinaryReader reader = new(stream); + DirectoryEntry actual = reader.ReadDirectoryEntry(Version.V4, 0); + + Assert.AreEqual(expected, actual); + } +} diff --git a/OpenMcdf3.Tests/DebugWriter.cs b/OpenMcdf3.Tests/DebugWriter.cs new file mode 100644 index 00000000..f795da38 --- /dev/null +++ b/OpenMcdf3.Tests/DebugWriter.cs @@ -0,0 +1,15 @@ +using System.Diagnostics; +using System.Text; + +namespace OpenMcdf3.Tests; + +internal sealed class DebugWriter : TextWriter +{ + public override Encoding Encoding => Encoding.Unicode; + + public override void Write(char value) => Debug.Write(value); + + public override void Write(string? value) => Debug.Write(value); + + public override void WriteLine(string? value) => Debug.WriteLine(value); +} diff --git a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj index 0a325a95..1a72e048 100644 --- a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj +++ b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net48;net8.0 Exe 11.0 enable @@ -14,7 +14,7 @@ - + @@ -68,6 +68,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/OpenMcdf3.Tests/StorageTests.cs b/OpenMcdf3.Tests/StorageTests.cs index 67e8c9d2..a4c1ea81 100644 --- a/OpenMcdf3.Tests/StorageTests.cs +++ b/OpenMcdf3.Tests/StorageTests.cs @@ -14,4 +14,41 @@ public void Read(string fileName, long storageCount) IEnumerable storageEntries = rootStorage.EnumerateEntries(StorageType.Storage); Assert.AreEqual(storageCount, storageEntries.Count()); } + + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 1)] + [DataRow(Version.V3, 2)] + [DataRow(Version.V3, 4)] // Required 2 sectors including root + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 1)] + [DataRow(Version.V4, 2)] + [DataRow(Version.V4, 32)] // Required 2 sectors including root + public void CreateStorage(Version version, int subStorageCount) + { + using MemoryStream memoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + for (int i = 0; i < subStorageCount; i++) + rootStorage.CreateStorage($"Test{i}"); + } + + memoryStream.Position = 0; + using (var rootStorage = RootStorage.Open(memoryStream)) + { + IEnumerable entries = rootStorage.EnumerateEntries(); + Assert.AreEqual(subStorageCount, entries.Count()); + } + } + + [TestMethod] + [DataRow(Version.V3)] + [DataRow(Version.V4)] + public void CreateDuplicateStorageThrowsException(Version version) + { + using MemoryStream memoryStream = new(); + using var rootStorage = RootStorage.Create(memoryStream, version); + rootStorage.CreateStorage("Test"); + Assert.ThrowsException(() => rootStorage.CreateStorage("Test")); + } } diff --git a/OpenMcdf3.Tests/StreamTests.cs b/OpenMcdf3.Tests/StreamTests.cs index 01d1e369..5be7f8b3 100644 --- a/OpenMcdf3.Tests/StreamTests.cs +++ b/OpenMcdf3.Tests/StreamTests.cs @@ -14,6 +14,7 @@ public sealed class StreamTests [DataRow(Version.V3, 4095)] [DataRow(Version.V3, 4096)] [DataRow(Version.V3, 4097)] + [DataRow(Version.V3, 65536)] [DataRow(Version.V4, 0)] [DataRow(Version.V4, 63)] [DataRow(Version.V4, 64)] @@ -24,7 +25,7 @@ public sealed class StreamTests [DataRow(Version.V4, 4095)] [DataRow(Version.V4, 4096)] [DataRow(Version.V4, 4097)] - public void Read(Version version, int length) + public void ReadViaCopyTo(Version version, int length) { string fileName = $"TestStream_v{(int)version}_{length}.cfs"; using var rootStorage = RootStorage.OpenRead(fileName); @@ -41,4 +42,397 @@ public void Read(Version version, int length) StreamAssert.AreEqual(expectedStream, actualStream); } + + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 63)] + [DataRow(Version.V3, 64)] + [DataRow(Version.V3, 65)] + [DataRow(Version.V3, 511)] + [DataRow(Version.V3, 512)] + [DataRow(Version.V3, 513)] + [DataRow(Version.V3, 4095)] + [DataRow(Version.V3, 4096)] + [DataRow(Version.V3, 4097)] + [DataRow(Version.V3, 65536)] + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 63)] + [DataRow(Version.V4, 64)] + [DataRow(Version.V4, 65)] + [DataRow(Version.V4, 511)] + [DataRow(Version.V4, 512)] + [DataRow(Version.V4, 513)] + [DataRow(Version.V4, 4095)] + [DataRow(Version.V4, 4096)] + [DataRow(Version.V4, 4097)] + public void ReadSingleByte(Version version, int length) + { + string fileName = $"TestStream_v{(int)version}_{length}.cfs"; + using var rootStorage = RootStorage.OpenRead(fileName); + using Stream stream = rootStorage.OpenStream("TestStream"); + Assert.AreEqual(length, stream.Length); + + // Test files are filled with bytes equal to their position modulo 256 + using MemoryStream expectedStream = new(length); + for (int i = 0; i < length; i++) + expectedStream.WriteByte((byte)i); + + using MemoryStream actualStream = new(); + for (int i = 0; i < length; i++) + { + int value = stream.ReadByte(); + Assert.AreNotEqual(-1, value, "End of stream"); + actualStream.WriteByte((byte)value); + } + + StreamAssert.AreEqual(expectedStream, actualStream); + } + + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 63)] + [DataRow(Version.V3, 64)] // Mini-stream sector size + [DataRow(Version.V3, 65)] + [DataRow(Version.V3, 511)] + [DataRow(Version.V3, 512)] // Multiple stream sectors + [DataRow(Version.V3, 513)] + [DataRow(Version.V3, 4095)] + [DataRow(Version.V3, 4096)] + [DataRow(Version.V3, 4097)] + [DataRow(Version.V3, 128 * 512)] // Multiple FAT sectors + [DataRow(Version.V3, 1024 * 4096)] // Multiple FAT sectors + [DataRow(Version.V3, 7087616)] // First DIFAT chain + [DataRow(Version.V3, 2 * 7087616)] // Long DIFAT chain + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 63)] + [DataRow(Version.V4, 64)] // Mini-stream sector size + [DataRow(Version.V4, 65)] + [DataRow(Version.V4, 511)] + [DataRow(Version.V4, 512)] + [DataRow(Version.V4, 513)] + [DataRow(Version.V4, 4095)] + [DataRow(Version.V4, 4096)] // Multiple stream sectors + [DataRow(Version.V4, 4097)] + [DataRow(Version.V4, 1024 * 4096)] // Multiple FAT sectors (1024 * 4096) + [DataRow(Version.V4, 7087616 * 4)] // First DIFAT chain + [DataRow(Version.V4, 2 * 7087616 * 4)] // Long DIFAT chain + public void Write(Version version, int length) + { + using MemoryStream memoryStream = new(); + using var rootStorage = RootStorage.Create(memoryStream, version); + using CfbStream stream = rootStorage.CreateStream("TestStream"); + Assert.AreEqual(0, stream.Length); + + // Fill with bytes equal to their position modulo 256 + byte[] expectedBuffer = new byte[length]; + for (int i = 0; i < length; i++) + expectedBuffer[i] = (byte)i; + + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + Assert.AreEqual(length, stream.Length); + Assert.AreEqual(length, stream.Position); + + byte[] actualBuffer = new byte[length]; + stream.Position = 0; + stream.ReadExactly(actualBuffer); + + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 63)] + [DataRow(Version.V3, 64)] // Mini-stream sector size + [DataRow(Version.V3, 65)] + [DataRow(Version.V3, 511)] + [DataRow(Version.V3, 512)] // Multiple stream sectors + [DataRow(Version.V3, 513)] + [DataRow(Version.V3, 4095)] + [DataRow(Version.V3, 4096)] + [DataRow(Version.V3, 4097)] + [DataRow(Version.V3, 128 * 512)] // Multiple FAT sectors + [DataRow(Version.V3, 1024 * 4096)] // Multiple FAT sectors + [DataRow(Version.V3, 7087616)] // First DIFAT chain + [DataRow(Version.V3, 2 * 7087616)] // Long DIFAT chain + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 63)] + [DataRow(Version.V4, 64)] // Mini-stream sector size + [DataRow(Version.V4, 65)] + [DataRow(Version.V4, 511)] + [DataRow(Version.V4, 512)] + [DataRow(Version.V4, 513)] + [DataRow(Version.V4, 4095)] + [DataRow(Version.V4, 4096)] // Multiple stream sectors + [DataRow(Version.V4, 4097)] + [DataRow(Version.V4, 1024 * 4096)] // Multiple FAT sectors (1024 * 4096) + [DataRow(Version.V4, 7087616 * 4)] // First DIFAT chain + [DataRow(Version.V4, 2 * 7087616 * 4)] // Long DIFAT chain + public void WriteThenRead(Version version, int length) + { + // Fill with bytes equal to their position modulo 256 + byte[] expectedBuffer = new byte[length]; + for (int i = 0; i < length; i++) + expectedBuffer[i] = (byte)i; + + using MemoryStream memoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + using CfbStream stream = rootStorage.CreateStream("TestStream"); + Assert.AreEqual(0, stream.Length); + + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + } + + memoryStream.Position = 0; + using (var rootStorage = RootStorage.Open(memoryStream)) + { + using CfbStream stream = rootStorage.OpenStream("TestStream"); + Assert.AreEqual(length, stream.Length); + + byte[] actualBuffer = new byte[length]; + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + } + + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 256)] + [DataRow(Version.V3, 63)] + [DataRow(Version.V3, 64)] + [DataRow(Version.V3, 65)] + [DataRow(Version.V3, 511)] + [DataRow(Version.V3, 512)] + [DataRow(Version.V3, 513)] + [DataRow(Version.V3, 4095)] + [DataRow(Version.V3, 4096)] + [DataRow(Version.V3, 4097)] + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 63)] + [DataRow(Version.V4, 64)] + [DataRow(Version.V4, 65)] + [DataRow(Version.V4, 511)] + [DataRow(Version.V4, 512)] + [DataRow(Version.V4, 513)] + [DataRow(Version.V4, 4095)] + [DataRow(Version.V4, 4096)] + [DataRow(Version.V4, 4097)] + public void WriteMultiple(Version version, int length) + { + const int IterationCount = 2048; + using MemoryStream memoryStream = new(); + using var rootStorage = RootStorage.Create(memoryStream, version); + using CfbStream stream = rootStorage.CreateStream("TestStream"); + Assert.AreEqual(0, stream.Length); + + // Fill with bytes equal to their position modulo 256 + byte[] expectedBuffer = new byte[length]; + for (int i = 0; i < length; i++) + expectedBuffer[i] = (byte)i; + + for (int i = 0; i < IterationCount; i++) + { + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + Assert.AreEqual(length * (i + 1), stream.Length); + } + + stream.Flush(); + + byte[] actualBuffer = new byte[length]; + stream.Position = 0; + for (int i = 0; i < IterationCount; i++) + { + actualBuffer.AsSpan().Clear(); + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + } + + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 63)] + [DataRow(Version.V3, 64)] // Mini-stream sector size + [DataRow(Version.V3, 2* 64)] // Simplest case (1 sector => 2) + [DataRow(Version.V3, 65)] + [DataRow(Version.V3, 511)] + [DataRow(Version.V3, 512)] // Multiple stream sectors + [DataRow(Version.V3, 513)] + [DataRow(Version.V3, 4095)] + [DataRow(Version.V3, 4096)] + [DataRow(Version.V3, 4097)] + [DataRow(Version.V3, 128 * 512)] // Multiple FAT sectors + [DataRow(Version.V3, 1024 * 4096)] // Multiple FAT sectors + [DataRow(Version.V3, 7087616)] // First DIFAT chain + [DataRow(Version.V3, 2 * 7087616)] // Long DIFAT chain + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 63)] + [DataRow(Version.V4, 64)] // Mini-stream sector size + [DataRow(Version.V4, 65)] + [DataRow(Version.V4, 511)] + [DataRow(Version.V4, 512)] + [DataRow(Version.V4, 513)] + [DataRow(Version.V4, 4095)] + [DataRow(Version.V4, 4096)] // Multiple stream sectors + [DataRow(Version.V4, 2 * 4096)] // Simplest case (1 sector => 2) + [DataRow(Version.V4, 4097)] + [DataRow(Version.V4, 1024 * 4096)] // Multiple FAT sectors (1024 * 4096) + [DataRow(Version.V4, 7087616 * 4)] // First DIFAT chain + [DataRow(Version.V4, 2 * 7087616 * 4)] // Long DIFAT chain + public void Shrink(Version version, int length) + { + using MemoryStream memoryStream = new(); + using var rootStorage = RootStorage.Create(memoryStream, version); + using CfbStream stream = rootStorage.CreateStream("Test"); + + // Fill with bytes equal to their position modulo 256 + byte[] expectedBuffer = new byte[length]; + for (int i = 0; i < length; i++) + expectedBuffer[i] = (byte)i; + + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + Assert.AreEqual(length, stream.Length); + + long baseStreamLength = memoryStream.Length; + + int newLength = length / 2; + stream.SetLength(newLength); + Assert.AreEqual(newLength, stream.Length); + + stream.Position = newLength; + stream.Write(expectedBuffer, newLength, expectedBuffer.Length - newLength); + Assert.AreEqual(length, stream.Length); + + byte[] actualBuffer = new byte[length]; + stream.Seek(0, SeekOrigin.Begin); + stream.ReadExactly(actualBuffer); + + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + + [TestMethod] + [DataRow(Version.V3)] + [DataRow(Version.V4)] + public void MiniFatToFat(Version version) + { + using MemoryStream memoryStream = new(); + using var rootStorage = RootStorage.Create(memoryStream, version); + using CfbStream stream = rootStorage.CreateStream("Test"); + + int length = 256; + // Fill with bytes equal to their position modulo 256 + byte[] expectedBuffer = new byte[length]; + for (int i = 0; i < length; i++) + expectedBuffer[i] = (byte)i; + + int iterations = (int)Header.MiniStreamCutoffSize / length; + for (int i = 0; i < iterations; i++) + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + + Assert.AreEqual(length * iterations, stream.Length); + + byte[] actualBuffer = new byte[length]; + stream.Position = 0; + for (int i = 0; i < iterations; i++) + { + actualBuffer.AsSpan().Clear(); + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + } + + [TestMethod] + [DataRow(Version.V3)] + [DataRow(Version.V4)] + public void FatToMiniFat(Version version) + { + const int length = 256; + + using MemoryStream memoryStream = new(); + using var rootStorage = RootStorage.Create(memoryStream, version); + using CfbStream stream = rootStorage.CreateStream("Test"); + + // Fill with bytes equal to their position modulo 256 + byte[] expectedBuffer = new byte[length]; + for (int i = 0; i < length; i++) + expectedBuffer[i] = (byte)i; + + int iterations = (int)Header.MiniStreamCutoffSize / length; + for (int i = 0; i < iterations; i++) + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + + Assert.AreEqual(length * iterations, stream.Length); + + byte[] actualBuffer = new byte[length]; + + // Check reading from the regular sectors + stream.Position = 0; + for (int i = 0; i < iterations; i++) + { + actualBuffer.AsSpan().Clear(); + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + + stream.SetLength(length); + Assert.AreEqual(length, stream.Length); + + stream.Position = 0; + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 63)] + [DataRow(Version.V3, 64)] + [DataRow(Version.V3, 65)] + [DataRow(Version.V3, 511)] + [DataRow(Version.V3, 512)] + [DataRow(Version.V3, 513)] + [DataRow(Version.V3, 4095)] + [DataRow(Version.V3, 4096)] + [DataRow(Version.V3, 4097)] + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 63)] + [DataRow(Version.V4, 64)] + [DataRow(Version.V4, 65)] + [DataRow(Version.V4, 511)] + [DataRow(Version.V4, 512)] + [DataRow(Version.V4, 513)] + [DataRow(Version.V4, 4095)] + [DataRow(Version.V4, 4096)] + [DataRow(Version.V4, 4097)] + public void CopyFromStream(Version version, int length) + { + using MemoryStream memoryStream = new(); + using var rootStorage = RootStorage.Create(memoryStream, version); + using CfbStream stream = rootStorage.CreateStream("TestStream"); + Assert.AreEqual(0, stream.Length); + + // Fill with bytes equal to their position modulo 256 + using MemoryStream expectedStream = new(length); + for (int i = 0; i < length; i++) + expectedStream.WriteByte((byte)i); + + expectedStream.Position = 0; + expectedStream.CopyTo(stream); + Assert.AreEqual(length, stream.Length); + + using MemoryStream actualStream = new(); + stream.Seek(0, SeekOrigin.Begin); + stream.CopyTo(actualStream); + + StreamAssert.AreEqual(expectedStream, actualStream); + } + + [TestMethod] + [DataRow(Version.V3)] + [DataRow(Version.V4)] + public void CreateDuplicateStreamThrowsException(Version version) + { + using MemoryStream memoryStream = new(); + using var rootStorage = RootStorage.Create(memoryStream, version); + using CfbStream stream = rootStorage.CreateStream("Test"); + Assert.ThrowsException(() => rootStorage.CreateStream("Test")); + } } diff --git a/OpenMcdf3.Tests/TestStream_v3_0.cfs b/OpenMcdf3.Tests/TestStream_v3_0.cfs index c3ee5ef6ddebb0ee8bec31dcf1ce2ac4d219681b..5052440a17b95ae8667d6ad50e3464851b2cbbd8 100644 GIT binary patch delta 66 zcmZqRY2cY)z{oh!P@07S2sR6{Y+z*l`~Uy{e?aDY#)%0$lbD>j7=Qv`z&JUONr8Pq PI=h|rZN|+dOg)SM9xfTz delta 58 zcmZqRY2cY)z{oJsP@092fq`MOAkzlMNi5D>jQ^p4aWW&T`sNar9!A!GK%u`t74I1* HCh!0NMIRS9 diff --git a/OpenMcdf3.Tests/TestStream_v3_4095.cfs b/OpenMcdf3.Tests/TestStream_v3_4095.cfs index effaf33da4b905593f2ce7e00c9f7b23284978bb..b95bc94c884c5cce3a1952ceb437bce7dbb1447f 100644 GIT binary patch delta 110 zcmZoLXfT*ypuorg1p*8VOh6`x|L_0*|1jarhC&+{dHw=LK%yW#nMXv1@gGq9JmbU! oj!9z9Tns=_FkqalC??OD(3Qb%r+u4|d2^{)H{&7^rbSEw0Kfz&Hvj+t delta 98 zcmZoLXfT*ypuoid1p*8VoIoZc5dZuC|365S1Bf>pGHqa7#33+=O@{!LaCQkUfcO7H?P9}x2c zF;E+WAP@@y@jsyA?TnK|oVgf)>cN0o&^04VG= Z*+qO0P{(=3i3x_AnEvxKGAv>e008lsEs6jD delta 157 zcmZn&XfT*ypuoid1p*8V|Nj5~58}aSP9S?TBijbXO&tIE8UIfXWG$YYz?y=D6Ezuu zYM6kS8Hibcm=%cGfS4VKIe-{s)?Xn02Q=t9<0KYmE=C{^3>YUfvZ_xmVQH9bz{0}B QC;&9yYZ4pJK%yYb0+iY;!1jT05|=X< p15f}A7$-Bb$a5Zen$2#feVdVab17Fh<0Lkg%`6;$7#A_I004f%Cl~+# delta 94 zcmZpWXpop-puoZa1p*8V%s?h15dZuC|365S35YifGHqa7#KAI&jfDp!{uhWr^kxC3 c4~&c0m?p6}b1?$>V8A$;kyU$h1xp7b0PpQ0oB#j- diff --git a/OpenMcdf3.Tests/TestStream_v3_63.cfs b/OpenMcdf3.Tests/TestStream_v3_63.cfs index eb2839c0790d255b8640c8031ecf739b41e53bba..652fd035935bf9e11d26bdf4cfc6756b5807b67c 100644 GIT binary patch delta 79 zcmZn=X%LxUz{oh!P@07S2sR6{Y+&U4`~Uy{e;~a1FY9;4NgU2x3_u_7Pdc(i<(-}!S1;3?((|4JFmOD^g6uI>prhbc72u` zW|;fOy}7?`cy{I)7Gzm=Sz-5mfA9CRZ*S6pU3*dv2LCvv2k}8Tk{FaJ_VGB*hl<{> z5(Gu>ha-_l(TBHjhIc*vPws&NzR&2-FIp*ncBLu$Gf$whlt|^M8dac*REa7R4}lOx(q8+E51)RTHqZ|Xzo)R+2Ee;PmoX%G#jAvBbR(Qq0;BWVm9|p`Un!SMrgF=3c$-&v3H~9*Q!|3 zJ3k>kZGWiv?~5#MaX#0ZS$N&q+#k_f82c$Xhzk{6fAnoEmn>iSNk{u`WBE#df6l+X z{@A~AY@hjGuD|$YJzw(ffgltYU#fJOgtCd{%2%jZsdAO1s@1C3NUm9{c1oSP_3AfB zZP=)Blcvp@w`kcat#zBW?b>(f*r{`uuHCx#=-I1xpY*={`VSa5Xz-As!-kI-IcoHn zvE#;PWM)m6IBD{fsne#Yz*tlu) zmaW^i@7Q_FvBw>M!iguHe9Eb(oqoodXPte{x#yjK!G#xHe95JkU4F%`E3dlxnrpAS z{)QWGx_S34x8Anr_B-yp>+XB*z3=`99(?HGM;?9b@h6^q>gi{meeU@eUVQ20z58By z^|jaEc=N5d-+6ccd+&en;emr6ef-I%pMCztmtTGT&9~ouf9QuFfBN~CUw`}kk3au9 zTo8_U`~CU*`1k+6zyJLG@83WF{`>sL=YKx`_4&WAe|-Jt>tA30yZyuMKW_hW`=8rC z-Tv$LZ@2%u|HJ)1?*DTCpZh=E|Lgv5_y4>8!Sx@me{ubf>z`cz<@z_*|GECr^`EYP zb^WjFpI!g$`ghm=d;EjPe|Y?h$NzZzlgEE~{F}%BdHkcte|r3@$Nzf#v&VmX{JY2h zJO9A>56-`E{)h8Vod4qd8|VKx|H%1I&cAa0m-Ek@|K|KV=l?nX(D{$fzjXem^G}`s z>ik>h|2qHJ`OnV3cK)~X&z=A7{CnsB>mTSp=wIl6=%47n=-=r7=pX4n>0jx8>7VJp z>EG%9=^yGp>R;-A>YwVr>fh@B>L2Sr>tE}C>!0htA1(+-f{^~Z{=5FW{=5FW{=5FW z{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW z{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW z{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW z{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW{=5FW z{=5FW{=5FW{=5FW{=5FW{=5ErasRy_5{c{xf?y|wIq#`t-2?jH`se!R`se!RCDT7Y E0w|`MRsaA1 literal 0 HcmV?d00001 diff --git a/OpenMcdf3.Tests/TestStream_v4_0.cfs b/OpenMcdf3.Tests/TestStream_v4_0.cfs index c3ee5ef6ddebb0ee8bec31dcf1ce2ac4d219681b..18971686e0ce07205e0882d8f230863d7a5dd0bd 100644 GIT binary patch delta 67 zcmZqRY2cY)z{oh!P@07S2sR6{Y+z*l`~Uy{e?aDY#)%0$lbD>j7=Qv`z&KfvNuDEN Qe+j#t_HD+^B}_ex05`50GXMYp delta 58 zcmZqRY2cY)z{oJsP@092fq`MOAkzlMNi5D>jQ^p4aWW&T`sNar9!A!GK%u`t74I1* HCh!0NMIRS9 diff --git a/OpenMcdf3.Tests/TestStream_v4_4095.cfs b/OpenMcdf3.Tests/TestStream_v4_4095.cfs index effaf33da4b905593f2ce7e00c9f7b23284978bb..6cef1929d9e69ec068a237b45863ed3c401c3cbc 100644 GIT binary patch delta 110 zcmZoLXfT*ypuorg1p*8VOh6`x|L_0*|1jarhC&+{dHw=LK%yW#nMXv1@gGq9JmbU! oj!9z9Tns=_FkqalC??Ojp{bPJPWv_^^X5{qZpK9-OpBNV0LGRmi~s-t delta 98 zcmZoLXfT*ypuoid1p*8VoIoZc5dZuC|365S1Bf>pGHqa7#33+=O@{!LaCQkUfcO7H?P9}x2c zF;E+WAP@@y@jsyA?TnK|oVgf)>cN0YUfvZ_xmVQH9bz{0}B QC;&9yYZ4pJpdlHl{^POaS0`Be(zn delta 72 zcmZn=X%LxUz{ot&P@092fq`MOAkzlMMI21L|Nj5~{}+fs^gp2Jd&Y?gEQ{EfCb2kk TF#^TFfN?S-tIp<1mUcz}JrE$A diff --git a/OpenMcdf3.Tests/TestStream_v4_512.cfs b/OpenMcdf3.Tests/TestStream_v4_512.cfs index 440560b20e37dc86d8af8ffdf89d1674b0dac0aa..58abae1433e03355910ad77481f446f66e024362 100644 GIT binary patch delta 82 zcmZn=X%LxUz{oh!P@07S2sR6{Y+&U5`~Uy{e=rObde1m9fn^ehGZzC;5DXY6D{{zl ZUT~~nx6{7O$h^6fqnmLN8`B~tCIH?ZBb5LE delta 72 zcmZn=X%LxUz{ot&P@092fq`MOAkzlMMI21L|Nj5~{}+fs^gp2Jd&Y?gEQ{EfCb2kk TF#^TFfN?S-tIp<1mUcz}JrE$A diff --git a/OpenMcdf3.Tests/TestStream_v4_513.cfs b/OpenMcdf3.Tests/TestStream_v4_513.cfs index 8c1f999a50206dd0836c92ae94b5fcbd3412ea57..740dda987624c24db8c5de5dcd7d6e123f790f6f 100644 GIT binary patch delta 110 zcmZpWXpop-puorg1p*8VOh6`x|L_0*|1jarf@~WY1^xm>K%yYb0+iY;!1jT05|=X< o15f}A7$-Bb$a5;NRV8A$;kyU$h1xp7b0PpQ0oB#j- diff --git a/OpenMcdf3.Tests/TestStream_v4_63.cfs b/OpenMcdf3.Tests/TestStream_v4_63.cfs index eb2839c0790d255b8640c8031ecf739b41e53bba..169a8470651051b3ed73175025edb2cc5e42573c 100644 GIT binary patch delta 80 zcmZn=X%LxUz{oh!P@07S2sR6{Y+&U4`~Uy{e;~a1FY9;4NgU2x3_u BaseStream.Position; + set => BaseStream.Position = value; + } + public Guid ReadGuid() { int bytesRead = 0; @@ -40,23 +46,21 @@ public Header ReadHeader() Header header = new(); Read(buffer, 0, Header.Signature.Length); if (!buffer.Take(Header.Signature.Length).SequenceEqual(Header.Signature)) - throw new FormatException("Invalid header signature"); + throw new FormatException("Invalid header signature."); header.CLSID = ReadGuid(); if (header.CLSID != Guid.Empty) - throw new FormatException($"Invalid header CLSID: {header.CLSID}"); + throw new FormatException($"Invalid header CLSID: {header.CLSID}."); header.MinorVersion = ReadUInt16(); header.MajorVersion = ReadUInt16(); if (header.MajorVersion is not (ushort)Version.V3 and not (ushort)Version.V4) - throw new FormatException($"Unsupported major version: {header.MajorVersion}"); + throw new FormatException($"Unsupported major version: {header.MajorVersion}."); else if (header.MinorVersion is not Header.ExpectedMinorVersion) - throw new FormatException($"Unsupported minor version: {header.MinorVersion}"); + throw new FormatException($"Unsupported minor version: {header.MinorVersion}."); ushort byteOrder = ReadUInt16(); if (byteOrder != Header.LittleEndian) - throw new FormatException($"Unsupported byte order: {byteOrder:X4}. Only little-endian is supported ({Header.LittleEndian:X4})"); + throw new FormatException($"Unsupported byte order: {byteOrder:X4}. Only little-endian is supported ({Header.LittleEndian:X4})."); header.SectorShift = ReadUInt16(); - ushort miniSectorShift = ReadUInt16(); - if (miniSectorShift != Header.MiniSectorShift) - throw new FormatException($"Unsupported sector shift {miniSectorShift}. Only {Header.MiniSectorShift} is supported"); + header.MiniSectorShift = ReadUInt16(); this.FillBuffer(6); header.DirectorySectorCount = ReadUInt32(); header.FatSectorCount = ReadUInt32(); @@ -64,7 +68,7 @@ public Header ReadHeader() this.FillBuffer(4); uint miniStreamCutoffSize = ReadUInt32(); if (miniStreamCutoffSize != Header.MiniStreamCutoffSize) - throw new FormatException("Mini stream cutoff size must be 4096 byte"); + throw new FormatException($"Mini stream cutoff size must be {Header.MiniStreamCutoffSize} bytes."); header.FirstMiniFatSectorId = ReadUInt32(); header.MiniFatSectorCount = ReadUInt32(); header.FirstDifatSectorId = ReadUInt32(); @@ -82,7 +86,7 @@ public StorageType ReadStorageType() { var type = (StorageType)ReadByte(); if (type is not StorageType.Storage and not StorageType.Stream and not StorageType.Root and not StorageType.Unallocated) - throw new FormatException($"Invalid storage type: {type}"); + throw new FormatException($"Invalid storage type: {type}."); return type; } @@ -90,36 +94,41 @@ public NodeColor ReadColor() { var color = (NodeColor)ReadByte(); if (color is not NodeColor.Black and not NodeColor.Red) - throw new FormatException($"Invalid node color: {color}"); + throw new FormatException($"Invalid node color: {color}."); return color; } - public DirectoryEntry ReadDirectoryEntry(Version version) + public DirectoryEntry ReadDirectoryEntry(Version version, uint sid) { if (version is not Version.V3 and not Version.V4) - throw new ArgumentException($"Unsupported version: {version}", nameof(version)); + throw new ArgumentException($"Unsupported version: {version}.", nameof(version)); - DirectoryEntry entry = new(); - Read(buffer, 0, DirectoryEntry.NameFieldLength); + Read(buffer, 0, DirectoryEntry.NameFieldLength); // TODO ushort nameLength = ReadUInt16(); - int clampedNameLength = Math.Max(0, Math.Min(ushort.MaxValue, nameLength - 2)); - entry.Name = Encoding.Unicode.GetString(buffer, 0, clampedNameLength); - entry.Type = ReadStorageType(); - entry.Color = ReadColor(); - entry.LeftSiblingId = ReadUInt32(); - entry.RightSiblingId = ReadUInt32(); - entry.ChildId = ReadUInt32(); - entry.CLSID = ReadGuid(); - entry.StateBits = ReadUInt32(); - entry.CreationTime = ReadFileTime(); - entry.ModifiedTime = ReadFileTime(); - entry.StartSectorId = ReadUInt32(); + int clampedNameLength = Math.Max(0, Math.Min(DirectoryEntry.NameFieldLength, nameLength - 2)); + string name = Encoding.Unicode.GetString(buffer, 0, clampedNameLength); + + DirectoryEntry entry = new() + { + Id = sid, + Name = name, + Type = ReadStorageType(), + Color = ReadColor(), + LeftSiblingId = ReadUInt32(), + RightSiblingId = ReadUInt32(), + ChildId = ReadUInt32(), + CLSID = ReadGuid(), + StateBits = ReadUInt32(), + CreationTime = ReadFileTime(), + ModifiedTime = ReadFileTime(), + StartSectorId = ReadUInt32() + }; if (version == Version.V3) { entry.StreamLength = ReadUInt32(); if (entry.StreamLength > DirectoryEntry.MaxV3StreamLength) - throw new FormatException($"Stream length {entry.StreamLength} exceeds maximum value {DirectoryEntry.MaxV3StreamLength}"); + throw new FormatException($"Stream length {entry.StreamLength} exceeds maximum value {DirectoryEntry.MaxV3StreamLength}."); ReadUInt32(); // Skip unused 4 bytes } else if (version == Version.V4) @@ -129,6 +138,4 @@ public DirectoryEntry ReadDirectoryEntry(Version version) return entry; } - - public void Seek(long position) => BaseStream.Position = position; } diff --git a/OpenMcdf3/CfbBinaryWriter.cs b/OpenMcdf3/CfbBinaryWriter.cs index f8f08e0a..b041469e 100644 --- a/OpenMcdf3/CfbBinaryWriter.cs +++ b/OpenMcdf3/CfbBinaryWriter.cs @@ -14,11 +14,26 @@ public CfbBinaryWriter(Stream input) { } + public long Position + { + get => BaseStream.Position; + set => BaseStream.Position = value; + } + +#if NETSTANDARD2_1_OR_GREATER + public override void Write(ReadOnlySpan buffer) => BaseStream.Write(buffer); +#endif + public void Write(Guid value) { - // TODO: Avoid heap allocation +#if NETSTANDARD2_1_OR_GREATER + Span localBuffer = stackalloc byte[16]; + value.TryWriteBytes(localBuffer); + Write(localBuffer); +#else byte[] bytes = value.ToByteArray(); - Write(bytes, 0, bytes.Length); + Write(bytes); +#endif } public void Write(DateTime value) @@ -27,8 +42,6 @@ public void Write(DateTime value) Write(fileTime); } - private void WriteBytes(byte[] buffer) => Write(buffer, 0, buffer.Length); - public void Write(Header header) { Write(Header.Signature); @@ -37,8 +50,8 @@ public void Write(Header header) Write(header.MajorVersion); Write(Header.LittleEndian); Write(header.SectorShift); - Write(Header.MiniSectorShift); - WriteBytes(Header.Unused); + Write(header.MiniSectorShift); + Write(Header.Unused); Write(header.DirectorySectorCount); Write(header.FatSectorCount); Write(header.FirstDirectorySectorId); @@ -48,13 +61,16 @@ public void Write(Header header) Write(header.MiniFatSectorCount); Write(header.FirstDifatSectorId); Write(header.DifatSectorCount); + for (int i = 0; i < Header.DifatArrayLength; i++) + Write(header.Difat[i]); } public void Write(DirectoryEntry entry) { + buffer.AsSpan().Clear(); int nameLength = Encoding.Unicode.GetBytes(entry.Name, 0, entry.Name.Length, buffer, 0); - Write(nameLength); Write(buffer, 0, DirectoryEntry.NameFieldLength); + Write((short)(nameLength + 2)); Write((byte)entry.Type); Write((byte)entry.Color); Write(entry.LeftSiblingId); diff --git a/OpenMcdf3/CfbStream.cs b/OpenMcdf3/CfbStream.cs new file mode 100644 index 00000000..473eae7f --- /dev/null +++ b/OpenMcdf3/CfbStream.cs @@ -0,0 +1,96 @@ +namespace OpenMcdf3; + +/// +/// Represents a stream in a compound file. +/// +public sealed class CfbStream : Stream +{ + private readonly IOContext ioContext; + private readonly DirectoryEntry directoryEntry; + private Stream stream; + + internal CfbStream(IOContext ioContext, DirectoryEntry directoryEntry) + { + this.ioContext = ioContext; + this.directoryEntry = directoryEntry; + stream = directoryEntry.StreamLength < Header.MiniStreamCutoffSize + ? new MiniFatStream(ioContext, directoryEntry) + : new FatStream(ioContext, directoryEntry); + } + + protected override void Dispose(bool disposing) + { + stream.Dispose(); + + base.Dispose(disposing); + } + + public override bool CanRead => stream.CanRead; + + public override bool CanSeek => stream.CanSeek; + + public override bool CanWrite => stream.CanWrite; + + public override long Length => stream.Length; + + public override long Position { get => stream.Position; set => stream.Position = value; } + + public override void Flush() => stream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) => stream.Read(buffer, offset, count); + + public override long Seek(long offset, SeekOrigin origin) => stream.Seek(offset, origin); + + public override void SetLength(long value) + { + this.ThrowIfNotWritable(); + + if (value >= Header.MiniStreamCutoffSize && stream is MiniFatStream miniStream) + { + long position = miniStream.Position; + miniStream.Position = 0; + + DirectoryEntry newDirectoryEntry = directoryEntry.Clone(); + FatStream fatStream = new(ioContext, newDirectoryEntry); + fatStream.SetLength(value); // Reserve enough space up front + miniStream.CopyTo(fatStream); + fatStream.Position = position; + stream = fatStream; + + miniStream.SetLength(0); + miniStream.Dispose(); + } + else if (value < Header.MiniStreamCutoffSize && stream is FatStream fatStream) + { + long position = fatStream.Position; + fatStream.Position = 0; + + DirectoryEntry newDirectoryEntry = directoryEntry.Clone(); + MiniFatStream miniFatStream = new(ioContext, newDirectoryEntry); + fatStream.SetLength(value); // Truncate the stream + fatStream.CopyTo(miniFatStream); + miniFatStream.Position = position; + stream = miniFatStream; + + fatStream.SetLength(0); + fatStream.Dispose(); + } + else + { + stream.SetLength(value); + } + } + + public override void Write(byte[] buffer, int offset, int count) + { + ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); + + this.ThrowIfNotWritable(); + + long newPosition = Position + count; + if (newPosition > stream.Length) + SetLength(newPosition); + + stream.Write(buffer, offset, count); + } +} diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index 122e1a45..6bdc87be 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -1,6 +1,4 @@ -using System.Text; - -namespace OpenMcdf3; +namespace OpenMcdf3; /// /// The storage type of a . @@ -34,7 +32,7 @@ internal static class StreamId /// /// Encapsulates data about a or Stream. /// -internal sealed class DirectoryEntry +internal sealed class DirectoryEntry : IEquatable { internal const int Length = 128; internal const int NameFieldLength = 64; @@ -42,21 +40,20 @@ internal sealed class DirectoryEntry internal static readonly DateTime ZeroFileTime = DateTime.FromFileTimeUtc(0); + internal static readonly byte[] Unallocated = new byte[128]; + string name = string.Empty; DateTime creationTime; DateTime modifiedTime; + public uint Id { get; set; } + public string Name { get => name; set { - if (value.Contains(@"\") || value.Contains(@"/") || value.Contains(@":") || value.Contains(@"!")) - throw new ArgumentException("Name cannot contain any of the following characters: '\\', '/', ':','!'", nameof(value)); - - if (Encoding.Unicode.GetByteCount(value) + 2 > NameFieldLength) - throw new ArgumentException($"{value} exceeds maximum encoded length of {NameFieldLength} bytes", nameof(value)); - + ThrowHelper.ThrowIfNameIsInvalid(value); name = value; } } @@ -71,17 +68,17 @@ public string Name /// /// Stream ID of the left sibling. /// - public uint LeftSiblingId { get; set; } + public uint LeftSiblingId { get; set; } = StreamId.NoStream; /// /// Stream ID of the right sibling. /// - public uint RightSiblingId { get; set; } + public uint RightSiblingId { get; set; } = StreamId.NoStream; /// /// Stream ID of the child. /// - public uint ChildId { get; set; } + public uint ChildId { get; set; } = StreamId.NoStream; /// /// GUID for storage objects. @@ -102,7 +99,7 @@ public DateTime CreationTime set { if (Type is StorageType.Stream or StorageType.Root && value != ZeroFileTime) - throw new ArgumentException("Creation time must be zero for streams and root", nameof(value)); + throw new ArgumentException("Creation time must be zero for streams and root.", nameof(value)); creationTime = value; } @@ -117,7 +114,7 @@ public DateTime ModifiedTime set { if (Type is StorageType.Stream && value != ZeroFileTime) - throw new ArgumentException("Modified time must be zero for streams", nameof(value)); + throw new ArgumentException("Modified time must be zero for streams.", nameof(value)); modifiedTime = value; } @@ -126,14 +123,84 @@ public DateTime ModifiedTime /// /// The starting sector location for a stream or the first sector of the mini-stream for the root storage object. /// - public uint StartSectorId { get; set; } + public uint StartSectorId { get; set; } = StreamId.NoStream; /// /// The length of the stream. /// public long StreamLength { get; set; } + public override bool Equals(object? obj) => Equals(obj as DirectoryEntry); + + public bool Equals(DirectoryEntry? other) + { + return other is not null + && Name == other.Name + && Type == other.Type + && Color == other.Color + && LeftSiblingId == other.LeftSiblingId + && RightSiblingId == other.RightSiblingId + && ChildId == other.ChildId + && CLSID == other.CLSID + && StateBits == other.StateBits + && CreationTime == other.CreationTime + && ModifiedTime == other.ModifiedTime + && StartSectorId == other.StartSectorId + && StreamLength == other.StreamLength; + } + + public void RecycleRoot() => Recycle(StorageType.Root, "Root Entry"); + + public void Recycle(StorageType storageType, string name) + { + Type = storageType; + Color = NodeColor.Black; + Name = name; + LeftSiblingId = StreamId.NoStream; + RightSiblingId = StreamId.NoStream; + ChildId = StreamId.NoStream; + StartSectorId = StreamId.NoStream; + StreamLength = 0; + + if (storageType is StorageType.Root) + { + CreationTime = ZeroFileTime; + ModifiedTime = DateTime.UtcNow; + } + if (storageType is StorageType.Storage) + { + DateTime now = DateTime.UtcNow; + CreationTime = now; + ModifiedTime = now; + } + else + { + CreationTime = ZeroFileTime; + ModifiedTime = ZeroFileTime; + } + } + public EntryInfo ToEntryInfo() => new() { Name = Name }; - public override string ToString() => Name; + public override string ToString() => $"{Id}: \"{Name}\""; + + public DirectoryEntry Clone() + { + return new DirectoryEntry + { + Id = Id, + Name = Name, + Type = Type, + Color = Color, + LeftSiblingId = LeftSiblingId, + RightSiblingId = RightSiblingId, + ChildId = ChildId, + CLSID = CLSID, + StateBits = StateBits, + CreationTime = CreationTime, + ModifiedTime = ModifiedTime, + StartSectorId = StreamId.NoStream, + StreamLength = 0 + }; + } } diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index 19c8adba..9e78f0de 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -8,26 +8,25 @@ namespace OpenMcdf3; internal sealed class DirectoryEntryEnumerator : IEnumerator { private readonly IOContext ioContext; - private readonly Version version; - private readonly int entryCount; - private readonly FatChainEnumerator chainEnumerator; - private int entryIndex = -1; + private readonly FatChainEnumerator fatChainEnumerator; + private bool start = true; + private uint index = uint.MaxValue; private DirectoryEntry? current; public DirectoryEntryEnumerator(IOContext ioContext) { this.ioContext = ioContext; - this.version = (Version)ioContext.Header.MajorVersion; - this.entryCount = ioContext.Header.SectorSize / DirectoryEntry.Length; - this.chainEnumerator = new FatChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); + this.fatChainEnumerator = new FatChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); } /// public void Dispose() { - chainEnumerator.Dispose(); + fatChainEnumerator.Dispose(); } + private int EntriesPerSector => ioContext.SectorSize / DirectoryEntry.Length; + /// public DirectoryEntry Current { @@ -45,47 +44,97 @@ public DirectoryEntry Current /// public bool MoveNext() { - if (entryIndex == -1 || entryIndex >= entryCount) + if (start) { - if (!chainEnumerator.MoveNext()) - { - entryIndex = int.MaxValue; - current = null; - return false; - } + start = false; + index = 0; + } - ioContext.Reader.Seek(chainEnumerator.Current.Position); - entryIndex = 0; + uint chainIndex = (uint)Math.DivRem(index, EntriesPerSector, out long entryIndex); + if (!fatChainEnumerator.MoveTo(chainIndex)) + { + current = null; + index = uint.MaxValue; + return false; } - current = ioContext.Reader.ReadDirectoryEntry(version); - entryIndex++; + ioContext.Reader.Position = fatChainEnumerator.CurrentSector.Position + entryIndex * DirectoryEntry.Length; + current = ioContext.Reader.ReadDirectoryEntry(ioContext.Version, index); + index++; return true; } + public DirectoryEntry CreateOrRecycleDirectoryEntry() + { + DirectoryEntry? entry = TryRecycleDirectoryEntry(); + if (entry is not null) + return entry; + + CfbBinaryWriter writer = ioContext.Writer; + uint id = fatChainEnumerator.Extend(); + if (ioContext.Header.FirstDirectorySectorId == SectorType.EndOfChain) + ioContext.Header.FirstDirectorySectorId = id; + + Sector sector = new(id, ioContext.SectorSize); + writer.Position = sector.Position; + int directoryEntriesPerSector = EntriesPerSector; + for (int i = 0; i < directoryEntriesPerSector; i++) + writer.Write(DirectoryEntry.Unallocated); + + entry = TryRecycleDirectoryEntry() + ?? throw new InvalidOperationException("Failed to add or recycle directory entry."); + return entry; + } + + private DirectoryEntry? TryRecycleDirectoryEntry() + { + Reset(); + + while (MoveNext()) + { + if (current!.Type == StorageType.Unallocated) + { + return current; + } + } + + return null; + } + /// /// Gets the for the specified stream ID. /// public DirectoryEntry GetDictionaryEntry(uint streamId) { if (streamId > StreamId.Maximum) - throw new ArgumentException($"Invalid directory entry stream ID: ${streamId:X8}"); + throw new ArgumentException($"Invalid directory entry stream ID: ${streamId:X8}.", nameof(streamId)); - uint chainIndex = (uint)Math.DivRem(streamId, entryCount, out long entryIndex); - if (!chainEnumerator.MoveTo(chainIndex)) - throw new KeyNotFoundException($"Directory entry {streamId} was not found"); + uint chainIndex = (uint)Math.DivRem(streamId, EntriesPerSector, out long entryIndex); + if (!fatChainEnumerator.MoveTo(chainIndex)) + throw new KeyNotFoundException($"Directory entry {streamId} was not found."); - long position = chainEnumerator.Current.Position + entryIndex * DirectoryEntry.Length; - ioContext.Reader.Seek(position); - current = ioContext.Reader.ReadDirectoryEntry(version); + ioContext.Reader.Position = fatChainEnumerator.CurrentSector.Position + entryIndex * DirectoryEntry.Length; + current = ioContext.Reader.ReadDirectoryEntry(ioContext.Version, streamId); return current; } + public void Write(DirectoryEntry entry) + { + uint chainIndex = (uint)Math.DivRem(entry.Id, EntriesPerSector, out long entryIndex); + if (!fatChainEnumerator.MoveTo(chainIndex)) + throw new KeyNotFoundException($"Directory entry {entry.Id} was not found."); + + CfbBinaryWriter writer = ioContext.Writer; + writer.Position = fatChainEnumerator.CurrentSector.Position + entryIndex * DirectoryEntry.Length; + writer.Write(entry); + } + /// public void Reset() { - chainEnumerator.Reset(); - entryIndex = -1; + fatChainEnumerator.Reset(); + start = true; current = null; + index = uint.MaxValue; } } diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index 73c8ce2a..f5a7490f 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Diagnostics; namespace OpenMcdf3; @@ -7,7 +8,9 @@ namespace OpenMcdf3; /// internal sealed class DirectoryTreeEnumerator : IEnumerator { - private readonly DirectoryEntry? child; + private readonly IOContext ioContext; + private readonly DirectoryEntry root; + private DirectoryEntry? child; private readonly Stack stack = new(); private readonly DirectoryEntryEnumerator directoryEntryEnumerator; DirectoryEntry? current; @@ -15,6 +18,8 @@ internal sealed class DirectoryTreeEnumerator : IEnumerator internal DirectoryTreeEnumerator(IOContext ioContext, DirectoryEntry root) { directoryEntryEnumerator = new(ioContext); + this.ioContext = ioContext; + this.root = root; if (root.ChildId != StreamId.NoStream) child = directoryEntryEnumerator.GetDictionaryEntry(root.ChildId); PushLeft(child); @@ -75,4 +80,62 @@ private void PushLeft(DirectoryEntry? node) node = node.LeftSiblingId == StreamId.NoStream ? null : directoryEntryEnumerator.GetDictionaryEntry(node.LeftSiblingId); } } + + public bool MoveTo(StorageType type, string name) + { + Reset(); + + while (MoveNext()) + { + if (Current.Type == type && Current.Name == name) + return true; + } + + return false; + } + + public DirectoryEntry? TryGetDirectoryEntry(StorageType type, string name) + { + if (MoveTo(type, name)) + return Current; + return null; + } + + public DirectoryEntry Add(StorageType storageType, string name) + { + if (MoveTo(storageType, name)) + throw new IOException($"{storageType} \"{name}\" already exists."); + + DirectoryEntry entry = directoryEntryEnumerator.CreateOrRecycleDirectoryEntry(); + entry.Recycle(storageType, name); + + Add(entry); + + return entry; + } + + void Add(DirectoryEntry entry) + { + Reset(); + + entry.Color = NodeColor.Black; + directoryEntryEnumerator.Write(entry); + + if (root.ChildId == StreamId.NoStream) + { + Debug.Assert(child is null); + root.ChildId = entry.Id; + directoryEntryEnumerator.Write(root); + child = entry; + } + else + { + Debug.Assert(child is not null); + DirectoryEntry node = child!; + while (node.LeftSiblingId != StreamId.NoStream) + node = directoryEntryEnumerator.GetDictionaryEntry(node.LeftSiblingId); + node.LeftSiblingId = entry.Id; + directoryEntryEnumerator.Write(node); + } + } } diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs new file mode 100644 index 00000000..5e2ec4c8 --- /dev/null +++ b/OpenMcdf3/Fat.cs @@ -0,0 +1,121 @@ +using System.Collections; +using System.Diagnostics; + +namespace OpenMcdf3; + +/// +/// Encapsulates getting and setting entries in the FAT. +/// +internal sealed class Fat : IEnumerable, IDisposable +{ + private readonly IOContext ioContext; + private readonly FatSectorEnumerator fatSectorEnumerator; + + internal int FatElementsPerSector => ioContext.SectorSize / sizeof(uint); + + internal int DifatElementsPerSector => ioContext.SectorSize / sizeof(uint) - 1; + + public Fat(IOContext ioContext) + { + this.ioContext = ioContext; + fatSectorEnumerator = new(ioContext); + } + + public void Dispose() + { + fatSectorEnumerator.Dispose(); + } + + public uint this[uint key] + { + get + { + if (!TryGetValue(key, out uint value)) + throw new KeyNotFoundException($"FAT index not found: {key}."); + return value; + + } + set + { + if (!TrySetValue(key, value)) + throw new KeyNotFoundException($"FAT index not found: {key}."); + } + } + + uint GetSectorIndexAndElementOffset(uint key, out long elementIndex) + { + int DifatArrayElementCount = Header.DifatArrayLength * FatElementsPerSector; + if (key < DifatArrayElementCount) + return (uint)Math.DivRem(key, FatElementsPerSector, out elementIndex); + + return (uint)Math.DivRem(key - DifatArrayElementCount, DifatElementsPerSector, out elementIndex); + } + + public bool TryGetValue(uint key, out uint value) + { + ThrowHelper.ThrowIfSectorIdIsInvalid(key); + + uint sectorId = GetSectorIndexAndElementOffset(key, out long elementIndex); + bool ok = fatSectorEnumerator.MoveTo(sectorId); + if (!ok) + { + value = uint.MaxValue; + return false; + } + + CfbBinaryReader reader = ioContext.Reader; + reader.Position = fatSectorEnumerator.Current.Position + elementIndex * sizeof(uint); + value = reader.ReadUInt32(); + return true; + } + + public bool TrySetValue(uint key, uint value) + { + ThrowHelper.ThrowIfSectorIdIsInvalid(key); + + uint fatSectorIndex = GetSectorIndexAndElementOffset(key, out long elementIndex); + if (!fatSectorEnumerator.MoveTo(fatSectorIndex)) + return false; + + CfbBinaryWriter writer = ioContext.Writer; + writer.Position = fatSectorEnumerator.Current.Position + elementIndex * sizeof(uint); + writer.Write(value); + return true; + } + + /// + /// Adds a new entry to the FAT. + /// + /// The index of the new entry in the FAT + public uint Add(FatEnumerator fatEnumerator, uint startIndex) + { + ThrowHelper.ThrowIfSectorIdIsInvalid(startIndex); + + bool movedToFreeEntry = fatEnumerator.MoveTo(startIndex) && fatEnumerator.MoveNextFreeEntry(); + if (!movedToFreeEntry) + { + uint newSectorId = fatSectorEnumerator.Add(); + + bool ok = fatEnumerator.MoveTo(newSectorId); + Debug.Assert(ok); + + ok = fatEnumerator.MoveNextFreeEntry(); + Debug.Assert(ok); + } + + FatEntry entry = fatEnumerator.Current; + ioContext.ExtendStreamLength(fatEnumerator.CurrentSector.EndPosition); + this[entry.Index] = SectorType.EndOfChain; + return entry.Index; + } + + public IEnumerator GetEnumerator() => new FatEnumerator(ioContext); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + internal void Trace(TextWriter writer) + { + using FatEnumerator fatEnumerator = new(ioContext); + fatEnumerator.Trace(writer); + } +} diff --git a/OpenMcdf3/FatChainEntry.cs b/OpenMcdf3/FatChainEntry.cs new file mode 100644 index 00000000..ba7302fc --- /dev/null +++ b/OpenMcdf3/FatChainEntry.cs @@ -0,0 +1,10 @@ +namespace OpenMcdf3; + +internal record struct FatChainEntry(uint Index, uint Value) +{ + internal static readonly FatChainEntry Invalid = new(uint.MaxValue, SectorType.EndOfChain); + + public readonly bool IsFreeOrEndOfChain => SectorType.IsFreeOrEndOfChain(Value); + + public override readonly string ToString() => $"#{Index}: {Value}"; +} diff --git a/OpenMcdf3/FatChainEnumerator.cs b/OpenMcdf3/FatChainEnumerator.cs index 567b2cdb..1ccae887 100644 --- a/OpenMcdf3/FatChainEnumerator.cs +++ b/OpenMcdf3/FatChainEnumerator.cs @@ -1,17 +1,20 @@ using System.Collections; +using System.Diagnostics; namespace OpenMcdf3; /// /// Enumerates the s in a FAT sector chain. /// -internal sealed class FatChainEnumerator : IEnumerator +internal sealed class FatChainEnumerator : IEnumerator { private readonly IOContext ioContext; - private readonly FatSectorEnumerator fatEnumerator; - private readonly uint startId; + private readonly FatEnumerator fatEnumerator; + private uint startId; private bool start = true; - private Sector current = Sector.EndOfChain; + private uint index = uint.MaxValue; + private FatChainEntry current = FatChainEntry.Invalid; + private long length = -1; public FatChainEnumerator(IOContext ioContext, uint startSectorId) { @@ -26,17 +29,16 @@ public void Dispose() fatEnumerator.Dispose(); } - /// - /// The index within the FAT sector chain, or if the enumeration has not started. - /// - public uint Index { get; private set; } = uint.MaxValue; + public uint StartId => startId; + + public Sector CurrentSector => new(Current.Value, ioContext.SectorSize); /// - public Sector Current + public FatChainEntry Current { get { - if (current.IsEndOfChain) + if (index == uint.MaxValue) throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); return current; } @@ -50,24 +52,44 @@ public bool MoveNext() { if (start) { - current = new(startId, ioContext.Header.SectorSize); - Index = 0; + if (startId is SectorType.EndOfChain or SectorType.Free) + { + index = uint.MaxValue; + current = FatChainEntry.Invalid; + return false; + } + + index = 0; + current = new(index, startId); start = false; + return true; } - else if (!current.IsEndOfChain) + + if (current.IsFreeOrEndOfChain || current == FatChainEntry.Invalid) { - uint sectorId = GetNextFatSectorId(current.Id); - current = new(sectorId, ioContext.Header.SectorSize); - Index++; + index = uint.MaxValue; + current = FatChainEntry.Invalid; + return false; } - if (current.IsEndOfChain) + uint value = ioContext.Fat[current.Value]; + if (value is SectorType.EndOfChain) { - current = Sector.EndOfChain; - Index = uint.MaxValue; + index = uint.MaxValue; + current = FatChainEntry.Invalid; return false; } + index++; + if (index > SectorType.Maximum) + { + // If the index is greater than the maximum, then the chain must contain a loop + index = uint.MaxValue; + current = FatChainEntry.Invalid; + throw new IOException("FAT sector chain is corrupt"); + } + + current = new(index, value); return true; } @@ -78,10 +100,10 @@ public bool MoveNext() /// true if the enumerator was successfully advanced to the given index public bool MoveTo(uint index) { - if (index < Index) + if (index < this.index) Reset(); - while (start || Index < index) + while (start || this.index < index) { if (!MoveNext()) return false; @@ -90,31 +112,122 @@ public bool MoveTo(uint index) return true; } - /// - public void Reset() + public long GetLength() { - fatEnumerator.Reset(); - start = true; - current = Sector.EndOfChain; - Index = uint.MaxValue; + if (length == -1) + { + Reset(); + length = 0; + while (MoveNext()) + { + length++; + } + } + + return length; + } + + /// + /// Extends the chain by one + /// + /// The ID of the new sector + public uint Extend() + { + if (startId == SectorType.EndOfChain) + { + startId = ioContext.Fat.Add(fatEnumerator, 0); + return startId; + } + + uint lastId = startId; + while (MoveNext()) + { + lastId = current.Value; + } + + uint id = ioContext.Fat.Add(fatEnumerator, lastId); + ioContext.Fat[lastId] = id; + return id; } /// - /// Gets the next sector ID in the FAT chain. + /// Returns the ID of the first sector in the chain. /// - uint GetNextFatSectorId(uint id) + public void Extend(uint requiredChainLength) { - if (id > SectorType.Maximum) - throw new ArgumentException("Invalid sector ID", nameof(id)); - - int elementCount = ioContext.Header.SectorSize / sizeof(uint); - uint sectorId = (uint)Math.DivRem(id, elementCount, out long sectorOffset); - if (!fatEnumerator.MoveTo(sectorId)) - throw new ArgumentException("Invalid sector ID", nameof(id)); - - long position = fatEnumerator.Current.Position + sectorOffset * sizeof(uint); - ioContext.Reader.Seek(position); - uint nextId = ioContext.Reader.ReadUInt32(); - return nextId; + uint chainLength = (uint)GetLength(); + if (chainLength >= requiredChainLength) + throw new ArgumentException("The chain is already longer than required.", nameof(requiredChainLength)); + + if (startId == StreamId.NoStream) + { + startId = ioContext.Fat.Add(fatEnumerator, 0); + chainLength = 1; + } + + bool ok = MoveTo(chainLength - 1); + Debug.Assert(ok); + + uint lastId = current.Value; + ok = fatEnumerator.MoveTo(lastId); + Debug.Assert(ok); + while (chainLength < requiredChainLength) + { + uint id = ioContext.Fat.Add(fatEnumerator, lastId); + ioContext.Fat[lastId] = id; + lastId = id; + chainLength++; + } + + this.length = requiredChainLength; + } + + public void Shrink(uint requiredChainLength) + { + uint chainLength = (uint)GetLength(); + if (chainLength <= requiredChainLength) + throw new ArgumentException("The chain is already shorter than required.", nameof(requiredChainLength)); + + Reset(); + + uint lastId = current.Value; + while (MoveNext()) + { + if (lastId is not SectorType.EndOfChain and not SectorType.Free) + { + if (index == requiredChainLength) + ioContext.Fat[lastId] = SectorType.EndOfChain; + else if (index > requiredChainLength) + ioContext.Fat[lastId] = SectorType.Free; + } + + lastId = current.Value; + } + + ioContext.Fat[lastId] = SectorType.Free; + + if (requiredChainLength == 0) + { + startId = StreamId.NoStream; + } + +#if DEBUG + this.length = -1; + this.length = GetLength(); + Debug.Assert(length == requiredChainLength); +#endif + + this.length = requiredChainLength; + } + + /// + public void Reset() => Reset(startId); + + public void Reset(uint startSectorId) + { + startId = startSectorId; + start = true; + index = uint.MaxValue; + current = FatChainEntry.Invalid; } } diff --git a/OpenMcdf3/FatEntry.cs b/OpenMcdf3/FatEntry.cs new file mode 100644 index 00000000..ecef6605 --- /dev/null +++ b/OpenMcdf3/FatEntry.cs @@ -0,0 +1,13 @@ +namespace OpenMcdf3; + +/// +/// Encapsulates an entry in the File Allocation Table (FAT). +/// +internal record struct FatEntry(uint Index, uint Value) +{ + internal static readonly FatEntry Invalid = new(uint.MaxValue, uint.MaxValue); + + public readonly bool IsFree => Value == SectorType.Free; + + public readonly override string ToString() => $"#{Index}: {Value}"; +} diff --git a/OpenMcdf3/FatEnumerator.cs b/OpenMcdf3/FatEnumerator.cs new file mode 100644 index 00000000..d362c6e2 --- /dev/null +++ b/OpenMcdf3/FatEnumerator.cs @@ -0,0 +1,132 @@ +using System.Collections; + +namespace OpenMcdf3; + +/// +/// Enumerates the entries in a FAT. +/// +internal class FatEnumerator : IEnumerator +{ + readonly IOContext ioContext; + bool start = true; + uint index = uint.MaxValue; + uint value = uint.MaxValue; + + public FatEnumerator(IOContext ioContext) + { + this.ioContext = ioContext; + } + + /// + public void Dispose() + { + } + + public Sector CurrentSector + { + get + { + if (index == uint.MaxValue) + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + return new(index, ioContext.SectorSize); + } + } + + /// + public FatEntry Current + { + get + { + if (index == uint.MaxValue) + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + return new(index, value); + } + } + + /// + object IEnumerator.Current => Current; + + /// + public bool MoveNext() + { + if (start) + { + start = false; + return MoveTo(0); + } + + if (index >= SectorType.Maximum) + return false; + + uint next = index + 1; + return MoveTo(next); + } + + public bool MoveTo(uint index) + { + ThrowHelper.ThrowIfSectorIdIsInvalid(index); + + start = false; + if (this.index == index) + return true; + + if (ioContext.Fat.TryGetValue(index, out value)) + { + this.index = index; + return true; + } + + this.index = uint.MaxValue; + return false; + } + + public bool MoveNextFreeEntry() + { + while (MoveNext()) + { + if (value == SectorType.Free) + return true; + } + + return false; + } + + /// + public void Reset() + { + start = true; + index = uint.MaxValue; + value = uint.MaxValue; + } + + internal void Trace(TextWriter writer) + { + Reset(); + + byte[] data = new byte[ioContext.SectorSize]; + + Stream baseStream = ioContext.Reader.BaseStream; + + writer.WriteLine("Start of FAT ================="); + + while (MoveNext()) + { + FatEntry current = Current; + if (current.IsFree) + { + writer.WriteLine($"{current}"); + } + else + { + baseStream.Position = CurrentSector.Position; + baseStream.ReadExactly(data, 0, data.Length); + string hex = BitConverter.ToString(data); + writer.WriteLine($"{current}: {hex}"); + } + } + + writer.WriteLine("End of FAT ==================="); + } + + public override string ToString() => $"{Current}"; +} diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index f9530a12..9ddf0cb4 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -9,9 +9,8 @@ internal sealed class FatSectorEnumerator : IEnumerator { private readonly IOContext ioContext; private bool start = true; - private uint id = SectorType.EndOfChain; + private uint index = uint.MaxValue; private uint difatSectorId; - private uint difatSectorElementIndex = 0; private Sector current = Sector.EndOfChain; public FatSectorEnumerator(IOContext ioContext) @@ -31,7 +30,7 @@ public Sector Current { get { - if (current.IsEndOfChain) + if (current.Id == SectorType.EndOfChain) throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); return current; } @@ -43,42 +42,30 @@ public Sector Current /// public bool MoveNext() { - if (start) - { - id = uint.MaxValue; - start = false; - } - - id++; + start = false; - if (id < ioContext.Header.FatSectorCount && id < Header.DifatArrayLength) + uint nextIndex = index + 1; + if (nextIndex < ioContext.Header.FatSectorCount && nextIndex < Header.DifatArrayLength) // Include the free entries { - uint id = ioContext.Header.Difat[this.id]; - current = new Sector(id, ioContext.Header.SectorSize); + uint id = ioContext.Header.Difat[nextIndex]; + index = nextIndex; + current = new Sector(id, ioContext.SectorSize); return true; } if (difatSectorId == SectorType.EndOfChain) { + index = uint.MaxValue; current = Sector.EndOfChain; - id = SectorType.EndOfChain; return false; } - int difatElementCount = ioContext.Header.SectorSize / sizeof(uint) - 1; - Sector difatSector = new(difatSectorId, ioContext.Header.SectorSize); - long position = difatSector.Position + difatSectorElementIndex * sizeof(uint); - ioContext.Reader.Seek(position); - uint sectorId = ioContext.Reader.ReadUInt32(); - current = new Sector(sectorId, ioContext.Header.SectorSize); - difatSectorElementIndex++; - id++; + Sector difatSector = new(difatSectorId, ioContext.SectorSize); + index = nextIndex; + current = difatSector; - if (difatSectorElementIndex == difatElementCount) - { - difatSectorId = ioContext.Reader.ReadUInt32(); - difatSectorElementIndex = 0; - } + ioContext.Reader.Position = difatSector.EndPosition - sizeof(uint); + difatSectorId = ioContext.Reader.ReadUInt32(); return true; } @@ -86,15 +73,32 @@ public bool MoveNext() /// /// Moves the enumerator to the specified sector. /// - public bool MoveTo(uint sectorId) + public bool MoveTo(uint index) { - if (sectorId > SectorType.Maximum) - throw new ArgumentOutOfRangeException(nameof(sectorId)); + ThrowHelper.ThrowIfSectorIdIsInvalid(index); - if (sectorId < id) - Reset(); + start = false; - while (start || id < sectorId) + if (index == this.index) + return true; + + if (index >= ioContext.Header.FatSectorCount + ioContext.Header.DifatSectorCount) + { + this.index = uint.MaxValue; + current = Sector.EndOfChain; + return false; + } + + if (this.index < Header.DifatArrayLength || index < this.index) + { + // Jump as close as possible + this.index = Math.Min(index, Header.DifatArrayLength - 1); + uint id = ioContext.Header.Difat[this.index]; + current = new(id, ioContext.SectorSize); + difatSectorId = ioContext.Header.FirstDifatSectorId; + } + + while (this.index < index) { if (!MoveNext()) return false; @@ -107,9 +111,74 @@ public bool MoveTo(uint sectorId) public void Reset() { start = true; - id = SectorType.EndOfChain; + index = uint.MaxValue; difatSectorId = ioContext.Header.FirstDifatSectorId; - difatSectorElementIndex = 0; current = Sector.EndOfChain; } + + (uint lastIndex, Sector lastSector) MoveToEnd() + { + Reset(); + + uint lastIndex = uint.MaxValue; + Sector lastSector = Sector.EndOfChain; + while (MoveNext()) + { + lastIndex = index; + lastSector = current; + } + + return (lastIndex, lastSector); + } + + /// + /// Extends the FAT by adding a new sector. + /// + /// The ID of the new sector that was added + public uint Add() + { + // No FAT sectors are free, so add a new one + Header header = ioContext.Header; + (uint lastIndex, Sector lastSector) = MoveToEnd(); + uint nextIndex = lastIndex + 1; + long id = Math.Max(0, (ioContext.Reader.BaseStream.Length - ioContext.SectorSize) / ioContext.SectorSize); // TODO: Check + Sector newSector = new((uint)id, ioContext.SectorSize); + + CfbBinaryWriter writer = ioContext.Writer; + writer.Position = newSector.Position; + writer.Write(SectorDataCache.GetFatEntryData(newSector.Length)); + + uint sectorType; + if (nextIndex < Header.DifatArrayLength) + { + index = nextIndex; + current = newSector; + sectorType = SectorType.Fat; + + header.Difat[nextIndex] = newSector.Id; + header.FatSectorCount++; // TODO: Check + } + else + { + index = nextIndex; + current = newSector; + difatSectorId = newSector.Id; + sectorType = SectorType.Difat; + + writer.Position = newSector.EndPosition - sizeof(uint); + writer.Write(SectorType.EndOfChain); + + writer.Position = lastSector.EndPosition - sizeof(uint); + writer.Write(newSector.Id); + + // Chain the sector + if (header.FirstDifatSectorId == SectorType.EndOfChain) + header.FirstDifatSectorId = newSector.Id; + header.DifatSectorCount++; + } + + ioContext.Fat[newSector.Id] = sectorType; + + return newSector.Id; + } } diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index 547b52fd..cf8f506e 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -7,7 +7,6 @@ internal class FatStream : Stream { readonly IOContext ioContext; readonly FatChainEnumerator chain; - readonly long length; long position; bool disposed; @@ -15,13 +14,14 @@ internal FatStream(IOContext ioContext, DirectoryEntry directoryEntry) { this.ioContext = ioContext; DirectoryEntry = directoryEntry; - length = directoryEntry.StreamLength; chain = new(ioContext, directoryEntry.StartSectorId); } /// internal DirectoryEntry DirectoryEntry { get; private set; } + internal long ChainCapacity => ((Length + ioContext.SectorSize - 1) / ioContext.SectorSize) * ioContext.SectorSize; + /// public override bool CanRead => true; @@ -29,10 +29,10 @@ internal FatStream(IOContext ioContext, DirectoryEntry directoryEntry) public override bool CanSeek => true; /// - public override bool CanWrite => false; + public override bool CanWrite => ioContext.CanWrite; /// - public override long Length => length; + public override long Length => DirectoryEntry.StreamLength; /// public override long Position @@ -54,30 +54,27 @@ protected override void Dispose(bool disposing) } /// - public override void Flush() => this.ThrowIfDisposed(disposed); + public override void Flush() + { + this.ThrowIfDisposed(disposed); + ioContext.Writer!.Flush(); // TODO: Check validity + } /// public override int Read(byte[] buffer, int offset, int count) { - if (buffer is null) - throw new ArgumentNullException(nameof(buffer)); - - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset), "Offset must be a non-negative number"); - - if ((uint)count > buffer.Length - offset) - throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection"); + ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); this.ThrowIfDisposed(disposed); if (count == 0) return 0; - int maxCount = (int)Math.Min(Math.Max(length - position, 0), int.MaxValue); + int maxCount = (int)Math.Min(Math.Max(Length - position, 0), int.MaxValue); if (maxCount == 0) return 0; - uint chainIndex = (uint)Math.DivRem(position, ioContext.Header.SectorSize, out long sectorOffset); + uint chainIndex = (uint)Math.DivRem(position, ioContext.SectorSize, out long sectorOffset); if (!chain.MoveTo(chainIndex)) return 0; @@ -85,10 +82,10 @@ public override int Read(byte[] buffer, int offset, int count) int readCount = 0; do { - Sector sector = chain.Current; + Sector sector = chain.CurrentSector; int remaining = realCount - readCount; long readLength = Math.Min(remaining, sector.Length - sectorOffset); - ioContext.Reader.Seek(sector.Position + sectorOffset); + ioContext.Reader.Position = sector.Position + sectorOffset; int localOffset = offset + readCount; int read = ioContext.Reader.Read(buffer, localOffset, (int)readLength); if (read == 0) @@ -112,19 +109,19 @@ public override long Seek(long offset, SeekOrigin origin) { case SeekOrigin.Begin: if (offset < 0) - throw new IOException("Seek before origin"); + ThrowHelper.ThrowSeekBeforeOrigin(); position = offset; break; case SeekOrigin.Current: if (position + offset < 0) - throw new IOException("Seek before origin"); + ThrowHelper.ThrowSeekBeforeOrigin(); position += offset; break; case SeekOrigin.End: if (Length - offset < 0) - throw new IOException("Seek before origin"); + ThrowHelper.ThrowSeekBeforeOrigin(); position = Length - offset; break; @@ -136,8 +133,60 @@ public override long Seek(long offset, SeekOrigin origin) } /// - public override void SetLength(long value) => throw new NotSupportedException(); + public override void SetLength(long value) + { + this.ThrowIfNotWritable(); + + uint requiredChainLength = (uint)((value + ioContext.SectorSize - 1) / ioContext.SectorSize); + if (value > ChainCapacity) + chain.Extend(requiredChainLength); + else if (value <= ChainCapacity - ioContext.SectorSize) + chain.Shrink(requiredChainLength); + + if (DirectoryEntry.StartSectorId != chain.StartId || DirectoryEntry.StreamLength != value) + { + DirectoryEntry.StartSectorId = chain.StartId; + DirectoryEntry.StreamLength = value; + ioContext.Write(DirectoryEntry); + } + } /// - public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override void Write(byte[] buffer, int offset, int count) + { + ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); + + this.ThrowIfNotWritable(); + + if (count == 0) + return; + + if (position + count > ChainCapacity) + SetLength(position + count); + + uint chainIndex = (uint)Math.DivRem(position, ioContext.SectorSize, out long sectorOffset); + if (!chain.MoveTo(chainIndex)) + throw new InvalidOperationException($"Failed to move to FAT chain index: {chainIndex}"); + + CfbBinaryWriter writer = ioContext.Writer!; + int writeCount = 0; + do + { + Sector sector = chain.CurrentSector; + writer.Position = sector.Position + sectorOffset; + int remaining = count - writeCount; + int localOffset = offset + writeCount; + long writeLength = Math.Min(remaining, sector.Length - sectorOffset); + writer.Write(buffer, localOffset, (int)writeLength); + position += writeLength; + writeCount += (int)writeLength; + if (position > Length) + DirectoryEntry.StreamLength = position; + sectorOffset = 0; + if (writeCount >= count) + return; + } while (chain.MoveNext()); + + throw new InvalidOperationException($"End of FAT chain was reached"); + } } diff --git a/OpenMcdf3/Header.cs b/OpenMcdf3/Header.cs index 93febf4f..7225028f 100644 --- a/OpenMcdf3/Header.cs +++ b/OpenMcdf3/Header.cs @@ -1,16 +1,17 @@ -namespace OpenMcdf3; + +namespace OpenMcdf3; /// /// The structure at the beginning of a compound file. /// -internal sealed class Header +internal sealed class Header : IEquatable { internal const int DifatArrayLength = 109; internal const ushort ExpectedMinorVersion = 0x003E; internal const ushort LittleEndian = 0xFFFE; internal const ushort SectorShiftV3 = 0x0009; internal const ushort SectorShiftV4 = 0x000C; - internal const short MiniSectorShift = 6; + internal const ushort ExpectedMiniSectorShift = 6; internal const uint MiniStreamCutoffSize = 4096; /// @@ -22,6 +23,7 @@ internal sealed class Header private ushort majorVersion; private ushort sectorShift = SectorShiftV3; + private ushort miniSectorShift = ExpectedMiniSectorShift; /// /// Reserved and unused class ID. @@ -54,16 +56,28 @@ public ushort SectorShift get => sectorShift; set { if (MajorVersion == 3 && value != SectorShiftV3) - throw new FormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV3:X4} is supported for Major Version 3"); + throw new FormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV3:X4} is supported for Major Version 3."); if (MajorVersion == 4 && value != SectorShiftV4) - throw new FormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV4:X4} is supported for Major Version 4"); + throw new FormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV4:X4} is supported for Major Version 4."); sectorShift = value; } } + public ushort MiniSectorShift + { + get => miniSectorShift; + set + { + if (value != ExpectedMiniSectorShift) + throw new FormatException($"Unsupported sector shift {value:X4}. Only {ExpectedMiniSectorShift:X4} is supported."); + + miniSectorShift = value; + } + } + /// - /// The number of directory sectors in the compound file. + /// The number of directory sectors in the compound file (not used in V3). /// public uint DirectorySectorCount { get; set; } @@ -98,7 +112,7 @@ public ushort SectorShift public uint FirstDifatSectorId { get; set; } = SectorType.EndOfChain; /// - /// The number of DIFACT sectors in the compound file. + /// The number of DIFAT sectors in the compound file. /// public uint DifatSectorCount { get; set; } @@ -107,17 +121,44 @@ public ushort SectorShift /// public uint[] Difat { get; } = new uint[DifatArrayLength]; - /// - /// The size of a regular sector. - /// - public int SectorSize => 1 << SectorShift; - public Header(Version version = Version.V3) { MajorVersion = (ushort)version; + MinorVersion = ExpectedMinorVersion; + SectorShift = version switch + { + Version.V3 => SectorShiftV3, + Version.V4 => SectorShiftV4, + _ => throw new FormatException($"Unsupported version: {version}.") + }; + FirstDirectorySectorId = SectorType.EndOfChain; + DirectorySectorCount = 0; // Not used in v3 + FatSectorCount = 0; for (int i = 0; i < Difat.Length; i++) { Difat[i] = SectorType.Free; } } + + public override bool Equals(object? obj) => Equals(obj as Header); + + public bool Equals(Header? other) + { + return other is not null + && CLSID == other.CLSID + && MinorVersion == other.MinorVersion + && MajorVersion == other.MajorVersion + && SectorShift == other.SectorShift + && DirectorySectorCount == other.DirectorySectorCount + && FatSectorCount == other.FatSectorCount + && FirstDirectorySectorId == other.FirstDirectorySectorId + && TransactionSignature == other.TransactionSignature + && FirstMiniFatSectorId == other.FirstMiniFatSectorId + && MiniFatSectorCount == other.MiniFatSectorCount + && FirstDifatSectorId == other.FirstDifatSectorId + && DifatSectorCount == other.DifatSectorCount + && Difat.SequenceEqual(other.Difat); + } + + public override string ToString() => $"MajorVersion: {MajorVersion}, MinorVersion: {MinorVersion}, FirstDirectorySectorId: {FirstDirectorySectorId}, FirstMiniFatSectorId: {FirstMiniFatSectorId}"; } diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index c9619a00..817dc110 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -12,46 +12,123 @@ enum IOContextFlags /// internal sealed class IOContext : IDisposable { + readonly DirectoryEntryEnumerator directoryEnumerator; + readonly CfbBinaryWriter? writer; + MiniFat? miniFat; + FatStream? miniStream; + public Header Header { get; } public CfbBinaryReader Reader { get; } - public CfbBinaryWriter? Writer { get; } + public CfbBinaryWriter Writer + { + get + { + if (writer is null) + throw new InvalidOperationException("Stream is not writable"); + return writer; + } + } + + public Fat Fat { get; } public DirectoryEntry RootEntry { get; } + public MiniFat MiniFat + { + get + { + miniFat ??= new(this); + return miniFat; + } + } + + public FatStream MiniStream + { + get + { + miniStream ??= new(this, RootEntry); + return miniStream; + } + } + + public bool CanWrite => writer is not null; + public bool IsDisposed { get; private set; } + /// + /// The size of a regular sector. + /// + public int SectorSize { get; } + + public int MiniSectorSize { get; } + + public Version Version => (Version)Header.MajorVersion; + public IOContext(Header header, CfbBinaryReader reader, CfbBinaryWriter? writer, IOContextFlags contextFlags = IOContextFlags.None) { + if (contextFlags.HasFlag(IOContextFlags.Create) && writer is null) + throw new ArgumentNullException(nameof(writer), "A writer is required to create a new compound file."); + Header = header; Reader = reader; - Writer = writer; - RootEntry = contextFlags.HasFlag(IOContextFlags.Create) - ? new DirectoryEntry() - : EnumerateDirectoryEntries().First(); - // TODO: Improve root directory entry validation + this.writer = writer; + + SectorSize = 1 << header.SectorShift; + MiniSectorSize = 1 << header.MiniSectorShift; + + Fat = new(this); + directoryEnumerator = new(this); + + if (contextFlags.HasFlag(IOContextFlags.Create)) + { + RootEntry = directoryEnumerator.CreateOrRecycleDirectoryEntry(); + RootEntry.RecycleRoot(); + + WriteHeader(); + Write(RootEntry); + } + else + { + if (!directoryEnumerator.MoveNext()) + throw new FormatException("Root directory entry not found."); + RootEntry = directoryEnumerator.Current; + } } public void Dispose() { if (!IsDisposed) { + if (CanWrite) + WriteHeader(); + miniStream?.Dispose(); + miniFat?.Dispose(); + directoryEnumerator.Dispose(); + Fat.Dispose(); + writer?.Dispose(); Reader.Dispose(); - Writer?.Dispose(); IsDisposed = true; } } - /// - /// Enumerates all the instances in the compound file. - /// - public IEnumerable EnumerateDirectoryEntries() + public void ExtendStreamLength(long length) { - this.ThrowIfDisposed(IsDisposed); + Stream baseStream = Writer.BaseStream; + if (baseStream.Length < length) + baseStream.SetLength(length); + } - using DirectoryEntryEnumerator directoryEntriesEnumerator = new(this); - while (directoryEntriesEnumerator.MoveNext()) - yield return directoryEntriesEnumerator.Current; + public void WriteHeader() + { + CfbBinaryWriter writer = Writer; + writer.Seek(0, SeekOrigin.Begin); + writer.Write(Header); + } + + public void Write(DirectoryEntry entry) + { + directoryEnumerator.Write(entry); } } diff --git a/OpenMcdf3/MiniFat.cs b/OpenMcdf3/MiniFat.cs new file mode 100644 index 00000000..bab51843 --- /dev/null +++ b/OpenMcdf3/MiniFat.cs @@ -0,0 +1,106 @@ +using System.Collections; +using System.Diagnostics; + +namespace OpenMcdf3; + +/// +/// Encapsulates getting and setting entries in the mini FAT. +/// +internal sealed class MiniFat : IEnumerable, IDisposable +{ + private readonly IOContext ioContext; + private readonly FatChainEnumerator fatChainEnumerator; + + internal int ElementsPerSector => ioContext.SectorSize / sizeof(uint); + + public MiniFat(IOContext ioContext) + { + this.ioContext = ioContext; + fatChainEnumerator = new(ioContext, ioContext.Header.FirstMiniFatSectorId); + } + + public void Dispose() => fatChainEnumerator.Dispose(); + + public IEnumerator GetEnumerator() => new MiniFatEnumerator(ioContext); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public uint this[uint key] + { + get + { + if (!TryGetValue(key, out uint value)) + throw new KeyNotFoundException($"Mini FAT index not found: {key}."); + return value; + } + set + { + ThrowHelper.ThrowIfSectorIdIsInvalid(key); + + uint fatSectorIndex = (uint)Math.DivRem(key, ElementsPerSector, out long elementIndex); + if (!fatChainEnumerator.MoveTo(fatSectorIndex)) + throw new KeyNotFoundException($"Mini FAT index not found: {fatSectorIndex}."); + + CfbBinaryWriter writer = ioContext.Writer; + writer.Position = fatChainEnumerator.CurrentSector.Position + elementIndex * sizeof(uint); + writer.Write(value); + } + } + + public bool TryGetValue(uint key, out uint value) + { + ThrowHelper.ThrowIfSectorIdIsInvalid(key); + + uint fatSectorIndex = (uint)Math.DivRem(key, ElementsPerSector, out long elementIndex); + bool ok = fatChainEnumerator.MoveTo(fatSectorIndex); + if (!ok) + { + value = uint.MaxValue; + return false; + } + + CfbBinaryReader reader = ioContext.Reader; + reader.Position = fatChainEnumerator.CurrentSector.Position + elementIndex * sizeof(uint); + value = reader.ReadUInt32(); + return true; + } + + public uint Add(MiniFatEnumerator miniFatEnumerator, uint startIndex) + { + ThrowHelper.ThrowIfSectorIdIsInvalid(startIndex); + + bool movedToFreeEntry = miniFatEnumerator.MoveTo(startIndex) && miniFatEnumerator.MoveNextFreeEntry(); + if (!movedToFreeEntry) + { + uint newSectorIndex = fatChainEnumerator.Extend(); + Sector sector = new(newSectorIndex, ioContext.SectorSize); + CfbBinaryWriter writer = ioContext.Writer; + writer.Position = sector.Position; + writer.Write(SectorDataCache.GetFatEntryData(sector.Length)); + + if (ioContext.Header.FirstMiniFatSectorId == SectorType.EndOfChain) + ioContext.Header.FirstMiniFatSectorId = newSectorIndex; + + miniFatEnumerator.Reset(); // TODO: Jump closer to the new sector + + bool ok = miniFatEnumerator.MoveNextFreeEntry(); + Debug.Assert(ok, "No free mini FAT entries found."); + } + + FatEntry entry = miniFatEnumerator.Current; + this[entry.Index] = SectorType.EndOfChain; + + Debug.Assert(entry.IsFree); + MiniSector miniSector = new(entry.Index, ioContext.MiniSectorSize); + if (ioContext.MiniStream.Length < miniSector.EndPosition) + ioContext.MiniStream.SetLength(miniSector.EndPosition); + + return entry.Index; + } + + internal void Trace(TextWriter writer) + { + using MiniFatEnumerator miniFatEnumerator = new(ioContext); + miniFatEnumerator.Trace(writer); + } +} diff --git a/OpenMcdf3/MiniFatChainEnumerator.cs b/OpenMcdf3/MiniFatChainEnumerator.cs index a5a8e407..a78be00f 100644 --- a/OpenMcdf3/MiniFatChainEnumerator.cs +++ b/OpenMcdf3/MiniFatChainEnumerator.cs @@ -1,19 +1,24 @@ using System.Collections; +using System.Diagnostics; namespace OpenMcdf3; /// /// Enumerates the s in a Mini FAT sector chain. /// -internal sealed class MiniFatChainEnumerator : IEnumerator +internal sealed class MiniFatChainEnumerator : IEnumerator { + private readonly IOContext ioContext; private readonly MiniFatEnumerator miniFatEnumerator; - private readonly uint startId; + private uint startId; private bool start = true; - private MiniSector current = MiniSector.EndOfChain; + uint index = uint.MaxValue; + private FatChainEntry current = FatChainEntry.Invalid; + private long length = -1; public MiniFatChainEnumerator(IOContext ioContext, uint startSectorId) { + this.ioContext = ioContext; this.startId = startSectorId; miniFatEnumerator = new(ioContext); } @@ -21,20 +26,24 @@ public MiniFatChainEnumerator(IOContext ioContext, uint startSectorId) /// public void Dispose() { - miniFatEnumerator.Dispose(); } /// /// The index within the Mini FAT sector chain, or if the enumeration has not started. /// - public uint Index { get; private set; } = uint.MaxValue; + + public uint StartId => startId; + + public uint Index => index; + + public MiniSector CurrentSector => new(Current.Value, ioContext.MiniSectorSize); /// - public MiniSector Current + public FatChainEntry Current { get { - if (current.IsEndOfChain) + if (current.IsFreeOrEndOfChain) throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); return current; } @@ -48,21 +57,33 @@ public bool MoveNext() { if (start) { - current = new(startId); - Index = 0; start = false; + index = 0; + current = new(index, startId); } - else if (!current.IsEndOfChain) + else if (!current.IsFreeOrEndOfChain) { - uint sectorId = GetNextMiniFatSectorId(current.Id); - current = new(sectorId); - Index++; + uint sectorId = ioContext.MiniFat[current.Value]; + if (sectorId == SectorType.EndOfChain) + { + index = uint.MaxValue; + current = FatChainEntry.Invalid; + return false; + } + + uint nextIndex = index + 1; + if (nextIndex > SectorType.Maximum) + throw new FormatException("Mini FAT chain is corrupt."); + + index = nextIndex; + current = new(nextIndex, sectorId); + return true; } - if (current.IsEndOfChain) + if (current.IsFreeOrEndOfChain) { - current = MiniSector.EndOfChain; - Index = uint.MaxValue; + index = uint.MaxValue; + current = FatChainEntry.Invalid; return false; } @@ -76,10 +97,10 @@ public bool MoveNext() /// true if the enumerator was successfully advanced to the given index public bool MoveTo(uint index) { - if (index < Index) + if (index < this.index) Reset(); - while (start || Index < index) + while (start || this.index < index) { if (!MoveNext()) return false; @@ -88,21 +109,102 @@ public bool MoveTo(uint index) return true; } + public long GetLength() + { + if (length == -1) + { + Reset(); + length = 0; + while (MoveNext()) + { + length++; + } + } + + return length; + } + + public void Extend(uint requiredChainLength) + { + uint chainLength = (uint)GetLength(); + if (chainLength >= requiredChainLength) + throw new ArgumentException("The chain is already longer than required.", nameof(requiredChainLength)); + + if (startId == StreamId.NoStream) + { + startId = ioContext.MiniFat.Add(miniFatEnumerator, 0); + chainLength = 1; + } + + bool ok = MoveTo(chainLength - 1); + Debug.Assert(ok); + + uint lastId = current.Value; + ok = miniFatEnumerator.MoveTo(lastId); + Debug.Assert(ok); + while (chainLength < requiredChainLength) + { + uint id = ioContext.MiniFat.Add(miniFatEnumerator, lastId); + ioContext.MiniFat[lastId] = id; + lastId = id; + chainLength++; + } + +#if DEBUG + this.length = -1; + this.length = GetLength(); + Debug.Assert(length == requiredChainLength); +#endif + + this.length = requiredChainLength; + } + + public void Shrink(uint requiredChainLength) + { + uint chainLength = (uint)GetLength(); + if (chainLength <= requiredChainLength) + throw new ArgumentException("The chain is already shorter than required.", nameof(requiredChainLength)); + + Reset(); + + uint lastId = current.Value; + while (MoveNext()) + { + if (lastId <= SectorType.Maximum) + { + if (index == requiredChainLength) + ioContext.MiniFat[lastId] = SectorType.EndOfChain; + else if (index > requiredChainLength) + ioContext.MiniFat[lastId] = SectorType.Free; + } + + lastId = current.Value; + } + + if (lastId <= SectorType.Maximum) + ioContext.MiniFat[lastId] = SectorType.Free; + + if (requiredChainLength == 0) + { + startId = StreamId.NoStream; + } + +#if DEBUG + this.length = -1; + this.length = GetLength(); + Debug.Assert(length == requiredChainLength); +#endif + + this.length = requiredChainLength; + } + /// public void Reset() { start = true; - miniFatEnumerator.Reset(); - current = MiniSector.EndOfChain; - Index = uint.MaxValue; + index = uint.MaxValue; + current = FatChainEntry.Invalid; } - /// - /// Gets the next sector ID in the FAT chain. - /// - uint GetNextMiniFatSectorId(uint id) - { - miniFatEnumerator.MoveTo(id); - return miniFatEnumerator.Current.Id; - } + public override string ToString() => $"Index: {index} Value {current}"; } diff --git a/OpenMcdf3/MiniFatEnumerator.cs b/OpenMcdf3/MiniFatEnumerator.cs index d1aa852c..48c9e017 100644 --- a/OpenMcdf3/MiniFatEnumerator.cs +++ b/OpenMcdf3/MiniFatEnumerator.cs @@ -5,28 +5,44 @@ namespace OpenMcdf3; /// /// Enumerates the s from the Mini FAT. /// -internal sealed class MiniFatEnumerator : IEnumerator +internal sealed class MiniFatEnumerator : IEnumerator { private readonly IOContext ioContext; - private readonly MiniFatSectorEnumerator miniFatSectorEnumerator; + private readonly FatChainEnumerator fatChainEnumerator; private bool start = true; - private int index = int.MaxValue; - private MiniSector current = MiniSector.EndOfChain; + private uint index = uint.MaxValue; + private uint value = uint.MaxValue; public MiniFatEnumerator(IOContext ioContext) { - miniFatSectorEnumerator = new(ioContext); + fatChainEnumerator = new(ioContext, ioContext.Header.FirstMiniFatSectorId); this.ioContext = ioContext; } /// - public MiniSector Current + public void Dispose() + { + fatChainEnumerator.Dispose(); + } + + public MiniSector CurrentSector + { + get + { + if (index == uint.MaxValue) + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + return new(value, ioContext.MiniSectorSize); + } + } + + /// + public FatEntry Current { get { - if (index == int.MaxValue) + if (index == uint.MaxValue) throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); - return current; + return new(index, value); } } @@ -38,65 +54,63 @@ public bool MoveNext() { if (start) { - if (!miniFatSectorEnumerator.MoveNext()) - { - index = int.MaxValue; - return false; - } - - index = -1; start = false; + return MoveTo(0); } - index++; - int elementCount = MiniSector.Length / sizeof(uint); - if (index > elementCount) - { - if (!miniFatSectorEnumerator.MoveNext()) - { - index = int.MaxValue; - return false; - } + if (index >= SectorType.Maximum) + return false; + + uint next = index + 1; + return MoveTo(next); + } - index = 0; + public bool MoveTo(uint index) + { + ThrowHelper.ThrowIfSectorIdIsInvalid(index); + + if (this.index == index) + return true; + + if (ioContext.MiniFat.TryGetValue(index, out value)) + { + this.index = index; + return true; } - long position = miniFatSectorEnumerator.Current.Position + index * sizeof(uint); - ioContext.Reader.Seek(position); - uint sectorId = ioContext.Reader.ReadUInt32(); - current = new(sectorId); - return true; + this.index = uint.MaxValue; + return false; } - public void MoveTo(uint id) + public bool MoveNextFreeEntry() { - if (id > SectorType.Maximum) - throw new ArgumentException("Invalid sector ID", nameof(id)); - - int elementCount = ioContext.Header.SectorSize / sizeof(uint); - uint sectorId = (uint)Math.DivRem(id, elementCount, out long index); - - miniFatSectorEnumerator.MoveTo(sectorId); - long position = miniFatSectorEnumerator.Current.Position + index * sizeof(uint); - ioContext.Reader.Seek(position); - uint value = ioContext.Reader.ReadUInt32(); - this.index = (int)index; - start = false; - current = new(value); + while (MoveNext()) + { + if (value == SectorType.Free) + { + return true; + } + } + + return false; } /// public void Reset() { - miniFatSectorEnumerator.Reset(); + fatChainEnumerator.Reset(ioContext.Header.FirstMiniFatSectorId); start = true; - current = MiniSector.EndOfChain; - index = int.MaxValue; + index = uint.MaxValue; + value = uint.MaxValue; } - /// - public void Dispose() + internal void Trace(TextWriter writer) { - miniFatSectorEnumerator.Dispose(); + Reset(); + + writer.WriteLine("Start of Mini FAT ============"); + while (MoveNext()) + writer.WriteLine($"Mini FAT entry {Current}"); + writer.WriteLine("End of Mini FAT =============="); } } diff --git a/OpenMcdf3/MiniFatSectorEnumerator.cs b/OpenMcdf3/MiniFatSectorEnumerator.cs deleted file mode 100644 index f9552d42..00000000 --- a/OpenMcdf3/MiniFatSectorEnumerator.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.Collections; - -namespace OpenMcdf3; - -/// -/// Enumerates the s in a FAT sector chain. -/// -internal sealed class MiniFatSectorEnumerator : IEnumerator -{ - private readonly IOContext ioContext; - private readonly FatChainEnumerator fatChain; - bool start = true; - MiniSector current = MiniSector.EndOfChain; - - public MiniFatSectorEnumerator(IOContext ioContext) - { - this.ioContext = ioContext; - fatChain = new(ioContext, ioContext.Header.FirstMiniFatSectorId); - } - - /// - public void Dispose() - { - fatChain.Dispose(); - } - - /// - public MiniSector Current - { - get - { - if (current.IsEndOfChain) - throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); - return current; - } - } - - /// - object IEnumerator.Current => Current; - - /// - public bool MoveNext() - { - if (start) - { - current = new(ioContext.Header.FirstMiniFatSectorId); - start = false; - } - else if (!current.IsEndOfChain) - { - uint sectorId = GetNextMiniFatSectorId(current.Id); - current = new(sectorId); - } - - return !current.IsEndOfChain; - } - - /// - /// Moves the enumerator to the specified sector. - /// - public bool MoveTo(uint sectorId) - { - if (sectorId > SectorType.Maximum) - throw new ArgumentOutOfRangeException(nameof(sectorId)); - - if (sectorId < current.Id) - Reset(); - - while (start || current.Id < sectorId) - { - if (!MoveNext()) - return false; - } - - return true; - } - - /// - public void Reset() - { - start = true; - current = MiniSector.EndOfChain; - } - - /// - /// Gets the next mini FAT sector ID. - /// - /// - /// - /// - public uint GetNextMiniFatSectorId(uint sectorId) - { - if (sectorId > SectorType.Maximum) - throw new ArgumentException($"Invalid sector ID: {sectorId}", nameof(sectorId)); - - int elementLength = ioContext.Header.SectorSize / sizeof(uint); - uint fatSectorId = (uint)Math.DivRem(sectorId, elementLength, out long sectorOffset); - if (!fatChain.MoveTo(fatSectorId)) - throw new ArgumentException($"Invalid sector ID: {sectorId}", nameof(sectorId)); - - long position = fatChain.Current.Position + sectorOffset * sizeof(uint); - ioContext.Reader.Seek(position); - uint nextId = ioContext.Reader.ReadUInt32(); - return nextId; - } -} diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index 915d5e88..bd274f32 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -6,9 +6,7 @@ internal sealed class MiniFatStream : Stream { readonly IOContext ioContext; - readonly MiniFatChainEnumerator chain; - readonly FatStream fatStream; - readonly long length; + readonly MiniFatChainEnumerator miniChain; long position; bool disposed; @@ -16,20 +14,20 @@ internal MiniFatStream(IOContext ioContext, DirectoryEntry directoryEntry) { this.ioContext = ioContext; DirectoryEntry = directoryEntry; - length = directoryEntry.StreamLength; - chain = new(ioContext, directoryEntry.StartSectorId); - fatStream = new(ioContext, ioContext.RootEntry); + miniChain = new(ioContext, directoryEntry.StartSectorId); } internal DirectoryEntry DirectoryEntry { get; private set; } + internal long ChainCapacity => ((Length + ioContext.MiniSectorSize - 1) / ioContext.MiniSectorSize) * ioContext.MiniSectorSize; + public override bool CanRead => true; public override bool CanSeek => true; - public override bool CanWrite => false; + public override bool CanWrite => ioContext.CanWrite; - public override long Length => length; + public override long Length => DirectoryEntry.StreamLength; public override long Position { @@ -41,8 +39,7 @@ protected override void Dispose(bool disposing) { if (!disposed) { - chain.Dispose(); - fatStream.Dispose(); + miniChain.Dispose(); disposed = true; } @@ -52,43 +49,38 @@ protected override void Dispose(bool disposing) public override void Flush() { this.ThrowIfDisposed(disposed); - fatStream.Flush(); + + ioContext.MiniStream.Flush(); } public override int Read(byte[] buffer, int offset, int count) { - if (buffer is null) - throw new ArgumentNullException(nameof(buffer)); - - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset), "Offset must be a non-negative number"); - - if ((uint)count > buffer.Length - offset) - throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection"); + ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); this.ThrowIfDisposed(disposed); if (count == 0) return 0; - int maxCount = (int)Math.Min(Math.Max(length - position, 0), int.MaxValue); + int maxCount = (int)Math.Min(Math.Max(Length - position, 0), int.MaxValue); if (maxCount == 0) return 0; - uint chainIndex = (uint)Math.DivRem(position, ioContext.Header.SectorSize, out long sectorOffset); - if (!chain.MoveTo(chainIndex)) + uint chainIndex = (uint)Math.DivRem(position, ioContext.MiniSectorSize, out long sectorOffset); + if (!miniChain.MoveTo(chainIndex)) return 0; + FatStream miniStream = ioContext.MiniStream; int realCount = Math.Min(count, maxCount); int readCount = 0; do { - MiniSector sector = chain.Current; + MiniSector miniSector = miniChain.CurrentSector; int remaining = realCount - readCount; - long readLength = Math.Min(remaining, buffer.Length); - fatStream.Position = sector.Position + sectorOffset; + long readLength = Math.Min(remaining, miniSector.Length - sectorOffset); + miniStream.Position = miniSector.Position + sectorOffset; int localOffset = offset + readCount; - int read = fatStream.Read(buffer, localOffset, (int)readLength); + int read = miniStream.Read(buffer, localOffset, (int)readLength); if (read == 0) return readCount; position += read; @@ -96,7 +88,7 @@ public override int Read(byte[] buffer, int offset, int count) sectorOffset = 0; if (readCount >= realCount) return readCount; - } while (chain.MoveNext()); + } while (miniChain.MoveNext()); return readCount; } @@ -109,30 +101,88 @@ public override long Seek(long offset, SeekOrigin origin) { case SeekOrigin.Begin: if (offset < 0) - throw new IOException("Seek before origin"); + ThrowHelper.ThrowSeekBeforeOrigin(); position = offset; break; case SeekOrigin.Current: if (position + offset < 0) - throw new IOException("Seek before origin"); + ThrowHelper.ThrowSeekBeforeOrigin(); position += offset; break; case SeekOrigin.End: if (Length - offset < 0) - throw new IOException("Seek before origin"); + ThrowHelper.ThrowSeekBeforeOrigin(); position = Length - offset; break; default: - throw new ArgumentException(nameof(origin), "Invalid seek origin"); + throw new ArgumentException(nameof(origin), "Invalid seek origin."); } return position; } - public override void SetLength(long value) => throw new NotSupportedException(); + public override void SetLength(long value) + { + if (value >= Header.MiniStreamCutoffSize) + throw new ArgumentOutOfRangeException(nameof(value)); + + this.ThrowIfDisposed(disposed); + this.ThrowIfNotWritable(); - public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + uint requiredChainLength = (uint)((value + ioContext.MiniSectorSize - 1) / ioContext.MiniSectorSize); + if (value > ChainCapacity) + miniChain.Extend(requiredChainLength); + else if (value <= ChainCapacity - ioContext.MiniSectorSize) + miniChain.Shrink(requiredChainLength); + + if (DirectoryEntry.StartSectorId != miniChain.StartId || DirectoryEntry.StreamLength != value) + { + DirectoryEntry.StartSectorId = miniChain.StartId; + DirectoryEntry.StreamLength = value; + ioContext.Write(DirectoryEntry); + } + } + + public override void Write(byte[] buffer, int offset, int count) + { + ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); + + this.ThrowIfDisposed(disposed); + this.ThrowIfNotWritable(); + + if (count == 0) + return; + + if (position + count > ChainCapacity) + SetLength(position + count); + + uint chainIndex = (uint)Math.DivRem(position, ioContext.MiniSectorSize, out long sectorOffset); + if (!miniChain.MoveTo(chainIndex)) + throw new InvalidOperationException($"Failed to move to mini FAT chain index: {chainIndex}."); + + FatStream miniStream = ioContext.MiniStream; + int writeCount = 0; + do + { + MiniSector miniSector = miniChain.CurrentSector; + long basePosition = miniSector.Position + sectorOffset; + miniStream.Seek(basePosition, SeekOrigin.Begin); + int remaining = count - writeCount; + int localOffset = offset + writeCount; + long writeLength = Math.Min(remaining, ioContext.MiniSectorSize - sectorOffset); + miniStream.Write(buffer, localOffset, (int)writeLength); + position += writeLength; + writeCount += (int)writeLength; + if (position > Length) + DirectoryEntry.StreamLength = position; + sectorOffset = 0; + if (writeCount >= count) + return; + } while (miniChain.MoveNext()); + + throw new InvalidOperationException($"End of mini FAT chain was reached."); + } } diff --git a/OpenMcdf3/MiniSector.cs b/OpenMcdf3/MiniSector.cs index e30b6e81..a8d0e105 100644 --- a/OpenMcdf3/MiniSector.cs +++ b/OpenMcdf3/MiniSector.cs @@ -4,11 +4,9 @@ /// Encapsulates information about a mini sector in a compound file. /// /// The ID of the mini sector -internal record struct MiniSector(uint Id) +internal record struct MiniSector(uint Id, int Length) { - public const int Length = 64; - - public static readonly MiniSector EndOfChain = new(SectorType.EndOfChain); + public static readonly MiniSector EndOfChain = new(SectorType.EndOfChain, int.MaxValue); public readonly bool IsValid => Id <= SectorType.Maximum; @@ -41,7 +39,7 @@ public readonly long EndPosition readonly void ThrowIfInvalid() { if (!IsValid) - throw new InvalidOperationException($"Invalid sector ID: {Id}"); + throw new InvalidOperationException($"Invalid mini FAT sector ID: {Id}."); } public override readonly string ToString() => $"{Id}"; diff --git a/OpenMcdf3/OpenMcdf3.csproj b/OpenMcdf3/OpenMcdf3.csproj index 1ace06ea..081a0aef 100644 --- a/OpenMcdf3/OpenMcdf3.csproj +++ b/OpenMcdf3/OpenMcdf3.csproj @@ -1,11 +1,15 @@  - netstandard2.0 + netstandard2.0;netstandard2.1;net8.0 11.0 enable enable true + + + + diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index 3df2d7ff..6ce63152 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -14,7 +14,13 @@ public sealed class RootStorage : Storage, IDisposable public static RootStorage Create(string fileName, Version version = Version.V3) { FileStream stream = File.Create(fileName); + return Create(stream, version); + } + + public static RootStorage Create(Stream stream, Version version = Version.V3) + { Header header = new(version); + stream.SetLength(0); CfbBinaryReader reader = new(stream); CfbBinaryWriter writer = new(stream); IOContext ioContext = new(header, reader, writer, IOContextFlags.Create); @@ -35,9 +41,16 @@ public static RootStorage OpenRead(string fileName) public static RootStorage Open(Stream stream, bool leaveOpen = false) { + stream.Position = 0; + + Header header; + using (CfbBinaryReader headerReader = new(stream)) + { + header = headerReader.ReadHeader(); + } + CfbBinaryReader reader = new(stream); CfbBinaryWriter? writer = stream.CanWrite ? new(stream) : null; - Header header = reader.ReadHeader(); IOContextFlags contextFlags = leaveOpen ? IOContextFlags.LeaveOpen : IOContextFlags.None; IOContext ioContext = new(header, reader, writer, contextFlags); return new RootStorage(ioContext); @@ -49,4 +62,11 @@ public static RootStorage Open(Stream stream, bool leaveOpen = false) } public void Dispose() => ioContext?.Dispose(); + + internal void Trace(TextWriter writer) + { + writer.WriteLine(ioContext.Header); + ioContext.Fat.Trace(writer); + ioContext.MiniFat.Trace(writer); + } } diff --git a/OpenMcdf3/Sector.cs b/OpenMcdf3/Sector.cs index 94a9ae9a..07beb12d 100644 --- a/OpenMcdf3/Sector.cs +++ b/OpenMcdf3/Sector.cs @@ -18,7 +18,7 @@ internal record struct Sector(uint Id, int Length) public readonly bool IsValid => Id <= SectorType.Maximum; /// - /// The position of the mini sector in the compound file stream. + /// The position of the sector in the compound file stream. /// public readonly long Position { @@ -30,7 +30,7 @@ public readonly long Position } /// - /// The end position of the mini sector in the compound file stream. + /// The end position of the sector in the compound file stream. /// public readonly long EndPosition { @@ -44,7 +44,7 @@ public readonly long EndPosition readonly void ThrowIfInvalid() { if (!IsValid) - throw new InvalidOperationException($"Invalid sector ID: {Id}"); + throw new InvalidOperationException($"Invalid FAT sector ID: {Id}."); } public override readonly string ToString() => $"{Id}"; diff --git a/OpenMcdf3/SectorDataCache.cs b/OpenMcdf3/SectorDataCache.cs new file mode 100644 index 00000000..77085b56 --- /dev/null +++ b/OpenMcdf3/SectorDataCache.cs @@ -0,0 +1,25 @@ +using System.Collections.Concurrent; +using System.Runtime.InteropServices; + +namespace OpenMcdf3; + +/// +/// Caches data for adding new sectors to the FAT. +/// +internal static class SectorDataCache +{ + static readonly ConcurrentDictionary freeFatSectorData = new(1, 2); + + public static byte[] GetFatEntryData(int sectorSize) + { + if (!freeFatSectorData.TryGetValue(sectorSize, out byte[]? data)) + { + data = new byte[sectorSize]; + Span uintSpan = MemoryMarshal.Cast(data); + uintSpan.Fill(SectorType.Free); + freeFatSectorData.TryAdd(sectorSize, data); + } + + return data; + } +} diff --git a/OpenMcdf3/SectorType.cs b/OpenMcdf3/SectorType.cs index 83098373..83582262 100644 --- a/OpenMcdf3/SectorType.cs +++ b/OpenMcdf3/SectorType.cs @@ -10,4 +10,6 @@ internal static class SectorType public const uint Fat = 0xFFFFFFFD; public const uint EndOfChain = 0xFFFFFFFE; public const uint Free = 0xFFFFFFFF; + + public static bool IsFreeOrEndOfChain(uint value) => value is Free or EndOfChain; } diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index ac428018..b3b3a347 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -17,7 +17,7 @@ internal Storage(IOContext ioContext, DirectoryEntry directoryEntry) public IEnumerable EnumerateEntries() { - this.ThrowIfDisposed(ioContext); + this.ThrowIfDisposed(ioContext.IsDisposed); return EnumerateDirectoryEntries() .Select(e => e.ToEntryInfo()); @@ -25,7 +25,7 @@ public IEnumerable EnumerateEntries() public IEnumerable EnumerateEntries(StorageType type) { - this.ThrowIfDisposed(ioContext); + this.ThrowIfDisposed(ioContext.IsDisposed); return EnumerateDirectoryEntries(type) .Select(e => e.ToEntryInfo()); @@ -33,8 +33,6 @@ public IEnumerable EnumerateEntries(StorageType type) IEnumerable EnumerateDirectoryEntries() { - this.ThrowIfDisposed(ioContext); - using DirectoryTreeEnumerator treeEnumerator = new(ioContext, DirectoryEntry); while (treeEnumerator.MoveNext()) { @@ -45,25 +43,60 @@ IEnumerable EnumerateDirectoryEntries() IEnumerable EnumerateDirectoryEntries(StorageType type) => EnumerateDirectoryEntries() .Where(e => e.Type == type); + DirectoryEntry? TryGetDirectoryEntry(StorageType storageType, string name) + { + using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext, DirectoryEntry); + return directoryTreeEnumerator.TryGetDirectoryEntry(storageType, name); + } + + DirectoryEntry AddDirectoryEntry(StorageType storageType, string name) + { + using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext, DirectoryEntry); + return directoryTreeEnumerator.Add(storageType, name); + } + + public Storage CreateStorage(string name) + { + ThrowHelper.ThrowIfNameIsInvalid(name); + + this.ThrowIfDisposed(ioContext.IsDisposed); + + DirectoryEntry entry = AddDirectoryEntry(StorageType.Storage, name); + return new Storage(ioContext, entry); + } + + public CfbStream CreateStream(string name) + { + ThrowHelper.ThrowIfNameIsInvalid(name); + + this.ThrowIfDisposed(ioContext.IsDisposed); + + // TODO: Return a Stream that can transition between FAT and mini FAT + DirectoryEntry entry = AddDirectoryEntry(StorageType.Stream, name); + return new CfbStream(ioContext, entry); + } + public Storage OpenStorage(string name) { - this.ThrowIfDisposed(ioContext); + ThrowHelper.ThrowIfNameIsInvalid(name); + + this.ThrowIfDisposed(ioContext.IsDisposed); - DirectoryEntry? entry = EnumerateDirectoryEntries(StorageType.Storage) - .FirstOrDefault(entry => entry.Name == name) ?? throw new DirectoryNotFoundException($"Storage not found: {name}"); + DirectoryEntry entry = TryGetDirectoryEntry(StorageType.Storage, name) + ?? throw new DirectoryNotFoundException($"Storage not found: {name}."); return new Storage(ioContext, entry); } - public Stream OpenStream(string name) + public CfbStream OpenStream(string name) { - this.ThrowIfDisposed(ioContext); + ThrowHelper.ThrowIfNameIsInvalid(name); - DirectoryEntry? entry = EnumerateDirectoryEntries(StorageType.Stream) - .FirstOrDefault(entry => entry.Name == name) ?? throw new FileNotFoundException($"Stream not found: {name}", name); - return entry.StreamLength switch - { - < Header.MiniStreamCutoffSize => new MiniFatStream(ioContext, entry), - _ => new FatStream(ioContext, entry) - }; + this.ThrowIfDisposed(ioContext.IsDisposed); + + DirectoryEntry? entry = TryGetDirectoryEntry(StorageType.Stream, name) + ?? throw new FileNotFoundException($"Stream not found: {name}.", name); + + // TODO: Return a Stream that can transition between FAT and mini FAT + return new CfbStream(ioContext, entry); } } diff --git a/OpenMcdf3/StreamExtensions.cs b/OpenMcdf3/StreamExtensions.cs new file mode 100644 index 00000000..b2fd63c4 --- /dev/null +++ b/OpenMcdf3/StreamExtensions.cs @@ -0,0 +1,40 @@ +using System.Buffers; + +namespace OpenMcdf3; + +#if !NET7_0_OR_GREATER + +internal static class StreamExtensions +{ + public static void ReadExactly(this Stream stream, Span buffer) + { + byte[] array = ArrayPool.Shared.Rent(buffer.Length); + try + { + stream.ReadExactly(array, 0, buffer.Length); + array.AsSpan(0, buffer.Length).CopyTo(buffer); + } + finally + { + ArrayPool.Shared.Return(array); + } + } + + public static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count) + { + if (count == 0) + return; + + int totalRead = 0; + do + { + int read = stream.Read(buffer, offset + totalRead, count - totalRead); + if (read == 0) + throw new EndOfStreamException(); + + totalRead += read; + } while (totalRead < count); + } +} + +#endif diff --git a/OpenMcdf3/ThrowHelper.cs b/OpenMcdf3/ThrowHelper.cs index aed8877a..b7c61608 100644 --- a/OpenMcdf3/ThrowHelper.cs +++ b/OpenMcdf3/ThrowHelper.cs @@ -1,4 +1,6 @@ -namespace OpenMcdf3; +using System.Text; + +namespace OpenMcdf3; /// /// Extensions to consistently throw exceptions. @@ -11,9 +13,41 @@ public static void ThrowIfDisposed(this object instance, bool disposed) throw new ObjectDisposedException(instance.GetType().FullName); } - public static void ThrowIfDisposed(this object instance, IOContext context) + public static void ThrowIfStreamArgumentsAreInvalid(byte[] buffer, int offset, int count) + { + if (buffer is null) + throw new ArgumentNullException(nameof(buffer)); + + if (offset < 0) + throw new ArgumentOutOfRangeException(nameof(offset), "Offset must be a non-negative number."); + + if ((uint)count > buffer.Length - offset) + throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); + } + + public static void ThrowIfNotWritable(this Stream stream) + { + if (!stream.CanWrite) + throw new NotSupportedException("Stream does not support writing."); + } + + public static void ThrowSeekBeforeOrigin() => throw new IOException("Seek before origin."); + + public static void ThrowIfNameIsInvalid(string value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + if (value.Contains(@"\") || value.Contains(@"/") || value.Contains(@":") || value.Contains(@"!")) + throw new ArgumentException("Name cannot contain any of the following characters: '\\', '/', ':', '!'.", nameof(value)); + + if (Encoding.Unicode.GetByteCount(value) > DirectoryEntry.NameFieldLength - 2) + throw new ArgumentException($"{value} exceeds maximum encoded length of {DirectoryEntry.NameFieldLength} bytes.", nameof(value)); + } + + public static void ThrowIfSectorIdIsInvalid(uint value) { - if (context.IsDisposed) - throw new InvalidOperationException("Root storage has been disposed"); + if (value > SectorType.Maximum) + throw new ArgumentOutOfRangeException(nameof(value), $"Invalid sector ID: {value:X8}."); } } diff --git a/global.json b/global.json new file mode 100644 index 00000000..1617b795 --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.402", + "rollForward": "latestMinor", + "allowPrerelease": false + } +} \ No newline at end of file From c14584e2d2e908f3debe7ac93da0baa957b8634e Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 4 Nov 2024 14:56:38 +1300 Subject: [PATCH 038/134] Apply minor refactor --- OpenMcdf3.Tests/StreamTests.cs | 2 +- OpenMcdf3/CfbBinaryReader.cs | 4 ++-- OpenMcdf3/DirectoryEntryEnumerator.cs | 8 ++++---- OpenMcdf3/DirectoryTreeEnumerator.cs | 2 -- OpenMcdf3/Fat.cs | 6 +++--- OpenMcdf3/FatChainEnumerator.cs | 6 +++--- OpenMcdf3/FatSectorEnumerator.cs | 8 ++++++-- OpenMcdf3/MiniFat.cs | 4 ++-- OpenMcdf3/MiniFatChainEnumerator.cs | 6 +++--- OpenMcdf3/MiniFatStream.cs | 2 +- OpenMcdf3/MiniSector.cs | 1 + OpenMcdf3/RootStorage.cs | 2 +- 12 files changed, 27 insertions(+), 24 deletions(-) diff --git a/OpenMcdf3.Tests/StreamTests.cs b/OpenMcdf3.Tests/StreamTests.cs index 5be7f8b3..d0249f72 100644 --- a/OpenMcdf3.Tests/StreamTests.cs +++ b/OpenMcdf3.Tests/StreamTests.cs @@ -252,7 +252,7 @@ public void WriteMultiple(Version version, int length) [DataRow(Version.V3, 0)] [DataRow(Version.V3, 63)] [DataRow(Version.V3, 64)] // Mini-stream sector size - [DataRow(Version.V3, 2* 64)] // Simplest case (1 sector => 2) + [DataRow(Version.V3, 2 * 64)] // Simplest case (1 sector => 2) [DataRow(Version.V3, 65)] [DataRow(Version.V3, 511)] [DataRow(Version.V3, 512)] // Multiple stream sectors diff --git a/OpenMcdf3/CfbBinaryReader.cs b/OpenMcdf3/CfbBinaryReader.cs index abda95af..fac360f1 100644 --- a/OpenMcdf3/CfbBinaryReader.cs +++ b/OpenMcdf3/CfbBinaryReader.cs @@ -61,11 +61,11 @@ public Header ReadHeader() throw new FormatException($"Unsupported byte order: {byteOrder:X4}. Only little-endian is supported ({Header.LittleEndian:X4})."); header.SectorShift = ReadUInt16(); header.MiniSectorShift = ReadUInt16(); - this.FillBuffer(6); + FillBuffer(6); header.DirectorySectorCount = ReadUInt32(); header.FatSectorCount = ReadUInt32(); header.FirstDirectorySectorId = ReadUInt32(); - this.FillBuffer(4); + FillBuffer(4); uint miniStreamCutoffSize = ReadUInt32(); if (miniStreamCutoffSize != Header.MiniStreamCutoffSize) throw new FormatException($"Mini stream cutoff size must be {Header.MiniStreamCutoffSize} bytes."); diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index 9e78f0de..2319af4c 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -16,7 +16,7 @@ internal sealed class DirectoryEntryEnumerator : IEnumerator public DirectoryEntryEnumerator(IOContext ioContext) { this.ioContext = ioContext; - this.fatChainEnumerator = new FatChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); + fatChainEnumerator = new FatChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); } /// @@ -58,7 +58,7 @@ public bool MoveNext() return false; } - ioContext.Reader.Position = fatChainEnumerator.CurrentSector.Position + entryIndex * DirectoryEntry.Length; + ioContext.Reader.Position = fatChainEnumerator.CurrentSector.Position + (entryIndex * DirectoryEntry.Length); current = ioContext.Reader.ReadDirectoryEntry(ioContext.Version, index); index++; return true; @@ -113,7 +113,7 @@ public DirectoryEntry GetDictionaryEntry(uint streamId) if (!fatChainEnumerator.MoveTo(chainIndex)) throw new KeyNotFoundException($"Directory entry {streamId} was not found."); - ioContext.Reader.Position = fatChainEnumerator.CurrentSector.Position + entryIndex * DirectoryEntry.Length; + ioContext.Reader.Position = fatChainEnumerator.CurrentSector.Position + (entryIndex * DirectoryEntry.Length); current = ioContext.Reader.ReadDirectoryEntry(ioContext.Version, streamId); return current; } @@ -125,7 +125,7 @@ public void Write(DirectoryEntry entry) throw new KeyNotFoundException($"Directory entry {entry.Id} was not found."); CfbBinaryWriter writer = ioContext.Writer; - writer.Position = fatChainEnumerator.CurrentSector.Position + entryIndex * DirectoryEntry.Length; + writer.Position = fatChainEnumerator.CurrentSector.Position + (entryIndex * DirectoryEntry.Length); writer.Write(entry); } diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index f5a7490f..c58262ec 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -8,7 +8,6 @@ namespace OpenMcdf3; /// internal sealed class DirectoryTreeEnumerator : IEnumerator { - private readonly IOContext ioContext; private readonly DirectoryEntry root; private DirectoryEntry? child; private readonly Stack stack = new(); @@ -18,7 +17,6 @@ internal sealed class DirectoryTreeEnumerator : IEnumerator internal DirectoryTreeEnumerator(IOContext ioContext, DirectoryEntry root) { directoryEntryEnumerator = new(ioContext); - this.ioContext = ioContext; this.root = root; if (root.ChildId != StreamId.NoStream) child = directoryEntryEnumerator.GetDictionaryEntry(root.ChildId); diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs index 5e2ec4c8..38223b7a 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf3/Fat.cs @@ -13,7 +13,7 @@ internal sealed class Fat : IEnumerable, IDisposable internal int FatElementsPerSector => ioContext.SectorSize / sizeof(uint); - internal int DifatElementsPerSector => ioContext.SectorSize / sizeof(uint) - 1; + internal int DifatElementsPerSector => (ioContext.SectorSize / sizeof(uint)) - 1; public Fat(IOContext ioContext) { @@ -64,7 +64,7 @@ public bool TryGetValue(uint key, out uint value) } CfbBinaryReader reader = ioContext.Reader; - reader.Position = fatSectorEnumerator.Current.Position + elementIndex * sizeof(uint); + reader.Position = fatSectorEnumerator.Current.Position + (elementIndex * sizeof(uint)); value = reader.ReadUInt32(); return true; } @@ -78,7 +78,7 @@ public bool TrySetValue(uint key, uint value) return false; CfbBinaryWriter writer = ioContext.Writer; - writer.Position = fatSectorEnumerator.Current.Position + elementIndex * sizeof(uint); + writer.Position = fatSectorEnumerator.Current.Position + (elementIndex * sizeof(uint)); writer.Write(value); return true; } diff --git a/OpenMcdf3/FatChainEnumerator.cs b/OpenMcdf3/FatChainEnumerator.cs index 1ccae887..2d538656 100644 --- a/OpenMcdf3/FatChainEnumerator.cs +++ b/OpenMcdf3/FatChainEnumerator.cs @@ -19,7 +19,7 @@ internal sealed class FatChainEnumerator : IEnumerator public FatChainEnumerator(IOContext ioContext, uint startSectorId) { this.ioContext = ioContext; - this.startId = startSectorId; + startId = startSectorId; fatEnumerator = new(ioContext); } @@ -179,7 +179,7 @@ public void Extend(uint requiredChainLength) chainLength++; } - this.length = requiredChainLength; + length = requiredChainLength; } public void Shrink(uint requiredChainLength) @@ -217,7 +217,7 @@ public void Shrink(uint requiredChainLength) Debug.Assert(length == requiredChainLength); #endif - this.length = requiredChainLength; + length = requiredChainLength; } /// diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index 9ddf0cb4..ce78326f 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -16,7 +16,7 @@ internal sealed class FatSectorEnumerator : IEnumerator public FatSectorEnumerator(IOContext ioContext) { this.ioContext = ioContext; - this.difatSectorId = ioContext.Header.FirstDifatSectorId; + difatSectorId = ioContext.Header.FirstDifatSectorId; } /// @@ -42,7 +42,11 @@ public Sector Current /// public bool MoveNext() { - start = false; + if (start) + { + start = false; + index = uint.MaxValue; + } uint nextIndex = index + 1; if (nextIndex < ioContext.Header.FatSectorCount && nextIndex < Header.DifatArrayLength) // Include the free entries diff --git a/OpenMcdf3/MiniFat.cs b/OpenMcdf3/MiniFat.cs index bab51843..8675ba92 100644 --- a/OpenMcdf3/MiniFat.cs +++ b/OpenMcdf3/MiniFat.cs @@ -42,7 +42,7 @@ public uint this[uint key] throw new KeyNotFoundException($"Mini FAT index not found: {fatSectorIndex}."); CfbBinaryWriter writer = ioContext.Writer; - writer.Position = fatChainEnumerator.CurrentSector.Position + elementIndex * sizeof(uint); + writer.Position = fatChainEnumerator.CurrentSector.Position + (elementIndex * sizeof(uint)); writer.Write(value); } } @@ -60,7 +60,7 @@ public bool TryGetValue(uint key, out uint value) } CfbBinaryReader reader = ioContext.Reader; - reader.Position = fatChainEnumerator.CurrentSector.Position + elementIndex * sizeof(uint); + reader.Position = fatChainEnumerator.CurrentSector.Position + (elementIndex * sizeof(uint)); value = reader.ReadUInt32(); return true; } diff --git a/OpenMcdf3/MiniFatChainEnumerator.cs b/OpenMcdf3/MiniFatChainEnumerator.cs index a78be00f..e261243f 100644 --- a/OpenMcdf3/MiniFatChainEnumerator.cs +++ b/OpenMcdf3/MiniFatChainEnumerator.cs @@ -19,7 +19,7 @@ internal sealed class MiniFatChainEnumerator : IEnumerator public MiniFatChainEnumerator(IOContext ioContext, uint startSectorId) { this.ioContext = ioContext; - this.startId = startSectorId; + startId = startSectorId; miniFatEnumerator = new(ioContext); } @@ -156,7 +156,7 @@ public void Extend(uint requiredChainLength) Debug.Assert(length == requiredChainLength); #endif - this.length = requiredChainLength; + length = requiredChainLength; } public void Shrink(uint requiredChainLength) @@ -195,7 +195,7 @@ public void Shrink(uint requiredChainLength) Debug.Assert(length == requiredChainLength); #endif - this.length = requiredChainLength; + length = requiredChainLength; } /// diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index bd274f32..032e8370 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -1,7 +1,7 @@ namespace OpenMcdf3; /// -/// Provides a for reading a from the mini FAT stream. +/// Provides a for reading a from the mini FAT stream. /// internal sealed class MiniFatStream : Stream { diff --git a/OpenMcdf3/MiniSector.cs b/OpenMcdf3/MiniSector.cs index a8d0e105..daf5b16e 100644 --- a/OpenMcdf3/MiniSector.cs +++ b/OpenMcdf3/MiniSector.cs @@ -4,6 +4,7 @@ /// Encapsulates information about a mini sector in a compound file. /// /// The ID of the mini sector +/// The sector length internal record struct MiniSector(uint Id, int Length) { public static readonly MiniSector EndOfChain = new(SectorType.EndOfChain, int.MaxValue); diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index 6ce63152..f94318f3 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -7,7 +7,7 @@ public enum Version : ushort } /// -/// Encapsulates the root of a compound file. +/// Encapsulates the root of a compound file. /// public sealed class RootStorage : Storage, IDisposable { From 8d411d17c52dc085f80d1c258f40757734b0e6c9 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 4 Nov 2024 15:05:30 +1300 Subject: [PATCH 039/134] Improve EntryInfo --- OpenMcdf3/CompilerServices.cs | 9 +++++++++ OpenMcdf3/DirectoryEntry.cs | 2 +- OpenMcdf3/EntryInfo.cs | 7 +------ 3 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 OpenMcdf3/CompilerServices.cs diff --git a/OpenMcdf3/CompilerServices.cs b/OpenMcdf3/CompilerServices.cs new file mode 100644 index 00000000..81332ada --- /dev/null +++ b/OpenMcdf3/CompilerServices.cs @@ -0,0 +1,9 @@ +#if NETSTANDARD2_0 || NETSTANDARD2_1 + +namespace System.Runtime.CompilerServices; + +internal class IsExternalInit +{ +} + +#endif \ No newline at end of file diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index 6bdc87be..4a423d25 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -180,7 +180,7 @@ public void Recycle(StorageType storageType, string name) } } - public EntryInfo ToEntryInfo() => new() { Name = Name }; + public EntryInfo ToEntryInfo() => new(Name, StreamLength); public override string ToString() => $"{Id}: \"{Name}\""; diff --git a/OpenMcdf3/EntryInfo.cs b/OpenMcdf3/EntryInfo.cs index b457ad3b..87e8f607 100644 --- a/OpenMcdf3/EntryInfo.cs +++ b/OpenMcdf3/EntryInfo.cs @@ -3,9 +3,4 @@ /// /// Encapsulates information about an entry in a . /// -public class EntryInfo -{ - public string Name { get; internal set; } = string.Empty; - - public override string ToString() => Name; -} +public readonly record struct EntryInfo(string Name, long Length); From 68f339231cb9038ccf458f9971f5a5af5dae29f8 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 4 Nov 2024 16:44:39 +1300 Subject: [PATCH 040/134] Add modify stream test --- OpenMcdf3.Tests/StreamTests.cs | 75 +++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/OpenMcdf3.Tests/StreamTests.cs b/OpenMcdf3.Tests/StreamTests.cs index d0249f72..3c83311a 100644 --- a/OpenMcdf3.Tests/StreamTests.cs +++ b/OpenMcdf3.Tests/StreamTests.cs @@ -183,7 +183,6 @@ public void WriteThenRead(Version version, int length) stream.Write(expectedBuffer, 0, expectedBuffer.Length); } - memoryStream.Position = 0; using (var rootStorage = RootStorage.Open(memoryStream)) { using CfbStream stream = rootStorage.OpenStream("TestStream"); @@ -248,6 +247,80 @@ public void WriteMultiple(Version version, int length) } } + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 63)] + [DataRow(Version.V3, 64)] // Mini-stream sector size + [DataRow(Version.V3, 65)] + [DataRow(Version.V3, 511)] + [DataRow(Version.V3, 512)] // Multiple stream sectors + [DataRow(Version.V3, 513)] + [DataRow(Version.V3, 4095)] + [DataRow(Version.V3, 4096)] + [DataRow(Version.V3, 4097)] + [DataRow(Version.V3, 128 * 512)] // Multiple FAT sectors + [DataRow(Version.V3, 1024 * 4096)] // Multiple FAT sectors + [DataRow(Version.V3, 7087616)] // First DIFAT chain + [DataRow(Version.V3, 2 * 7087616)] // Long DIFAT chain + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 63)] + [DataRow(Version.V4, 64)] // Mini-stream sector size + [DataRow(Version.V4, 65)] + [DataRow(Version.V4, 511)] + [DataRow(Version.V4, 512)] + [DataRow(Version.V4, 513)] + [DataRow(Version.V4, 4095)] + [DataRow(Version.V4, 4096)] // Multiple stream sectors + [DataRow(Version.V4, 4097)] + [DataRow(Version.V4, 1024 * 4096)] // Multiple FAT sectors (1024 * 4096) + [DataRow(Version.V4, 7087616 * 4)] // First DIFAT chain + [DataRow(Version.V4, 2 * 7087616 * 4)] // Long DIFAT chain + public void Modify(Version version, int length) + { + // Fill with bytes equal to their position modulo 256 + byte[] expectedBuffer = new byte[length]; + for (int i = 0; i < length; i++) + expectedBuffer[i] = (byte)i; + + using MemoryStream memoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + using CfbStream stream = rootStorage.CreateStream("TestStream1"); + Assert.AreEqual(0, stream.Length); + + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + using CfbStream stream = rootStorage.CreateStream("TestStream2"); + Assert.AreEqual(0, stream.Length); + + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + using (CfbStream stream = rootStorage.OpenStream("TestStream1")) + { + Assert.AreEqual(length, stream.Length); + + byte[] actualBuffer = new byte[length]; + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + + using (CfbStream stream = rootStorage.OpenStream("TestStream2")) + { + Assert.AreEqual(length, stream.Length); + + byte[] actualBuffer = new byte[length]; + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + } + } + [TestMethod] [DataRow(Version.V3, 0)] [DataRow(Version.V3, 63)] From e84e8b86e87a44b4dcf5c041e14c04163d2d4f8b Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 4 Nov 2024 20:29:42 +1300 Subject: [PATCH 041/134] Check stream is seekable --- OpenMcdf3/RootStorage.cs | 5 ++++- OpenMcdf3/ThrowHelper.cs | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index f94318f3..3a87930c 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -19,8 +19,10 @@ public static RootStorage Create(string fileName, Version version = Version.V3) public static RootStorage Create(Stream stream, Version version = Version.V3) { - Header header = new(version); + stream.ThrowIfNotSeekable(); stream.SetLength(0); + + Header header = new(version); CfbBinaryReader reader = new(stream); CfbBinaryWriter writer = new(stream); IOContext ioContext = new(header, reader, writer, IOContextFlags.Create); @@ -41,6 +43,7 @@ public static RootStorage OpenRead(string fileName) public static RootStorage Open(Stream stream, bool leaveOpen = false) { + stream.ThrowIfNotSeekable(); stream.Position = 0; Header header; diff --git a/OpenMcdf3/ThrowHelper.cs b/OpenMcdf3/ThrowHelper.cs index b7c61608..83ada20e 100644 --- a/OpenMcdf3/ThrowHelper.cs +++ b/OpenMcdf3/ThrowHelper.cs @@ -25,6 +25,12 @@ public static void ThrowIfStreamArgumentsAreInvalid(byte[] buffer, int offset, i throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); } + public static void ThrowIfNotSeekable(this Stream stream) + { + if (!stream.CanSeek) + throw new ArgumentException("Stream must be seekable", nameof(stream)); + } + public static void ThrowIfNotWritable(this Stream stream) { if (!stream.CanWrite) From 8aa1f26d7ba24fde5959951d4a34f695f2e9675c Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 4 Nov 2024 16:44:53 +1300 Subject: [PATCH 042/134] Add transaction stream --- OpenMcdf3.Tests/StreamTests.cs | 144 ++++++++++++++++++++++++++ OpenMcdf3/DirectoryEntryEnumerator.cs | 1 + OpenMcdf3/FatStream.cs | 2 +- OpenMcdf3/IOContext.cs | 46 ++++++-- OpenMcdf3/RootStorage.cs | 54 +++++++--- OpenMcdf3/TransactedStream.cs | 128 +++++++++++++++++++++++ 6 files changed, 347 insertions(+), 28 deletions(-) create mode 100644 OpenMcdf3/TransactedStream.cs diff --git a/OpenMcdf3.Tests/StreamTests.cs b/OpenMcdf3.Tests/StreamTests.cs index 3c83311a..058182ab 100644 --- a/OpenMcdf3.Tests/StreamTests.cs +++ b/OpenMcdf3.Tests/StreamTests.cs @@ -321,6 +321,150 @@ public void Modify(Version version, int length) } } + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 63)] + [DataRow(Version.V3, 64)] // Mini-stream sector size + [DataRow(Version.V3, 65)] + [DataRow(Version.V3, 511)] + [DataRow(Version.V3, 512)] // Multiple stream sectors + [DataRow(Version.V3, 513)] + [DataRow(Version.V3, 4095)] + [DataRow(Version.V3, 4096)] + [DataRow(Version.V3, 4097)] + [DataRow(Version.V3, 128 * 512)] // Multiple FAT sectors + [DataRow(Version.V3, 1024 * 4096)] // Multiple FAT sectors + [DataRow(Version.V3, 7087616)] // First DIFAT chain + [DataRow(Version.V3, 2 * 7087616)] // Long DIFAT chain + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 63)] + [DataRow(Version.V4, 64)] // Mini-stream sector size + [DataRow(Version.V4, 65)] + [DataRow(Version.V4, 511)] + [DataRow(Version.V4, 512)] + [DataRow(Version.V4, 513)] + [DataRow(Version.V4, 4095)] + [DataRow(Version.V4, 4096)] // Multiple stream sectors + [DataRow(Version.V4, 4097)] + [DataRow(Version.V4, 1024 * 4096)] // Multiple FAT sectors (1024 * 4096) + [DataRow(Version.V4, 7087616 * 4)] // First DIFAT chain + [DataRow(Version.V4, 2 * 7087616 * 4)] // Long DIFAT chain + public void ModifyCommit(Version version, int length) + { + // Fill with bytes equal to their position modulo 256 + byte[] expectedBuffer = new byte[length]; + for (int i = 0; i < length; i++) + expectedBuffer[i] = (byte)i; + + using MemoryStream memoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + using CfbStream stream = rootStorage.CreateStream("TestStream1"); + Assert.AreEqual(0, stream.Length); + + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + } + + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.Transacted)) + { + using CfbStream stream = rootStorage.CreateStream("TestStream2"); + Assert.AreEqual(0, stream.Length); + + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + stream.Flush(); + rootStorage.Commit(); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + using (CfbStream stream = rootStorage.OpenStream("TestStream1")) + { + Assert.AreEqual(length, stream.Length); + + byte[] actualBuffer = new byte[length]; + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + + using (CfbStream stream = rootStorage.OpenStream("TestStream2")) + { + Assert.AreEqual(length, stream.Length); + + byte[] actualBuffer = new byte[length]; + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + } + } + + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 63)] + [DataRow(Version.V3, 64)] // Mini-stream sector size + [DataRow(Version.V3, 65)] + [DataRow(Version.V3, 511)] + [DataRow(Version.V3, 512)] // Multiple stream sectors + [DataRow(Version.V3, 513)] + [DataRow(Version.V3, 4095)] + [DataRow(Version.V3, 4096)] + [DataRow(Version.V3, 4097)] + [DataRow(Version.V3, 128 * 512)] // Multiple FAT sectors + [DataRow(Version.V3, 1024 * 4096)] // Multiple FAT sectors + [DataRow(Version.V3, 7087616)] // First DIFAT chain + [DataRow(Version.V3, 2 * 7087616)] // Long DIFAT chain + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 63)] + [DataRow(Version.V4, 64)] // Mini-stream sector size + [DataRow(Version.V4, 65)] + [DataRow(Version.V4, 511)] + [DataRow(Version.V4, 512)] + [DataRow(Version.V4, 513)] + [DataRow(Version.V4, 4095)] + [DataRow(Version.V4, 4096)] // Multiple stream sectors + [DataRow(Version.V4, 4097)] + [DataRow(Version.V4, 1024 * 4096)] // Multiple FAT sectors (1024 * 4096) + [DataRow(Version.V4, 7087616 * 4)] // First DIFAT chain + [DataRow(Version.V4, 2 * 7087616 * 4)] // Long DIFAT chain + public void ModifyRevert(Version version, int length) + { + // Fill with bytes equal to their position modulo 256 + byte[] expectedBuffer = new byte[length]; + for (int i = 0; i < length; i++) + expectedBuffer[i] = (byte)i; + + using MemoryStream memoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + using CfbStream stream = rootStorage.CreateStream("TestStream1"); + Assert.AreEqual(0, stream.Length); + + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + } + + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.Transacted)) + { + using CfbStream stream = rootStorage.CreateStream("TestStream2"); + Assert.AreEqual(0, stream.Length); + + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + rootStorage.Revert(); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + using (CfbStream stream = rootStorage.OpenStream("TestStream1")) + { + Assert.AreEqual(length, stream.Length); + + byte[] actualBuffer = new byte[length]; + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + + Assert.ThrowsException(() => rootStorage.OpenStream("TestStream2")); + } + } + [TestMethod] [DataRow(Version.V3, 0)] [DataRow(Version.V3, 63)] diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index 2319af4c..39f858a4 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Diagnostics; namespace OpenMcdf3; diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index cf8f506e..afefd4f1 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -168,7 +168,7 @@ public override void Write(byte[] buffer, int offset, int count) if (!chain.MoveTo(chainIndex)) throw new InvalidOperationException($"Failed to move to FAT chain index: {chainIndex}"); - CfbBinaryWriter writer = ioContext.Writer!; + CfbBinaryWriter writer = ioContext.Writer; int writeCount = 0; do { diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index 817dc110..b42eb961 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -4,7 +4,8 @@ enum IOContextFlags { None = 0, Create = 1, - LeaveOpen = 2 + LeaveOpen = 2, + Transacted = 4, } /// @@ -12,6 +13,7 @@ enum IOContextFlags /// internal sealed class IOContext : IDisposable { + readonly Stream stream; readonly DirectoryEntryEnumerator directoryEnumerator; readonly CfbBinaryWriter? writer; MiniFat? miniFat; @@ -66,17 +68,25 @@ public FatStream MiniStream public Version Version => (Version)Header.MajorVersion; - public IOContext(Header header, CfbBinaryReader reader, CfbBinaryWriter? writer, IOContextFlags contextFlags = IOContextFlags.None) + public IOContext(Stream stream, Version version, IOContextFlags contextFlags = IOContextFlags.None) { - if (contextFlags.HasFlag(IOContextFlags.Create) && writer is null) - throw new ArgumentNullException(nameof(writer), "A writer is required to create a new compound file."); + this.stream = stream; - Header = header; - Reader = reader; - this.writer = writer; + using CfbBinaryReader reader = new(stream); + Header = contextFlags.HasFlag(IOContextFlags.Create) ? new(version) : reader.ReadHeader(); + SectorSize = 1 << Header.SectorShift; + MiniSectorSize = 1 << Header.MiniSectorShift; - SectorSize = 1 << header.SectorShift; - MiniSectorSize = 1 << header.MiniSectorShift; + Stream transactedStream = stream; + if (contextFlags.HasFlag(IOContextFlags.Transacted)) + { + Stream overlayStream = stream is MemoryStream ? new MemoryStream() : File.Create(Path.GetTempFileName()); + transactedStream = new TransactedStream(this, stream, overlayStream); + } + + Reader = new(transactedStream); + if (stream.CanWrite) + writer = new(transactedStream); Fat = new(this); directoryEnumerator = new(this); @@ -101,7 +111,7 @@ public void Dispose() { if (!IsDisposed) { - if (CanWrite) + if (writer is not null && writer.BaseStream is not TransactedStream) WriteHeader(); miniStream?.Dispose(); miniFat?.Dispose(); @@ -131,4 +141,20 @@ public void Write(DirectoryEntry entry) { directoryEnumerator.Write(entry); } + + public void Commit() + { + if (writer is null || writer.BaseStream is not TransactedStream transactedStream) + throw new InvalidOperationException("Cannot commit non-transacted storage."); + + WriteHeader(); + transactedStream.Commit(); + } + public void Revert() + { + if (writer is null || writer.BaseStream is not TransactedStream transactedStream) + throw new InvalidOperationException("Cannot commit non-transacted storage."); + + transactedStream.Revert(); + } } diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index 3a87930c..0201bcef 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -2,34 +2,47 @@ public enum Version : ushort { + Unknown = 0, V3 = 3, V4 = 4 } +[Flags] +public enum StorageModeFlags +{ + None = 0, + LeaveOpen = 0x01, + Transacted = 0x02, +} + /// /// Encapsulates the root of a compound file. /// public sealed class RootStorage : Storage, IDisposable { - public static RootStorage Create(string fileName, Version version = Version.V3) + public static RootStorage Create(string fileName, Version version = Version.V3, StorageModeFlags flags = StorageModeFlags.None) { FileStream stream = File.Create(fileName); return Create(stream, version); } - public static RootStorage Create(Stream stream, Version version = Version.V3) + public static RootStorage Create(Stream stream, Version version = Version.V3, StorageModeFlags flags = StorageModeFlags.None) { stream.ThrowIfNotSeekable(); stream.SetLength(0); + stream.Position = 0; + + IOContextFlags contextFlags = IOContextFlags.Create; + if (flags.HasFlag(StorageModeFlags.LeaveOpen)) + contextFlags |= IOContextFlags.LeaveOpen; + if (flags.HasFlag(StorageModeFlags.Transacted)) + contextFlags |= IOContextFlags.Transacted; - Header header = new(version); - CfbBinaryReader reader = new(stream); - CfbBinaryWriter writer = new(stream); - IOContext ioContext = new(header, reader, writer, IOContextFlags.Create); + IOContext ioContext = new(stream, version, contextFlags); return new RootStorage(ioContext); } - public static RootStorage Open(string fileName, FileMode mode) + public static RootStorage Open(string fileName, FileMode mode, StorageModeFlags flags = StorageModeFlags.None) { FileStream stream = File.Open(fileName, mode); return Open(stream); @@ -41,21 +54,18 @@ public static RootStorage OpenRead(string fileName) return Open(stream); } - public static RootStorage Open(Stream stream, bool leaveOpen = false) + public static RootStorage Open(Stream stream, StorageModeFlags flags = StorageModeFlags.None) { stream.ThrowIfNotSeekable(); stream.Position = 0; - Header header; - using (CfbBinaryReader headerReader = new(stream)) - { - header = headerReader.ReadHeader(); - } + IOContextFlags contextFlags = IOContextFlags.None; + if (flags.HasFlag(StorageModeFlags.LeaveOpen)) + contextFlags |= IOContextFlags.LeaveOpen; + if (flags.HasFlag(StorageModeFlags.Transacted)) + contextFlags |= IOContextFlags.Transacted; - CfbBinaryReader reader = new(stream); - CfbBinaryWriter? writer = stream.CanWrite ? new(stream) : null; - IOContextFlags contextFlags = leaveOpen ? IOContextFlags.LeaveOpen : IOContextFlags.None; - IOContext ioContext = new(header, reader, writer, contextFlags); + IOContext ioContext = new(stream, Version.Unknown, contextFlags); return new RootStorage(ioContext); } @@ -66,6 +76,16 @@ public static RootStorage Open(Stream stream, bool leaveOpen = false) public void Dispose() => ioContext?.Dispose(); + public void Commit() + { + ioContext.Commit(); + } + + public void Revert() + { + ioContext.Revert(); + } + internal void Trace(TextWriter writer) { writer.WriteLine(ioContext.Header); diff --git a/OpenMcdf3/TransactedStream.cs b/OpenMcdf3/TransactedStream.cs new file mode 100644 index 00000000..15c35227 --- /dev/null +++ b/OpenMcdf3/TransactedStream.cs @@ -0,0 +1,128 @@ +using System.Diagnostics; + +namespace OpenMcdf3; + +internal class TransactedStream : Stream +{ + readonly IOContext ioContext; + readonly Stream originalStream; + readonly Stream overlayStream; + readonly Dictionary dirtySectorPositions = new(); + readonly byte[] buffer; + + public TransactedStream(IOContext ioContext, Stream originalStream, Stream overlayStream) + { + this.ioContext = ioContext; + this.originalStream = originalStream; + this.overlayStream = overlayStream; + buffer = new byte[ioContext.SectorSize]; + } + + protected override void Dispose(bool disposing) + { + // Original stream might be owned by the caller + overlayStream.Dispose(); + + base.Dispose(disposing); + } + + public override bool CanRead => true; + + public override bool CanSeek => true; + + public override bool CanWrite => true; + + public override long Length => overlayStream.Length; + + public override long Position { get => originalStream.Position; set => originalStream.Position = value; } + + public override void Flush() => overlayStream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) + { + ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); + + uint sectorId = (uint)Math.DivRem(originalStream.Position, ioContext.SectorSize, out long sectorOffset); + int remainingFromSector = ioContext.SectorSize - (int)sectorOffset; + int localCount = Math.Min(count, remainingFromSector); + Debug.Assert(localCount == count); + + int read; + if (dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) + { + overlayStream.Position = overlayPosition + sectorOffset; + read = overlayStream.Read(buffer, offset, localCount); + originalStream.Seek(read, SeekOrigin.Current); + } + else + { + read = originalStream.Read(buffer, offset, localCount); + } + + return read; + } + + public override long Seek(long offset, SeekOrigin origin) + { + return originalStream.Seek(offset, origin); + } + + public override void SetLength(long value) => overlayStream.SetLength(value); + + public override void Write(byte[] buffer, int offset, int count) + { + ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); + + uint sectorId = (uint)Math.DivRem(originalStream.Position, ioContext.SectorSize, out long sectorOffset); + int remainingFromSector = ioContext.SectorSize - (int)sectorOffset; + int localCount = Math.Min(count, remainingFromSector); + Debug.Assert(localCount == count); + // TODO: Loop through the buffer and write to the overlay stream + + bool added = false; + if (!dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) + { + overlayPosition = overlayStream.Length; + dirtySectorPositions.Add(sectorId, overlayPosition); + added = true; + } + + if (added && originalStream.Position < originalStream.Length && localCount != ioContext.SectorSize) + { + // Copy the existing sector data + long originalPosition = originalStream.Position; + originalStream.Position = originalPosition - sectorOffset; + originalStream.ReadExactly(this.buffer); + originalStream.Position = originalPosition; + + overlayStream.Position = overlayPosition; + overlayStream.Write(this.buffer, 0, this.buffer.Length); + } + + if (overlayStream.Length < overlayPosition + ioContext.SectorSize) + overlayStream.SetLength(overlayPosition + ioContext.SectorSize); + overlayStream.Position = overlayPosition + sectorOffset; + overlayStream.Write(buffer, offset, localCount); + originalStream.Seek(localCount, SeekOrigin.Current); + } + + public void Commit() + { + foreach (KeyValuePair entry in dirtySectorPositions) + { + overlayStream.Position = entry.Value; + overlayStream.ReadExactly(buffer); + + originalStream.Position = entry.Key * ioContext.SectorSize; + originalStream.Write(buffer, 0, buffer.Length); + } + + originalStream.Flush(); + dirtySectorPositions.Clear(); + } + + public void Revert() + { + dirtySectorPositions.Clear(); + } +} \ No newline at end of file From f71220091fb3e35d86a84fee643c2e138dbac189 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 11:48:17 +1300 Subject: [PATCH 043/134] Add transaction benchmark --- OpenMcdf3.Benchmarks/InMemory.cs | 62 ++++++++----------- .../OpenMcdf3.Benchmarks.csproj | 1 - 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/OpenMcdf3.Benchmarks/InMemory.cs b/OpenMcdf3.Benchmarks/InMemory.cs index 4d3297e5..be786753 100644 --- a/OpenMcdf3.Benchmarks/InMemory.cs +++ b/OpenMcdf3.Benchmarks/InMemory.cs @@ -1,6 +1,5 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Order; -using OpenMcdf; namespace OpenMcdf3.Benchmark; @@ -18,10 +17,10 @@ public class InMemory : IDisposable private const string storageName = "MyStorage"; private const string streamName = "MyStream"; - private byte[] readBuffer; + private MemoryStream readStream = new(); + private MemoryStream writeStream = new(); - private readonly MemoryStream readStream = new(); - private readonly MemoryStream writeStream = new(); + private byte[] buffer = Array.Empty(); [Params(512, Mb /*Kb, 4 * Kb, 128 * Kb, 256 * Kb, 512 * Kb,*/)] public int BufferSize { get; set; } @@ -38,58 +37,49 @@ public void Dispose() [GlobalSetup] public void GlobalSetup() { - readBuffer = new byte[BufferSize]; - CreateFile(1); + buffer = new byte[BufferSize]; + readStream = new MemoryStream(2 * TotalStreamSize); + writeStream = new MemoryStream(2 * TotalStreamSize); + + using var storage = RootStorage.Create(readStream, Version.V3, StorageModeFlags.LeaveOpen); + using CfbStream stream = storage.CreateStream(streamName); + + int iterationCount = TotalStreamSize / BufferSize; + for (int iteration = 0; iteration < iterationCount; ++iteration) + stream.Write(buffer); } [Benchmark] public void Read() { using var compoundFile = RootStorage.Open(readStream); - using CfbStream cfStream = compoundFile.OpenStream(streamName + 0); + using CfbStream cfStream = compoundFile.OpenStream(streamName); long streamSize = cfStream.Length; long position = 0L; - while (true) + while (position < streamSize) { - if (position >= streamSize) - break; - int read = cfStream.Read(readBuffer, 0, readBuffer.Length); + int read = cfStream.Read(buffer, 0, buffer.Length); + if (read <= 0) + throw new EndOfStreamException(); position += read; - if (read <= 0) break; } } [Benchmark] - public void Write() + public void Write() => WriteCore(StorageModeFlags.None); + + [Benchmark] + public void WriteTransacted() => WriteCore(StorageModeFlags.Transacted); + + void WriteCore(StorageModeFlags flags) { - MemoryStream memoryStream = writeStream; - using var storage = RootStorage.Create(memoryStream); + using var storage = RootStorage.Create(writeStream, Version.V3, flags); Storage subStorage = storage.CreateStorage(storageName); CfbStream stream = subStorage.CreateStream(streamName + 0); while (stream.Length < TotalStreamSize) { - stream.Write(readBuffer, 0, readBuffer.Length); - } - } - - private void CreateFile(int streamCount) - { - int iterationCount = TotalStreamSize / BufferSize; - - byte[] buffer = new byte[BufferSize]; - buffer.AsSpan().Fill(byte.MaxValue); - const CFSConfiguration flags = CFSConfiguration.Default | CFSConfiguration.LeaveOpen; - using var compoundFile = new CompoundFile(CFSVersion.Ver_3, flags); - CFStorage st = compoundFile.RootStorage; - for (int streamId = 0; streamId < streamCount; ++streamId) - { - CFStream sm = st.AddStream(streamName + streamId); - for (int iteration = 0; iteration < iterationCount; ++iteration) - sm.Append(buffer); + stream.Write(buffer, 0, buffer.Length); } - - compoundFile.Save(readStream); - compoundFile.Close(); } } diff --git a/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj b/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj index 8d732776..209959c3 100644 --- a/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj +++ b/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj @@ -10,7 +10,6 @@ - From e489569a05d022ae591b833e2a319f21c49241da Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 14:28:12 +1300 Subject: [PATCH 044/134] Fix write overloads --- OpenMcdf3/CfbBinaryWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenMcdf3/CfbBinaryWriter.cs b/OpenMcdf3/CfbBinaryWriter.cs index b041469e..bb079926 100644 --- a/OpenMcdf3/CfbBinaryWriter.cs +++ b/OpenMcdf3/CfbBinaryWriter.cs @@ -20,13 +20,13 @@ public long Position set => BaseStream.Position = value; } -#if NETSTANDARD2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER public override void Write(ReadOnlySpan buffer) => BaseStream.Write(buffer); #endif public void Write(Guid value) { -#if NETSTANDARD2_1_OR_GREATER +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER Span localBuffer = stackalloc byte[16]; value.TryWriteBytes(localBuffer); Write(localBuffer); From 982b4ec5c3abf1ee739653be803076a13ab90f5f Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 14:43:17 +1300 Subject: [PATCH 045/134] Use stackalloc for writing bytes --- OpenMcdf3/CfbBinaryWriter.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/OpenMcdf3/CfbBinaryWriter.cs b/OpenMcdf3/CfbBinaryWriter.cs index bb079926..a8bd2327 100644 --- a/OpenMcdf3/CfbBinaryWriter.cs +++ b/OpenMcdf3/CfbBinaryWriter.cs @@ -22,6 +22,12 @@ public long Position #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER public override void Write(ReadOnlySpan buffer) => BaseStream.Write(buffer); + + public override void Write(byte value) + { + Span localBuffer = stackalloc byte[1] { value }; + Write(localBuffer); + } #endif public void Write(Guid value) From d46ecc6f82a02d571277524da735677dc27773fc Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 15:12:00 +1300 Subject: [PATCH 046/134] Add netstandard2.1 stream overrides --- OpenMcdf3/CfbBinaryWriter.cs | 4 +- OpenMcdf3/CfbStream.cs | 21 +++++++++ OpenMcdf3/FatStream.cs | 87 ++++++++++++++++++++++++++++++++++- OpenMcdf3/MiniFatStream.cs | 86 ++++++++++++++++++++++++++++++++++ OpenMcdf3/StreamExtensions.cs | 24 ++++++++-- OpenMcdf3/TransactedStream.cs | 66 ++++++++++++++++++++++++++ 6 files changed, 283 insertions(+), 5 deletions(-) diff --git a/OpenMcdf3/CfbBinaryWriter.cs b/OpenMcdf3/CfbBinaryWriter.cs index a8bd2327..f17a0339 100644 --- a/OpenMcdf3/CfbBinaryWriter.cs +++ b/OpenMcdf3/CfbBinaryWriter.cs @@ -21,6 +21,7 @@ public long Position } #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER + public override void Write(ReadOnlySpan buffer) => BaseStream.Write(buffer); public override void Write(byte value) @@ -28,9 +29,10 @@ public override void Write(byte value) Span localBuffer = stackalloc byte[1] { value }; Write(localBuffer); } + #endif - public void Write(Guid value) + public void Write(in Guid value) { #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER Span localBuffer = stackalloc byte[16]; diff --git a/OpenMcdf3/CfbStream.cs b/OpenMcdf3/CfbStream.cs index 473eae7f..933ef167 100644 --- a/OpenMcdf3/CfbStream.cs +++ b/OpenMcdf3/CfbStream.cs @@ -93,4 +93,25 @@ public override void Write(byte[] buffer, int offset, int count) stream.Write(buffer, offset, count); } + +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER + + public override int Read(Span buffer) => stream.Read(buffer); + + public override int ReadByte() => this.ReadByteCore(); + + public override void WriteByte(byte value) => this.WriteByteCore(value); + + public override void Write(ReadOnlySpan buffer) + { + this.ThrowIfNotWritable(); + + long newPosition = Position + buffer.Length; + if (newPosition > stream.Length) + SetLength(newPosition); + + stream.Write(buffer); + } + +#endif } diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index afefd4f1..c73ba459 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -1,4 +1,6 @@ -namespace OpenMcdf3; +using System.IO; + +namespace OpenMcdf3; /// /// Provides a for a stream object in a compound file./> @@ -189,4 +191,87 @@ public override void Write(byte[] buffer, int offset, int count) throw new InvalidOperationException($"End of FAT chain was reached"); } + +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER + + public override int ReadByte() => this.ReadByteCore(); + + public override int Read(Span buffer) + { + this.ThrowIfDisposed(disposed); + + if (buffer.Length == 0) + return 0; + + int maxCount = (int)Math.Min(Math.Max(Length - position, 0), int.MaxValue); + if (maxCount == 0) + return 0; + + uint chainIndex = (uint)Math.DivRem(position, ioContext.SectorSize, out long sectorOffset); + if (!chain.MoveTo(chainIndex)) + return 0; + + int realCount = Math.Min(buffer.Length, maxCount); + int readCount = 0; + do + { + Sector sector = chain.CurrentSector; + int remaining = realCount - readCount; + long readLength = Math.Min(remaining, sector.Length - sectorOffset); + ioContext.Reader.Position = sector.Position + sectorOffset; + int localOffset = readCount; + Span slice = buffer.Slice(localOffset, (int)readLength); + int read = ioContext.Reader.Read(slice); + if (read == 0) + return readCount; + position += read; + readCount += read; + sectorOffset = 0; + if (readCount >= realCount) + return readCount; + } while (chain.MoveNext()); + + return readCount; + } + + public override void WriteByte(byte value) => this.WriteByteCore(value); + + public override void Write(ReadOnlySpan buffer) + { + this.ThrowIfNotWritable(); + + if (buffer.Length == 0) + return; + + if (position + buffer.Length > ChainCapacity) + SetLength(position + buffer.Length); + + uint chainIndex = (uint)Math.DivRem(position, ioContext.SectorSize, out long sectorOffset); + if (!chain.MoveTo(chainIndex)) + throw new InvalidOperationException($"Failed to move to FAT chain index: {chainIndex}"); + + CfbBinaryWriter writer = ioContext.Writer; + int writeCount = 0; + do + { + Sector sector = chain.CurrentSector; + writer.Position = sector.Position + sectorOffset; + int remaining = buffer.Length - writeCount; + int localOffset = writeCount; + long writeLength = Math.Min(remaining, sector.Length - sectorOffset); + ReadOnlySpan slice = buffer.Slice(localOffset, (int)writeLength); + writer.Write(slice); + position += writeLength; + writeCount += (int)writeLength; + if (position > Length) + DirectoryEntry.StreamLength = position; + sectorOffset = 0; + if (writeCount >= buffer.Length) + return; + } while (chain.MoveNext()); + + throw new InvalidOperationException($"End of FAT chain was reached"); + } + +#endif } diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index 032e8370..e4357f62 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -185,4 +185,90 @@ public override void Write(byte[] buffer, int offset, int count) throw new InvalidOperationException($"End of mini FAT chain was reached."); } + +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER + + public override int ReadByte() => this.ReadByteCore(); + + public override int Read(Span buffer) + { + this.ThrowIfDisposed(disposed); + + if (buffer.Length == 0) + return 0; + + int maxCount = (int)Math.Min(Math.Max(Length - position, 0), int.MaxValue); + if (maxCount == 0) + return 0; + + uint chainIndex = (uint)Math.DivRem(position, ioContext.MiniSectorSize, out long sectorOffset); + if (!miniChain.MoveTo(chainIndex)) + return 0; + + FatStream miniStream = ioContext.MiniStream; + int realCount = Math.Min(buffer.Length, maxCount); + int readCount = 0; + do + { + MiniSector miniSector = miniChain.CurrentSector; + int remaining = realCount - readCount; + long readLength = Math.Min(remaining, miniSector.Length - sectorOffset); + miniStream.Position = miniSector.Position + sectorOffset; + int localOffset = readCount; + Span slice = buffer.Slice(localOffset, (int)readLength); + int read = miniStream.Read(slice); + if (read == 0) + return readCount; + position += read; + readCount += read; + sectorOffset = 0; + if (readCount >= realCount) + return readCount; + } while (miniChain.MoveNext()); + + return readCount; + } + + public override void WriteByte(byte value) => this.WriteByteCore(value); + + public override void Write(ReadOnlySpan buffer) + { + this.ThrowIfDisposed(disposed); + this.ThrowIfNotWritable(); + + if (buffer.Length == 0) + return; + + if (position + buffer.Length > ChainCapacity) + SetLength(position + buffer.Length); + + uint chainIndex = (uint)Math.DivRem(position, ioContext.MiniSectorSize, out long sectorOffset); + if (!miniChain.MoveTo(chainIndex)) + throw new InvalidOperationException($"Failed to move to mini FAT chain index: {chainIndex}."); + + FatStream miniStream = ioContext.MiniStream; + int writeCount = 0; + do + { + MiniSector miniSector = miniChain.CurrentSector; + long basePosition = miniSector.Position + sectorOffset; + miniStream.Seek(basePosition, SeekOrigin.Begin); + int remaining = buffer.Length - writeCount; + int localOffset = writeCount; + long writeLength = Math.Min(remaining, ioContext.MiniSectorSize - sectorOffset); + ReadOnlySpan slice = buffer.Slice(localOffset, (int)writeLength); + miniStream.Write(slice); + position += writeLength; + writeCount += (int)writeLength; + if (position > Length) + DirectoryEntry.StreamLength = position; + sectorOffset = 0; + if (writeCount >= buffer.Length) + return; + } while (miniChain.MoveNext()); + + throw new InvalidOperationException($"End of mini FAT chain was reached."); + } + +#endif } diff --git a/OpenMcdf3/StreamExtensions.cs b/OpenMcdf3/StreamExtensions.cs index b2fd63c4..ec600f63 100644 --- a/OpenMcdf3/StreamExtensions.cs +++ b/OpenMcdf3/StreamExtensions.cs @@ -2,10 +2,10 @@ namespace OpenMcdf3; -#if !NET7_0_OR_GREATER - internal static class StreamExtensions { +#if !NET7_0_OR_GREATER + public static void ReadExactly(this Stream stream, Span buffer) { byte[] array = ArrayPool.Shared.Rent(buffer.Length); @@ -35,6 +35,24 @@ public static void ReadExactly(this Stream stream, byte[] buffer, int offset, in totalRead += read; } while (totalRead < count); } -} #endif + +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER + + public static int ReadByteCore(this Stream stream) + { + Span bytes = stackalloc byte[1]; + int read = stream.Read(bytes); + return read == 0 ? -1 : bytes[0]; + } + + public static void WriteByteCore(this Stream stream, byte value) + { + ReadOnlySpan bytes = stackalloc byte[] { value }; + stream.Write(bytes); + } + +#endif +} + diff --git a/OpenMcdf3/TransactedStream.cs b/OpenMcdf3/TransactedStream.cs index 15c35227..6af5179c 100644 --- a/OpenMcdf3/TransactedStream.cs +++ b/OpenMcdf3/TransactedStream.cs @@ -125,4 +125,70 @@ public void Revert() { dirtySectorPositions.Clear(); } + +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER + + public override int ReadByte() => this.ReadByteCore(); + + public override int Read(Span buffer) + { + uint sectorId = (uint)Math.DivRem(originalStream.Position, ioContext.SectorSize, out long sectorOffset); + int remainingFromSector = ioContext.SectorSize - (int)sectorOffset; + int localCount = Math.Min(buffer.Length, remainingFromSector); + Debug.Assert(localCount == buffer.Length); + + Span slice = buffer.Slice(0, localCount); + int read; + if (dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) + { + overlayStream.Position = overlayPosition + sectorOffset; + read = overlayStream.Read(slice); + originalStream.Seek(read, SeekOrigin.Current); + } + else + { + read = originalStream.Read(slice); + } + + return read; + } + + public override void WriteByte(byte value) => this.WriteByteCore(value); + + public override void Write(ReadOnlySpan buffer) + { + uint sectorId = (uint)Math.DivRem(originalStream.Position, ioContext.SectorSize, out long sectorOffset); + int remainingFromSector = ioContext.SectorSize - (int)sectorOffset; + int localCount = Math.Min(buffer.Length, remainingFromSector); + Debug.Assert(localCount == buffer.Length); + // TODO: Loop through the buffer and write to the overlay stream + + bool added = false; + if (!dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) + { + overlayPosition = overlayStream.Length; + dirtySectorPositions.Add(sectorId, overlayPosition); + added = true; + } + + if (added && originalStream.Position < originalStream.Length && localCount != ioContext.SectorSize) + { + // Copy the existing sector data + long originalPosition = originalStream.Position; + originalStream.Position = originalPosition - sectorOffset; + originalStream.ReadExactly(this.buffer); + originalStream.Position = originalPosition; + + overlayStream.Position = overlayPosition; + overlayStream.Write(this.buffer, 0, this.buffer.Length); + } + + if (overlayStream.Length < overlayPosition + ioContext.SectorSize) + overlayStream.SetLength(overlayPosition + ioContext.SectorSize); + overlayStream.Position = overlayPosition + sectorOffset; + overlayStream.Write(buffer); + originalStream.Seek(localCount, SeekOrigin.Current); + } + +#endif } \ No newline at end of file From 97794f70d5c2a92f4d054df45f4aa0c919462d99 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 16:25:53 +1300 Subject: [PATCH 047/134] Benchmark stuff --- OpenMcdf3.Benchmarks/InMemory.cs | 10 +++++----- OpenMcdf3.Perf/Program.cs | 6 +++--- OpenMcdf3/IOContext.cs | 2 +- OpenMcdf3/OpenMcdf3.csproj | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/OpenMcdf3.Benchmarks/InMemory.cs b/OpenMcdf3.Benchmarks/InMemory.cs index be786753..3dbe73d6 100644 --- a/OpenMcdf3.Benchmarks/InMemory.cs +++ b/OpenMcdf3.Benchmarks/InMemory.cs @@ -3,7 +3,7 @@ namespace OpenMcdf3.Benchmark; -[SimpleJob] +[ShortRunJob] [CsvExporter] [HtmlExporter] [MarkdownExporter] @@ -17,8 +17,8 @@ public class InMemory : IDisposable private const string storageName = "MyStorage"; private const string streamName = "MyStream"; - private MemoryStream readStream = new(); - private MemoryStream writeStream = new(); + private FileStream readStream = File.Create(Path.GetTempFileName()); + private FileStream writeStream = File.Create(Path.GetTempFileName()); private byte[] buffer = Array.Empty(); @@ -38,8 +38,8 @@ public void Dispose() public void GlobalSetup() { buffer = new byte[BufferSize]; - readStream = new MemoryStream(2 * TotalStreamSize); - writeStream = new MemoryStream(2 * TotalStreamSize); + //readStream = new MemoryStream(2 * TotalStreamSize); + //writeStream = new MemoryStream(2 * TotalStreamSize); using var storage = RootStorage.Create(readStream, Version.V3, StorageModeFlags.LeaveOpen); using CfbStream stream = storage.CreateStream(streamName); diff --git a/OpenMcdf3.Perf/Program.cs b/OpenMcdf3.Perf/Program.cs index 63bb11d7..d26fa186 100644 --- a/OpenMcdf3.Perf/Program.cs +++ b/OpenMcdf3.Perf/Program.cs @@ -7,7 +7,7 @@ internal sealed class Program static void Main(string[] args) { var stopwatch = Stopwatch.StartNew(); - Write(Version.V3, 512, 1024); + Write(Version.V3, 512, 2 * 1024); Console.WriteLine($"Elapsed: {stopwatch.Elapsed}"); } @@ -20,8 +20,8 @@ static void Write(Version version, int length, int iterations) //byte[] actualBuffer = new byte[length]; - using MemoryStream memoryStream = new(2 * length); - using var rootStorage = RootStorage.Create(memoryStream, version); + using MemoryStream memoryStream = new(2 * length * iterations); + using var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.Transacted); using Stream stream = rootStorage.CreateStream("TestStream"); for (int i = 0; i < iterations; i++) diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index b42eb961..72111bc6 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -80,7 +80,7 @@ public IOContext(Stream stream, Version version, IOContextFlags contextFlags = I Stream transactedStream = stream; if (contextFlags.HasFlag(IOContextFlags.Transacted)) { - Stream overlayStream = stream is MemoryStream ? new MemoryStream() : File.Create(Path.GetTempFileName()); + Stream overlayStream = stream is MemoryStream ? new MemoryStream((int)stream.Length) : File.Create(Path.GetTempFileName()); transactedStream = new TransactedStream(this, stream, overlayStream); } diff --git a/OpenMcdf3/OpenMcdf3.csproj b/OpenMcdf3/OpenMcdf3.csproj index 081a0aef..48b852fb 100644 --- a/OpenMcdf3/OpenMcdf3.csproj +++ b/OpenMcdf3/OpenMcdf3.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1;net8.0 + netstandard2.0;net8.0 11.0 enable enable From 45ace4ffcdec0e94203020e8fab25e9b098f4e84 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 21:25:50 +1300 Subject: [PATCH 048/134] Minor refactor --- OpenMcdf3/FatEntry.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenMcdf3/FatEntry.cs b/OpenMcdf3/FatEntry.cs index ecef6605..db01412c 100644 --- a/OpenMcdf3/FatEntry.cs +++ b/OpenMcdf3/FatEntry.cs @@ -9,5 +9,5 @@ internal record struct FatEntry(uint Index, uint Value) public readonly bool IsFree => Value == SectorType.Free; - public readonly override string ToString() => $"#{Index}: {Value}"; + public override readonly string ToString() => $"#{Index}: {Value}"; } From 315fd663d0c54ac185996ea848b3da21713a4430 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 21:26:23 +1300 Subject: [PATCH 049/134] Optimize directory entry writes --- OpenMcdf3/FatStream.cs | 17 ++++++++++------- OpenMcdf3/MiniFatStream.cs | 12 ++++++------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index c73ba459..ad98aed5 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -48,6 +48,8 @@ protected override void Dispose(bool disposing) { if (!disposed) { + Flush(); + chain.Dispose(); disposed = true; } @@ -59,7 +61,12 @@ protected override void Dispose(bool disposing) public override void Flush() { this.ThrowIfDisposed(disposed); - ioContext.Writer!.Flush(); // TODO: Check validity + + if (CanWrite) + { + ioContext.Write(DirectoryEntry); + ioContext.Writer!.Flush(); + } } /// @@ -145,12 +152,8 @@ public override void SetLength(long value) else if (value <= ChainCapacity - ioContext.SectorSize) chain.Shrink(requiredChainLength); - if (DirectoryEntry.StartSectorId != chain.StartId || DirectoryEntry.StreamLength != value) - { - DirectoryEntry.StartSectorId = chain.StartId; - DirectoryEntry.StreamLength = value; - ioContext.Write(DirectoryEntry); - } + DirectoryEntry.StartSectorId = chain.StartId; + DirectoryEntry.StreamLength = value; } /// diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index e4357f62..c7c00984 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -39,6 +39,8 @@ protected override void Dispose(bool disposing) { if (!disposed) { + Flush(); + miniChain.Dispose(); disposed = true; } @@ -50,6 +52,8 @@ public override void Flush() { this.ThrowIfDisposed(disposed); + if (CanWrite) + ioContext.Write(DirectoryEntry); ioContext.MiniStream.Flush(); } @@ -138,12 +142,8 @@ public override void SetLength(long value) else if (value <= ChainCapacity - ioContext.MiniSectorSize) miniChain.Shrink(requiredChainLength); - if (DirectoryEntry.StartSectorId != miniChain.StartId || DirectoryEntry.StreamLength != value) - { - DirectoryEntry.StartSectorId = miniChain.StartId; - DirectoryEntry.StreamLength = value; - ioContext.Write(DirectoryEntry); - } + DirectoryEntry.StartSectorId = miniChain.StartId; + DirectoryEntry.StreamLength = value; } public override void Write(byte[] buffer, int offset, int count) From d496dda45b4a8ce2f8efd9d088c128cf1ea7c2bd Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 21:58:31 +1300 Subject: [PATCH 050/134] Minor performance improvements --- OpenMcdf3/Fat.cs | 33 ++++++++++++++++++--------------- OpenMcdf3/MiniFat.cs | 4 ++-- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs index 38223b7a..48993745 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf3/Fat.cs @@ -10,14 +10,16 @@ internal sealed class Fat : IEnumerable, IDisposable { private readonly IOContext ioContext; private readonly FatSectorEnumerator fatSectorEnumerator; - - internal int FatElementsPerSector => ioContext.SectorSize / sizeof(uint); - - internal int DifatElementsPerSector => (ioContext.SectorSize / sizeof(uint)) - 1; + private readonly int DifatArrayElementCount; + private readonly int FatElementsPerSector; + private readonly int DifatElementsPerSector; public Fat(IOContext ioContext) { this.ioContext = ioContext; + FatElementsPerSector = ioContext.SectorSize / sizeof(uint); + DifatElementsPerSector = FatElementsPerSector - 1; + DifatArrayElementCount = Header.DifatArrayLength * FatElementsPerSector; fatSectorEnumerator = new(ioContext); } @@ -44,19 +46,22 @@ public uint this[uint key] uint GetSectorIndexAndElementOffset(uint key, out long elementIndex) { - int DifatArrayElementCount = Header.DifatArrayLength * FatElementsPerSector; if (key < DifatArrayElementCount) return (uint)Math.DivRem(key, FatElementsPerSector, out elementIndex); - return (uint)Math.DivRem(key - DifatArrayElementCount, DifatElementsPerSector, out elementIndex); } + bool TryMoveToSectorForKey(uint key, out long offset) + { + uint sectorId = GetSectorIndexAndElementOffset(key, out offset); + return fatSectorEnumerator.MoveTo(sectorId); + } + public bool TryGetValue(uint key, out uint value) { ThrowHelper.ThrowIfSectorIdIsInvalid(key); - uint sectorId = GetSectorIndexAndElementOffset(key, out long elementIndex); - bool ok = fatSectorEnumerator.MoveTo(sectorId); + bool ok = TryMoveToSectorForKey(key, out long elementIndex); if (!ok) { value = uint.MaxValue; @@ -73,8 +78,7 @@ public bool TrySetValue(uint key, uint value) { ThrowHelper.ThrowIfSectorIdIsInvalid(key); - uint fatSectorIndex = GetSectorIndexAndElementOffset(key, out long elementIndex); - if (!fatSectorEnumerator.MoveTo(fatSectorIndex)) + if (!TryMoveToSectorForKey(key, out long elementIndex)) return false; CfbBinaryWriter writer = ioContext.Writer; @@ -91,15 +95,14 @@ public uint Add(FatEnumerator fatEnumerator, uint startIndex) { ThrowHelper.ThrowIfSectorIdIsInvalid(startIndex); - bool movedToFreeEntry = fatEnumerator.MoveTo(startIndex) && fatEnumerator.MoveNextFreeEntry(); + bool movedToFreeEntry = fatEnumerator.MoveTo(startIndex) + && fatEnumerator.MoveNextFreeEntry(); if (!movedToFreeEntry) { uint newSectorId = fatSectorEnumerator.Add(); - bool ok = fatEnumerator.MoveTo(newSectorId); - Debug.Assert(ok); - - ok = fatEnumerator.MoveNextFreeEntry(); + // Next id must be free + bool ok = fatEnumerator.MoveTo(newSectorId + 1); Debug.Assert(ok); } diff --git a/OpenMcdf3/MiniFat.cs b/OpenMcdf3/MiniFat.cs index 8675ba92..5fe425a0 100644 --- a/OpenMcdf3/MiniFat.cs +++ b/OpenMcdf3/MiniFat.cs @@ -10,12 +10,12 @@ internal sealed class MiniFat : IEnumerable, IDisposable { private readonly IOContext ioContext; private readonly FatChainEnumerator fatChainEnumerator; - - internal int ElementsPerSector => ioContext.SectorSize / sizeof(uint); + private readonly int ElementsPerSector; public MiniFat(IOContext ioContext) { this.ioContext = ioContext; + ElementsPerSector = ioContext.SectorSize / sizeof(uint); fatChainEnumerator = new(ioContext, ioContext.Header.FirstMiniFatSectorId); } From e40aae66600e9c433a2065a347a69063a592d7cd Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 22:10:21 +1300 Subject: [PATCH 051/134] Perf exe improvements --- OpenMcdf3.Perf/Program.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/OpenMcdf3.Perf/Program.cs b/OpenMcdf3.Perf/Program.cs index d26fa186..2e356c9a 100644 --- a/OpenMcdf3.Perf/Program.cs +++ b/OpenMcdf3.Perf/Program.cs @@ -7,11 +7,11 @@ internal sealed class Program static void Main(string[] args) { var stopwatch = Stopwatch.StartNew(); - Write(Version.V3, 512, 2 * 1024); + Write(Version.V3, 4096, 1024 / 4, StorageModeFlags.Transacted); Console.WriteLine($"Elapsed: {stopwatch.Elapsed}"); } - static void Write(Version version, int length, int iterations) + static void Write(Version version, int length, int iterations, StorageModeFlags storageModeFlags) { // Fill with bytes equal to their position modulo 256 byte[] expectedBuffer = new byte[length]; @@ -21,12 +21,15 @@ static void Write(Version version, int length, int iterations) //byte[] actualBuffer = new byte[length]; using MemoryStream memoryStream = new(2 * length * iterations); - using var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.Transacted); + using var rootStorage = RootStorage.Create(memoryStream, version, storageModeFlags); using Stream stream = rootStorage.CreateStream("TestStream"); for (int i = 0; i < iterations; i++) { stream.Write(expectedBuffer, 0, expectedBuffer.Length); } + + if (storageModeFlags.HasFlag(StorageModeFlags.Transacted)) + rootStorage.Commit(); } } From f1f8a67e24f690ba9e6c895c7342c2d1eb14d7d8 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 22:46:16 +1300 Subject: [PATCH 052/134] Cache current FAT sector --- OpenMcdf3/CfbBinaryWriter.cs | 6 ----- OpenMcdf3/Fat.cs | 44 ++++++++++++++++++++++++++------ OpenMcdf3/FatSectorEnumerator.cs | 2 ++ OpenMcdf3/IOContext.cs | 1 + OpenMcdf3/TransactedStream.cs | 2 +- 5 files changed, 40 insertions(+), 15 deletions(-) diff --git a/OpenMcdf3/CfbBinaryWriter.cs b/OpenMcdf3/CfbBinaryWriter.cs index f17a0339..73e65954 100644 --- a/OpenMcdf3/CfbBinaryWriter.cs +++ b/OpenMcdf3/CfbBinaryWriter.cs @@ -24,12 +24,6 @@ public long Position public override void Write(ReadOnlySpan buffer) => BaseStream.Write(buffer); - public override void Write(byte value) - { - Span localBuffer = stackalloc byte[1] { value }; - Write(localBuffer); - } - #endif public void Write(in Guid value) diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs index 48993745..70baf9d2 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf3/Fat.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System.Buffers.Binary; +using System.Collections; using System.Diagnostics; namespace OpenMcdf3; @@ -13,6 +14,8 @@ internal sealed class Fat : IEnumerable, IDisposable private readonly int DifatArrayElementCount; private readonly int FatElementsPerSector; private readonly int DifatElementsPerSector; + private readonly byte[] sector; + private bool isDirty; public Fat(IOContext ioContext) { @@ -21,10 +24,13 @@ public Fat(IOContext ioContext) DifatElementsPerSector = FatElementsPerSector - 1; DifatArrayElementCount = Header.DifatArrayLength * FatElementsPerSector; fatSectorEnumerator = new(ioContext); + sector = new byte[ioContext.SectorSize]; } public void Dispose() { + Flush(); + fatSectorEnumerator.Dispose(); } @@ -51,10 +57,33 @@ uint GetSectorIndexAndElementOffset(uint key, out long elementIndex) return (uint)Math.DivRem(key - DifatArrayElementCount, DifatElementsPerSector, out elementIndex); } + public void Flush() + { + if (isDirty) + { + CfbBinaryWriter writer = ioContext.Writer; + writer.Position = fatSectorEnumerator.Current.Position; + writer.Write(sector); + isDirty = false; + } + } + bool TryMoveToSectorForKey(uint key, out long offset) { uint sectorId = GetSectorIndexAndElementOffset(key, out offset); - return fatSectorEnumerator.MoveTo(sectorId); + if (fatSectorEnumerator.IsAt(sectorId)) + return true; + + Flush(); + + bool ok = fatSectorEnumerator.MoveTo(sectorId); + if (!ok) + return false; + + CfbBinaryReader reader = ioContext.Reader; + reader.Position = fatSectorEnumerator.Current.Position; + reader.Read(sector); + return true; } public bool TryGetValue(uint key, out uint value) @@ -68,9 +97,8 @@ public bool TryGetValue(uint key, out uint value) return false; } - CfbBinaryReader reader = ioContext.Reader; - reader.Position = fatSectorEnumerator.Current.Position + (elementIndex * sizeof(uint)); - value = reader.ReadUInt32(); + ReadOnlySpan slice = sector.AsSpan((int)elementIndex * sizeof(uint)); + value = BinaryPrimitives.ReadUInt32LittleEndian(slice); return true; } @@ -81,9 +109,9 @@ public bool TrySetValue(uint key, uint value) if (!TryMoveToSectorForKey(key, out long elementIndex)) return false; - CfbBinaryWriter writer = ioContext.Writer; - writer.Position = fatSectorEnumerator.Current.Position + (elementIndex * sizeof(uint)); - writer.Write(value); + Span slice = sector.AsSpan((int)elementIndex * sizeof(uint)); + BinaryPrimitives.WriteUInt32LittleEndian(slice, value); + isDirty = true; return true; } diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index ce78326f..e1a4c66a 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -74,6 +74,8 @@ public bool MoveNext() return true; } + public bool IsAt(uint index) => !start && index == this.index; + /// /// Moves the enumerator to the specified sector. /// diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index 72111bc6..a58e09ca 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -148,6 +148,7 @@ public void Commit() throw new InvalidOperationException("Cannot commit non-transacted storage."); WriteHeader(); + Fat.Flush(); transactedStream.Commit(); } public void Revert() diff --git a/OpenMcdf3/TransactedStream.cs b/OpenMcdf3/TransactedStream.cs index 6af5179c..478e3259 100644 --- a/OpenMcdf3/TransactedStream.cs +++ b/OpenMcdf3/TransactedStream.cs @@ -87,7 +87,7 @@ public override void Write(byte[] buffer, int offset, int count) added = true; } - if (added && originalStream.Position < originalStream.Length && localCount != ioContext.SectorSize) + if (added && localCount != ioContext.SectorSize && originalStream.Position < originalStream.Length) { // Copy the existing sector data long originalPosition = originalStream.Position; From dc11f6cc703610cca04bdea1c76399745249a36a Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 23:03:28 +1300 Subject: [PATCH 053/134] Optimize SetLength --- OpenMcdf3/TransactedStream.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenMcdf3/TransactedStream.cs b/OpenMcdf3/TransactedStream.cs index 478e3259..53377df0 100644 --- a/OpenMcdf3/TransactedStream.cs +++ b/OpenMcdf3/TransactedStream.cs @@ -99,10 +99,10 @@ public override void Write(byte[] buffer, int offset, int count) overlayStream.Write(this.buffer, 0, this.buffer.Length); } - if (overlayStream.Length < overlayPosition + ioContext.SectorSize) - overlayStream.SetLength(overlayPosition + ioContext.SectorSize); overlayStream.Position = overlayPosition + sectorOffset; overlayStream.Write(buffer, offset, localCount); + if (overlayStream.Length < overlayPosition + ioContext.SectorSize) + overlayStream.SetLength(overlayPosition + ioContext.SectorSize); originalStream.Seek(localCount, SeekOrigin.Current); } From f4f548b51696c5fa5f4c01b75d7bb9d7d08387d6 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 23:18:42 +1300 Subject: [PATCH 054/134] Benchmark improvement --- OpenMcdf3.Benchmarks/InMemory.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/OpenMcdf3.Benchmarks/InMemory.cs b/OpenMcdf3.Benchmarks/InMemory.cs index 3dbe73d6..90ca4db4 100644 --- a/OpenMcdf3.Benchmarks/InMemory.cs +++ b/OpenMcdf3.Benchmarks/InMemory.cs @@ -12,13 +12,14 @@ namespace OpenMcdf3.Benchmark; [Orderer(SummaryOrderPolicy.FastestToSlowest)] public class InMemory : IDisposable { + bool inMemory = false; private const int Kb = 1024; private const int Mb = Kb * Kb; private const string storageName = "MyStorage"; private const string streamName = "MyStream"; - private FileStream readStream = File.Create(Path.GetTempFileName()); - private FileStream writeStream = File.Create(Path.GetTempFileName()); + private Stream? readStream; + private Stream? writeStream; private byte[] buffer = Array.Empty(); @@ -38,8 +39,8 @@ public void Dispose() public void GlobalSetup() { buffer = new byte[BufferSize]; - //readStream = new MemoryStream(2 * TotalStreamSize); - //writeStream = new MemoryStream(2 * TotalStreamSize); + readStream = inMemory ? new MemoryStream(2 * TotalStreamSize) : File.Create(Path.GetTempFileName()); + writeStream = inMemory ? new MemoryStream(2 * TotalStreamSize) : File.Create(Path.GetTempFileName()); using var storage = RootStorage.Create(readStream, Version.V3, StorageModeFlags.LeaveOpen); using CfbStream stream = storage.CreateStream(streamName); From d0d86b02d2ef41f76267d9ab70d8525ce0033c1b Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 23:19:09 +1300 Subject: [PATCH 055/134] Optimize adding new FAT sector --- OpenMcdf3/FatSectorEnumerator.cs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index e1a4c66a..ec72ce59 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Diagnostics; namespace OpenMcdf3; @@ -145,10 +146,10 @@ public uint Add() { // No FAT sectors are free, so add a new one Header header = ioContext.Header; - (uint lastIndex, Sector lastSector) = MoveToEnd(); - uint nextIndex = lastIndex + 1; - long id = Math.Max(0, (ioContext.Reader.BaseStream.Length - ioContext.SectorSize) / ioContext.SectorSize); // TODO: Check - Sector newSector = new((uint)id, ioContext.SectorSize); + uint nextIndex = ioContext.Header.FatSectorCount + ioContext.Header.DifatSectorCount; + uint lastIndex = nextIndex - 1; + uint id = (uint)Math.Max(0, (ioContext.Reader.BaseStream.Length - ioContext.SectorSize) / ioContext.SectorSize); // TODO: Check + Sector newSector = new(id, ioContext.SectorSize); CfbBinaryWriter writer = ioContext.Writer; writer.Position = newSector.Position; @@ -163,20 +164,30 @@ public uint Add() header.Difat[nextIndex] = newSector.Id; header.FatSectorCount++; // TODO: Check + + writer.Position = newSector.Position; + writer.Write(SectorDataCache.GetFatEntryData(newSector.Length)); } else { + bool ok = MoveTo(lastIndex); + Debug.Assert(ok); + + Sector lastSector = current; + writer.Position = lastSector.EndPosition - sizeof(uint); + writer.Write(newSector.Id); + index = nextIndex; current = newSector; difatSectorId = newSector.Id; sectorType = SectorType.Difat; + writer.Position = newSector.Position; + writer.Write(SectorDataCache.GetFatEntryData(newSector.Length)); + writer.Position = newSector.EndPosition - sizeof(uint); writer.Write(SectorType.EndOfChain); - writer.Position = lastSector.EndPosition - sizeof(uint); - writer.Write(newSector.Id); - // Chain the sector if (header.FirstDifatSectorId == SectorType.EndOfChain) header.FirstDifatSectorId = newSector.Id; From 8fb7b948700bf4b8d13c1859384b33d78452b765 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 6 Nov 2024 10:31:20 +1300 Subject: [PATCH 056/134] Simplify extending base stream --- OpenMcdf3/FatSectorEnumerator.cs | 3 +-- OpenMcdf3/IOContext.cs | 7 ++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index ec72ce59..2e5b625f 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -148,8 +148,7 @@ public uint Add() Header header = ioContext.Header; uint nextIndex = ioContext.Header.FatSectorCount + ioContext.Header.DifatSectorCount; uint lastIndex = nextIndex - 1; - uint id = (uint)Math.Max(0, (ioContext.Reader.BaseStream.Length - ioContext.SectorSize) / ioContext.SectorSize); // TODO: Check - Sector newSector = new(id, ioContext.SectorSize); + Sector newSector = new(ioContext.SectorCount, ioContext.SectorSize); CfbBinaryWriter writer = ioContext.Writer; writer.Position = newSector.Position; diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index a58e09ca..10ef2f89 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -68,6 +68,10 @@ public FatStream MiniStream public Version Version => (Version)Header.MajorVersion; + public long Length => Reader.BaseStream.Length; + + public uint SectorCount => (uint)Math.Max(0, (Length - SectorSize) / SectorSize); // TODO: Check + public IOContext(Stream stream, Version version, IOContextFlags contextFlags = IOContextFlags.None) { this.stream = stream; @@ -80,7 +84,7 @@ public IOContext(Stream stream, Version version, IOContextFlags contextFlags = I Stream transactedStream = stream; if (contextFlags.HasFlag(IOContextFlags.Transacted)) { - Stream overlayStream = stream is MemoryStream ? new MemoryStream((int)stream.Length) : File.Create(Path.GetTempFileName()); + Stream overlayStream = stream is MemoryStream ? new MemoryStream() : File.Create(Path.GetTempFileName()); transactedStream = new TransactedStream(this, stream, overlayStream); } @@ -151,6 +155,7 @@ public void Commit() Fat.Flush(); transactedStream.Commit(); } + public void Revert() { if (writer is null || writer.BaseStream is not TransactedStream transactedStream) From 41aee2ac610f160b896f09a87432c10505a89ea7 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 5 Nov 2024 23:32:06 +1300 Subject: [PATCH 057/134] Improve chain extension --- OpenMcdf3/FatChainEnumerator.cs | 35 +++++++++++++++++++-------------- OpenMcdf3/FatStream.cs | 19 ++++++++++++------ 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/OpenMcdf3/FatChainEnumerator.cs b/OpenMcdf3/FatChainEnumerator.cs index 2d538656..cc794d04 100644 --- a/OpenMcdf3/FatChainEnumerator.cs +++ b/OpenMcdf3/FatChainEnumerator.cs @@ -133,21 +133,7 @@ public long GetLength() /// The ID of the new sector public uint Extend() { - if (startId == SectorType.EndOfChain) - { - startId = ioContext.Fat.Add(fatEnumerator, 0); - return startId; - } - - uint lastId = startId; - while (MoveNext()) - { - lastId = current.Value; - } - - uint id = ioContext.Fat.Add(fatEnumerator, lastId); - ioContext.Fat[lastId] = id; - return id; + return ExtendFrom(0); } /// @@ -182,6 +168,25 @@ public void Extend(uint requiredChainLength) length = requiredChainLength; } + public uint ExtendFrom(uint hintId) + { + if (startId == SectorType.EndOfChain) + { + startId = ioContext.Fat.Add(fatEnumerator, hintId); + return startId; + } + + uint lastId = startId; + while (MoveNext()) + { + lastId = current.Value; + } + + uint id = ioContext.Fat.Add(fatEnumerator, lastId); + ioContext.Fat[lastId] = id; + return id; + } + public void Shrink(uint requiredChainLength) { uint chainLength = (uint)GetLength(); diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index ad98aed5..ef711519 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -166,23 +166,28 @@ public override void Write(byte[] buffer, int offset, int count) if (count == 0) return; - if (position + count > ChainCapacity) - SetLength(position + count); + //if (position + count > ChainCapacity) + // SetLength(position + count); uint chainIndex = (uint)Math.DivRem(position, ioContext.SectorSize, out long sectorOffset); - if (!chain.MoveTo(chainIndex)) - throw new InvalidOperationException($"Failed to move to FAT chain index: {chainIndex}"); CfbBinaryWriter writer = ioContext.Writer; int writeCount = 0; - do + uint lastIndex = 0; + for (; ; ) { + if (!chain.MoveTo(chainIndex)) + { + lastIndex = chain.ExtendFrom(lastIndex); + } + Sector sector = chain.CurrentSector; writer.Position = sector.Position + sectorOffset; int remaining = count - writeCount; int localOffset = offset + writeCount; long writeLength = Math.Min(remaining, sector.Length - sectorOffset); writer.Write(buffer, localOffset, (int)writeLength); + ioContext.ExtendStreamLength(sector.EndPosition); position += writeLength; writeCount += (int)writeLength; if (position > Length) @@ -190,7 +195,9 @@ public override void Write(byte[] buffer, int offset, int count) sectorOffset = 0; if (writeCount >= count) return; - } while (chain.MoveNext()); + + chainIndex++; + } throw new InvalidOperationException($"End of FAT chain was reached"); } From a35e3716f5cc268c94c7a42c962bebde5360803e Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 6 Nov 2024 11:21:21 +1300 Subject: [PATCH 058/134] Optimize transacted stream --- OpenMcdf3/TransactedStream.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/OpenMcdf3/TransactedStream.cs b/OpenMcdf3/TransactedStream.cs index 53377df0..2391f9e0 100644 --- a/OpenMcdf3/TransactedStream.cs +++ b/OpenMcdf3/TransactedStream.cs @@ -62,10 +62,7 @@ public override int Read(byte[] buffer, int offset, int count) return read; } - public override long Seek(long offset, SeekOrigin origin) - { - return originalStream.Seek(offset, origin); - } + public override long Seek(long offset, SeekOrigin origin) => originalStream.Seek(offset, origin); public override void SetLength(long value) => overlayStream.SetLength(value); @@ -87,13 +84,12 @@ public override void Write(byte[] buffer, int offset, int count) added = true; } - if (added && localCount != ioContext.SectorSize && originalStream.Position < originalStream.Length) + long originalPosition = originalStream.Position; + if (added && localCount != ioContext.SectorSize && originalPosition < originalStream.Length) { // Copy the existing sector data - long originalPosition = originalStream.Position; originalStream.Position = originalPosition - sectorOffset; originalStream.ReadExactly(this.buffer); - originalStream.Position = originalPosition; overlayStream.Position = overlayPosition; overlayStream.Write(this.buffer, 0, this.buffer.Length); @@ -101,9 +97,7 @@ public override void Write(byte[] buffer, int offset, int count) overlayStream.Position = overlayPosition + sectorOffset; overlayStream.Write(buffer, offset, localCount); - if (overlayStream.Length < overlayPosition + ioContext.SectorSize) - overlayStream.SetLength(overlayPosition + ioContext.SectorSize); - originalStream.Seek(localCount, SeekOrigin.Current); + originalStream.Position = originalPosition + localCount; } public void Commit() From 35ff3907659cbff1048e55ba0eb585bb597f642c Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 6 Nov 2024 11:22:29 +1300 Subject: [PATCH 059/134] Flush mini stream --- OpenMcdf3/IOContext.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index 10ef2f89..d1cef369 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -151,8 +151,9 @@ public void Commit() if (writer is null || writer.BaseStream is not TransactedStream transactedStream) throw new InvalidOperationException("Cannot commit non-transacted storage."); - WriteHeader(); + miniStream?.Flush(); Fat.Flush(); + WriteHeader(); transactedStream.Commit(); } From ad77175f7a102c4009ab9c95b52d8e5da9d0be42 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 6 Nov 2024 12:08:20 +1300 Subject: [PATCH 060/134] Fix transaction stream length --- OpenMcdf3/IOContext.cs | 13 +++++++++---- OpenMcdf3/TransactedStream.cs | 15 ++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index d1cef369..d9dae135 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -68,7 +68,7 @@ public FatStream MiniStream public Version Version => (Version)Header.MajorVersion; - public long Length => Reader.BaseStream.Length; + public long Length { get; private set; } public uint SectorCount => (uint)Math.Max(0, (Length - SectorSize) / SectorSize); // TODO: Check @@ -80,6 +80,7 @@ public IOContext(Stream stream, Version version, IOContextFlags contextFlags = I Header = contextFlags.HasFlag(IOContextFlags.Create) ? new(version) : reader.ReadHeader(); SectorSize = 1 << Header.SectorShift; MiniSectorSize = 1 << Header.MiniSectorShift; + Length = stream.Length; Stream transactedStream = stream; if (contextFlags.HasFlag(IOContextFlags.Transacted)) @@ -116,7 +117,12 @@ public void Dispose() if (!IsDisposed) { if (writer is not null && writer.BaseStream is not TransactedStream) + { + // Ensure the stream is as long as expected + writer.BaseStream.SetLength(Length); WriteHeader(); + } + miniStream?.Dispose(); miniFat?.Dispose(); directoryEnumerator.Dispose(); @@ -129,9 +135,8 @@ public void Dispose() public void ExtendStreamLength(long length) { - Stream baseStream = Writer.BaseStream; - if (baseStream.Length < length) - baseStream.SetLength(length); + if (Length < length) + Length = length; } public void WriteHeader() diff --git a/OpenMcdf3/TransactedStream.cs b/OpenMcdf3/TransactedStream.cs index 2391f9e0..2736e250 100644 --- a/OpenMcdf3/TransactedStream.cs +++ b/OpenMcdf3/TransactedStream.cs @@ -64,7 +64,7 @@ public override int Read(byte[] buffer, int offset, int count) public override long Seek(long offset, SeekOrigin origin) => originalStream.Seek(offset, origin); - public override void SetLength(long value) => overlayStream.SetLength(value); + public override void SetLength(long value) => throw new NotImplementedException(); public override void Write(byte[] buffer, int offset, int count) { @@ -97,6 +97,8 @@ public override void Write(byte[] buffer, int offset, int count) overlayStream.Position = overlayPosition + sectorOffset; overlayStream.Write(buffer, offset, localCount); + if (overlayStream.Length < overlayPosition + ioContext.SectorSize) + overlayStream.SetLength(overlayPosition + ioContext.SectorSize); originalStream.Position = originalPosition + localCount; } @@ -165,23 +167,22 @@ public override void Write(ReadOnlySpan buffer) added = true; } - if (added && originalStream.Position < originalStream.Length && localCount != ioContext.SectorSize) + long originalPosition = originalStream.Position; + if (added && localCount != ioContext.SectorSize && originalPosition < originalStream.Length) { // Copy the existing sector data - long originalPosition = originalStream.Position; originalStream.Position = originalPosition - sectorOffset; originalStream.ReadExactly(this.buffer); - originalStream.Position = originalPosition; overlayStream.Position = overlayPosition; overlayStream.Write(this.buffer, 0, this.buffer.Length); } - if (overlayStream.Length < overlayPosition + ioContext.SectorSize) - overlayStream.SetLength(overlayPosition + ioContext.SectorSize); overlayStream.Position = overlayPosition + sectorOffset; overlayStream.Write(buffer); - originalStream.Seek(localCount, SeekOrigin.Current); + if (overlayStream.Length < overlayPosition + ioContext.SectorSize) + overlayStream.SetLength(overlayPosition + ioContext.SectorSize); + originalStream.Position = originalPosition + localCount; } #endif From cb549bd5b2077667f593fcfe72bc5ad6d6c09680 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 6 Nov 2024 12:54:15 +1300 Subject: [PATCH 061/134] Cache the current mini FAT sector --- OpenMcdf3/FatChainEnumerator.cs | 2 + OpenMcdf3/IOContext.cs | 1 + OpenMcdf3/MiniFat.cs | 73 +++++++++++++++++++++++++-------- 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/OpenMcdf3/FatChainEnumerator.cs b/OpenMcdf3/FatChainEnumerator.cs index cc794d04..18c89890 100644 --- a/OpenMcdf3/FatChainEnumerator.cs +++ b/OpenMcdf3/FatChainEnumerator.cs @@ -47,6 +47,8 @@ public FatChainEntry Current /// object IEnumerator.Current => Current; + public bool IsAt(uint index) => !start && index == this.index; + /// public bool MoveNext() { diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index d9dae135..f45d63cb 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -157,6 +157,7 @@ public void Commit() throw new InvalidOperationException("Cannot commit non-transacted storage."); miniStream?.Flush(); + miniFat?.Flush(); Fat.Flush(); WriteHeader(); transactedStream.Commit(); diff --git a/OpenMcdf3/MiniFat.cs b/OpenMcdf3/MiniFat.cs index 5fe425a0..e2a526f4 100644 --- a/OpenMcdf3/MiniFat.cs +++ b/OpenMcdf3/MiniFat.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System.Buffers.Binary; +using System.Collections; using System.Diagnostics; namespace OpenMcdf3; @@ -11,15 +12,34 @@ internal sealed class MiniFat : IEnumerable, IDisposable private readonly IOContext ioContext; private readonly FatChainEnumerator fatChainEnumerator; private readonly int ElementsPerSector; + private readonly byte[] sector; + private bool isDirty; public MiniFat(IOContext ioContext) { this.ioContext = ioContext; ElementsPerSector = ioContext.SectorSize / sizeof(uint); fatChainEnumerator = new(ioContext, ioContext.Header.FirstMiniFatSectorId); + sector = new byte[ioContext.SectorSize]; } - public void Dispose() => fatChainEnumerator.Dispose(); + public void Dispose() + { + Flush(); + + fatChainEnumerator.Dispose(); + } + + public void Flush() + { + if (isDirty) + { + CfbBinaryWriter writer = ioContext.Writer; + writer.Position = fatChainEnumerator.CurrentSector.Position; + writer.Write(sector); + isDirty = false; + } + } public IEnumerator GetEnumerator() => new MiniFatEnumerator(ioContext); @@ -35,33 +55,54 @@ public uint this[uint key] } set { - ThrowHelper.ThrowIfSectorIdIsInvalid(key); + if (!TrySetValue(key, value)) + throw new KeyNotFoundException($"Mini FAT index not found: {key}."); + } + } - uint fatSectorIndex = (uint)Math.DivRem(key, ElementsPerSector, out long elementIndex); - if (!fatChainEnumerator.MoveTo(fatSectorIndex)) - throw new KeyNotFoundException($"Mini FAT index not found: {fatSectorIndex}."); + bool TryMoveToSectorForKey(uint key, out long elementIndex) + { + uint fatChain = (uint)Math.DivRem(key, ElementsPerSector, out elementIndex); + if (fatChainEnumerator.IsAt(fatChain)) + return true; - CfbBinaryWriter writer = ioContext.Writer; - writer.Position = fatChainEnumerator.CurrentSector.Position + (elementIndex * sizeof(uint)); - writer.Write(value); - } + Flush(); + + bool ok = fatChainEnumerator.MoveTo(fatChain); + if (!ok) + return false; + + CfbBinaryReader reader = ioContext.Reader; + reader.Position = fatChainEnumerator.CurrentSector.Position; + reader.Read(sector); + return true; } public bool TryGetValue(uint key, out uint value) { ThrowHelper.ThrowIfSectorIdIsInvalid(key); - uint fatSectorIndex = (uint)Math.DivRem(key, ElementsPerSector, out long elementIndex); - bool ok = fatChainEnumerator.MoveTo(fatSectorIndex); - if (!ok) + if (!TryMoveToSectorForKey(key, out long elementIndex)) { value = uint.MaxValue; return false; } - CfbBinaryReader reader = ioContext.Reader; - reader.Position = fatChainEnumerator.CurrentSector.Position + (elementIndex * sizeof(uint)); - value = reader.ReadUInt32(); + Span slice = sector.AsSpan((int)elementIndex * sizeof(uint)); + value = BinaryPrimitives.ReadUInt32LittleEndian(slice); + return true; + } + + public bool TrySetValue(uint key, uint value) + { + ThrowHelper.ThrowIfSectorIdIsInvalid(key); + + if (!TryMoveToSectorForKey(key, out long elementIndex)) + return false; + + Span slice = sector.AsSpan((int)elementIndex * sizeof(uint)); + BinaryPrimitives.WriteUInt32LittleEndian(slice, value); + isDirty = true; return true; } From ea2d4bf5c79d5e919f29e1af9fde493e51d502d5 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 6 Nov 2024 13:25:08 +1300 Subject: [PATCH 062/134] Benchmark improvements --- OpenMcdf3.Perf/Program.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/OpenMcdf3.Perf/Program.cs b/OpenMcdf3.Perf/Program.cs index 2e356c9a..751cf9b1 100644 --- a/OpenMcdf3.Perf/Program.cs +++ b/OpenMcdf3.Perf/Program.cs @@ -7,29 +7,31 @@ internal sealed class Program static void Main(string[] args) { var stopwatch = Stopwatch.StartNew(); - Write(Version.V3, 4096, 1024 / 4, StorageModeFlags.Transacted); + Write(Version.V3, StorageModeFlags.Transacted, 1024 * 1024, 1024 * 1024, 100); Console.WriteLine($"Elapsed: {stopwatch.Elapsed}"); } - static void Write(Version version, int length, int iterations, StorageModeFlags storageModeFlags) + static void Write(Version version, StorageModeFlags storageModeFlags, int bufferLength, int streamLength, int iterations) { // Fill with bytes equal to their position modulo 256 - byte[] expectedBuffer = new byte[length]; - for (int i = 0; i < length; i++) + byte[] expectedBuffer = new byte[bufferLength]; + for (int i = 0; i < bufferLength; i++) expectedBuffer[i] = (byte)i; //byte[] actualBuffer = new byte[length]; - using MemoryStream memoryStream = new(2 * length * iterations); - using var rootStorage = RootStorage.Create(memoryStream, version, storageModeFlags); - using Stream stream = rootStorage.CreateStream("TestStream"); - + //using MemoryStream memoryStream = new(2 * length * iterations); + using FileStream baseStream = File.Create(Path.GetTempFileName()); for (int i = 0; i < iterations; i++) { - stream.Write(expectedBuffer, 0, expectedBuffer.Length); - } + using var rootStorage = RootStorage.Create(baseStream, version, storageModeFlags); + using Stream stream = rootStorage.CreateStream("TestStream"); - if (storageModeFlags.HasFlag(StorageModeFlags.Transacted)) - rootStorage.Commit(); + for (int j = 0; j < streamLength / bufferLength; j++) + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + + if (storageModeFlags.HasFlag(StorageModeFlags.Transacted)) + rootStorage.Commit(); + } } } From 340bd2b1a9c1826381f7c31a694739efbc26a1d2 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 6 Nov 2024 13:26:08 +1300 Subject: [PATCH 063/134] Improve dotnet 8 FatStream write --- OpenMcdf3/FatStream.cs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index ef711519..7923be07 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -161,14 +161,12 @@ public override void Write(byte[] buffer, int offset, int count) { ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); + this.ThrowIfDisposed(disposed); this.ThrowIfNotWritable(); if (count == 0) return; - //if (position + count > ChainCapacity) - // SetLength(position + count); - uint chainIndex = (uint)Math.DivRem(position, ioContext.SectorSize, out long sectorOffset); CfbBinaryWriter writer = ioContext.Writer; @@ -177,9 +175,7 @@ public override void Write(byte[] buffer, int offset, int count) for (; ; ) { if (!chain.MoveTo(chainIndex)) - { lastIndex = chain.ExtendFrom(lastIndex); - } Sector sector = chain.CurrentSector; writer.Position = sector.Position + sectorOffset; @@ -248,22 +244,22 @@ public override int Read(Span buffer) public override void Write(ReadOnlySpan buffer) { + this.ThrowIfDisposed(disposed); this.ThrowIfNotWritable(); if (buffer.Length == 0) return; - if (position + buffer.Length > ChainCapacity) - SetLength(position + buffer.Length); - uint chainIndex = (uint)Math.DivRem(position, ioContext.SectorSize, out long sectorOffset); - if (!chain.MoveTo(chainIndex)) - throw new InvalidOperationException($"Failed to move to FAT chain index: {chainIndex}"); CfbBinaryWriter writer = ioContext.Writer; int writeCount = 0; - do + uint lastIndex = 0; + for (; ; ) { + if (!chain.MoveTo(chainIndex)) + lastIndex = chain.ExtendFrom(lastIndex); + Sector sector = chain.CurrentSector; writer.Position = sector.Position + sectorOffset; int remaining = buffer.Length - writeCount; @@ -271,6 +267,7 @@ public override void Write(ReadOnlySpan buffer) long writeLength = Math.Min(remaining, sector.Length - sectorOffset); ReadOnlySpan slice = buffer.Slice(localOffset, (int)writeLength); writer.Write(slice); + ioContext.ExtendStreamLength(sector.EndPosition); position += writeLength; writeCount += (int)writeLength; if (position > Length) @@ -278,7 +275,9 @@ public override void Write(ReadOnlySpan buffer) sectorOffset = 0; if (writeCount >= buffer.Length) return; - } while (chain.MoveNext()); + + chainIndex++; + } throw new InvalidOperationException($"End of FAT chain was reached"); } From f2f6ac2110468e366242a03a249f4bbc78e9c01e Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 6 Nov 2024 14:56:30 +1300 Subject: [PATCH 064/134] Add transacted read-only test --- OpenMcdf3.Tests/StreamTests.cs | 53 ++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/OpenMcdf3.Tests/StreamTests.cs b/OpenMcdf3.Tests/StreamTests.cs index 058182ab..bbf9d460 100644 --- a/OpenMcdf3.Tests/StreamTests.cs +++ b/OpenMcdf3.Tests/StreamTests.cs @@ -397,6 +397,59 @@ public void ModifyCommit(Version version, int length) } } + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 63)] + [DataRow(Version.V3, 64)] // Mini-stream sector size + [DataRow(Version.V3, 65)] + [DataRow(Version.V3, 511)] + [DataRow(Version.V3, 512)] // Multiple stream sectors + [DataRow(Version.V3, 513)] + [DataRow(Version.V3, 4095)] + [DataRow(Version.V3, 4096)] + [DataRow(Version.V3, 4097)] + [DataRow(Version.V3, 128 * 512)] // Multiple FAT sectors + [DataRow(Version.V3, 1024 * 4096)] // Multiple FAT sectors + [DataRow(Version.V3, 7087616)] // First DIFAT chain + [DataRow(Version.V3, 2 * 7087616)] // Long DIFAT chain + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 63)] + [DataRow(Version.V4, 64)] // Mini-stream sector size + [DataRow(Version.V4, 65)] + [DataRow(Version.V4, 511)] + [DataRow(Version.V4, 512)] + [DataRow(Version.V4, 513)] + [DataRow(Version.V4, 4095)] + [DataRow(Version.V4, 4096)] // Multiple stream sectors + [DataRow(Version.V4, 4097)] + [DataRow(Version.V4, 1024 * 4096)] // Multiple FAT sectors (1024 * 4096) + [DataRow(Version.V4, 7087616 * 4)] // First DIFAT chain + [DataRow(Version.V4, 2 * 7087616 * 4)] // Long DIFAT chain + public void TransactedRead(Version version, int length) + { + // Fill with bytes equal to their position modulo 256 + byte[] expectedBuffer = new byte[length]; + for (int i = 0; i < length; i++) + expectedBuffer[i] = (byte)i; + + using MemoryStream memoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + using CfbStream stream = rootStorage.CreateStream("TestStream1"); + Assert.AreEqual(0, stream.Length); + + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + } + + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.Transacted)) + { + using CfbStream stream = rootStorage.OpenStream("TestStream1"); + byte[] actualBuffer = new byte[length]; + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + } + [TestMethod] [DataRow(Version.V3, 0)] [DataRow(Version.V3, 63)] From 375f1a0aa980ba1823f97cf9b68546f67e7ed55a Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 6 Nov 2024 14:56:52 +1300 Subject: [PATCH 065/134] Only write directories when dirty --- OpenMcdf3/FatChainEnumerator.cs | 2 ++ OpenMcdf3/FatStream.cs | 12 ++++++++++-- OpenMcdf3/MiniFatStream.cs | 11 ++++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/OpenMcdf3/FatChainEnumerator.cs b/OpenMcdf3/FatChainEnumerator.cs index 18c89890..f97128a3 100644 --- a/OpenMcdf3/FatChainEnumerator.cs +++ b/OpenMcdf3/FatChainEnumerator.cs @@ -237,4 +237,6 @@ public void Reset(uint startSectorId) index = uint.MaxValue; current = FatChainEntry.Invalid; } + + public override string ToString() => $"{current}"; } diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index 7923be07..06f6aefe 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -10,6 +10,7 @@ internal class FatStream : Stream readonly IOContext ioContext; readonly FatChainEnumerator chain; long position; + bool isDirty; bool disposed; internal FatStream(IOContext ioContext, DirectoryEntry directoryEntry) @@ -62,11 +63,14 @@ public override void Flush() { this.ThrowIfDisposed(disposed); - if (CanWrite) + if (isDirty) { ioContext.Write(DirectoryEntry); - ioContext.Writer!.Flush(); + isDirty = false; } + + if (CanWrite) + ioContext.Writer!.Flush(); } /// @@ -154,6 +158,7 @@ public override void SetLength(long value) DirectoryEntry.StartSectorId = chain.StartId; DirectoryEntry.StreamLength = value; + isDirty = true; } /// @@ -187,7 +192,10 @@ public override void Write(byte[] buffer, int offset, int count) position += writeLength; writeCount += (int)writeLength; if (position > Length) + { DirectoryEntry.StreamLength = position; + isDirty = true; + } sectorOffset = 0; if (writeCount >= count) return; diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index c7c00984..bddf78e6 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -9,6 +9,7 @@ internal sealed class MiniFatStream : Stream readonly MiniFatChainEnumerator miniChain; long position; bool disposed; + bool isDirty; internal MiniFatStream(IOContext ioContext, DirectoryEntry directoryEntry) { @@ -52,8 +53,12 @@ public override void Flush() { this.ThrowIfDisposed(disposed); - if (CanWrite) + if (isDirty) + { ioContext.Write(DirectoryEntry); + isDirty = false; + } + ioContext.MiniStream.Flush(); } @@ -144,6 +149,7 @@ public override void SetLength(long value) DirectoryEntry.StartSectorId = miniChain.StartId; DirectoryEntry.StreamLength = value; + isDirty = true; } public override void Write(byte[] buffer, int offset, int count) @@ -177,7 +183,10 @@ public override void Write(byte[] buffer, int offset, int count) position += writeLength; writeCount += (int)writeLength; if (position > Length) + { DirectoryEntry.StreamLength = position; + isDirty = true; + } sectorOffset = 0; if (writeCount >= count) return; From 07c6b1476043dcb30820887e8b4886102972a65e Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 6 Nov 2024 15:45:46 +1300 Subject: [PATCH 066/134] Buffer transacted streams --- OpenMcdf3.Tests/OpenMcdf3.Tests.csproj | 2 +- OpenMcdf3/IOContext.cs | 17 ++++++----- OpenMcdf3/OpenMcdf3.csproj | 2 +- OpenMcdf3/TransactedStream.cs | 40 +++++++++++++++----------- 4 files changed, 36 insertions(+), 25 deletions(-) diff --git a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj index 1a72e048..68952b93 100644 --- a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj +++ b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj @@ -1,7 +1,7 @@ - net48;net8.0 + net8.0 Exe 11.0 enable diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index f45d63cb..15fa1cfc 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -16,6 +16,7 @@ internal sealed class IOContext : IDisposable readonly Stream stream; readonly DirectoryEntryEnumerator directoryEnumerator; readonly CfbBinaryWriter? writer; + TransactedStream? transactedStream; MiniFat? miniFat; FatStream? miniStream; @@ -82,16 +83,17 @@ public IOContext(Stream stream, Version version, IOContextFlags contextFlags = I MiniSectorSize = 1 << Header.MiniSectorShift; Length = stream.Length; - Stream transactedStream = stream; + Stream actualStream = stream; if (contextFlags.HasFlag(IOContextFlags.Transacted)) { Stream overlayStream = stream is MemoryStream ? new MemoryStream() : File.Create(Path.GetTempFileName()); transactedStream = new TransactedStream(this, stream, overlayStream); + actualStream = new BufferedStream(transactedStream, SectorSize); } - Reader = new(transactedStream); + Reader = new(actualStream); if (stream.CanWrite) - writer = new(transactedStream); + writer = new(actualStream); Fat = new(this); directoryEnumerator = new(this); @@ -116,10 +118,10 @@ public void Dispose() { if (!IsDisposed) { - if (writer is not null && writer.BaseStream is not TransactedStream) + if (writer is not null && transactedStream is null) { // Ensure the stream is as long as expected - writer.BaseStream.SetLength(Length); + stream.SetLength(Length); WriteHeader(); } @@ -153,19 +155,20 @@ public void Write(DirectoryEntry entry) public void Commit() { - if (writer is null || writer.BaseStream is not TransactedStream transactedStream) + if (writer is null || transactedStream is null) throw new InvalidOperationException("Cannot commit non-transacted storage."); miniStream?.Flush(); miniFat?.Flush(); Fat.Flush(); WriteHeader(); + writer.BaseStream.Flush(); transactedStream.Commit(); } public void Revert() { - if (writer is null || writer.BaseStream is not TransactedStream transactedStream) + if (writer is null || transactedStream is null) throw new InvalidOperationException("Cannot commit non-transacted storage."); transactedStream.Revert(); diff --git a/OpenMcdf3/OpenMcdf3.csproj b/OpenMcdf3/OpenMcdf3.csproj index 48b852fb..be7314e4 100644 --- a/OpenMcdf3/OpenMcdf3.csproj +++ b/OpenMcdf3/OpenMcdf3.csproj @@ -1,7 +1,7 @@  - netstandard2.0;net8.0 + net8.0 11.0 enable enable diff --git a/OpenMcdf3/TransactedStream.cs b/OpenMcdf3/TransactedStream.cs index 2736e250..2c055837 100644 --- a/OpenMcdf3/TransactedStream.cs +++ b/OpenMcdf3/TransactedStream.cs @@ -42,24 +42,32 @@ public override int Read(byte[] buffer, int offset, int count) { ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); - uint sectorId = (uint)Math.DivRem(originalStream.Position, ioContext.SectorSize, out long sectorOffset); - int remainingFromSector = ioContext.SectorSize - (int)sectorOffset; - int localCount = Math.Min(count, remainingFromSector); - Debug.Assert(localCount == count); - int read; - if (dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) - { - overlayStream.Position = overlayPosition + sectorOffset; - read = overlayStream.Read(buffer, offset, localCount); - originalStream.Seek(read, SeekOrigin.Current); - } - else + int totalRead = 0; + do { - read = originalStream.Read(buffer, offset, localCount); - } - - return read; + uint sectorId = (uint)Math.DivRem(originalStream.Position, ioContext.SectorSize, out long sectorOffset); + int remainingFromSector = ioContext.SectorSize - (int)sectorOffset; + int localCount = Math.Min(count - totalRead, remainingFromSector); + + if (dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) + { + overlayStream.Position = overlayPosition + sectorOffset; + read = overlayStream.Read(buffer, offset + totalRead, localCount); + originalStream.Seek(read, SeekOrigin.Current); + } + else + { + read = originalStream.Read(buffer, offset + totalRead, localCount); + } + + if (read == 0) + break; + + totalRead += read; + } while (totalRead < count); + + return totalRead; } public override long Seek(long offset, SeekOrigin origin) => originalStream.Seek(offset, origin); From a265ef1f9c004cb1e094a89597191c5df6bf4e38 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 6 Nov 2024 16:25:34 +1300 Subject: [PATCH 067/134] Fix building for netstandard2.0 --- OpenMcdf3.Tests/OpenMcdf3.Tests.csproj | 2 +- OpenMcdf3/CfbBinaryReader.cs | 22 +++++++++++++++++++++- OpenMcdf3/OpenMcdf3.csproj | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj index 68952b93..1a72e048 100644 --- a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj +++ b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net48;net8.0 Exe 11.0 enable diff --git a/OpenMcdf3/CfbBinaryReader.cs b/OpenMcdf3/CfbBinaryReader.cs index fac360f1..c9c6b0f6 100644 --- a/OpenMcdf3/CfbBinaryReader.cs +++ b/OpenMcdf3/CfbBinaryReader.cs @@ -1,4 +1,5 @@ -using System.Text; +using System.Buffers; +using System.Text; namespace OpenMcdf3; @@ -21,6 +22,25 @@ public long Position set => BaseStream.Position = value; } +#if NETSTANDARD2_0 + + public int Read(Span buffer) + { + byte[] array = ArrayPool.Shared.Rent(buffer.Length); + try + { + int bytesRead = BaseStream.Read(array, 0, buffer.Length); + array.AsSpan(0, bytesRead).CopyTo(buffer); + return bytesRead; + } + finally + { + ArrayPool.Shared.Return(array); + } + } + +#endif + public Guid ReadGuid() { int bytesRead = 0; diff --git a/OpenMcdf3/OpenMcdf3.csproj b/OpenMcdf3/OpenMcdf3.csproj index be7314e4..48b852fb 100644 --- a/OpenMcdf3/OpenMcdf3.csproj +++ b/OpenMcdf3/OpenMcdf3.csproj @@ -1,7 +1,7 @@  - net8.0 + netstandard2.0;net8.0 11.0 enable enable From 858eb99124aa64d52dd066a39618f4eb14c92053 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 6 Nov 2024 17:31:20 +1300 Subject: [PATCH 068/134] Delete directory entries --- OpenMcdf3.Tests/StorageTests.cs | 145 +++++++++++++++++++++++++++ OpenMcdf3/DirectoryEntry.cs | 2 + OpenMcdf3/DirectoryTreeEnumerator.cs | 47 ++++++++- OpenMcdf3/Storage.cs | 54 ++++++++-- 4 files changed, 237 insertions(+), 11 deletions(-) diff --git a/OpenMcdf3.Tests/StorageTests.cs b/OpenMcdf3.Tests/StorageTests.cs index a4c1ea81..003a4b87 100644 --- a/OpenMcdf3.Tests/StorageTests.cs +++ b/OpenMcdf3.Tests/StorageTests.cs @@ -51,4 +51,149 @@ public void CreateDuplicateStorageThrowsException(Version version) rootStorage.CreateStorage("Test"); Assert.ThrowsException(() => rootStorage.CreateStorage("Test")); } + + [TestMethod] + [DataRow(Version.V3)] + [DataRow(Version.V4)] + public void DeleteSingleStorage(Version version) + { + using MemoryStream memoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + rootStorage.CreateStorage("Test"); + Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + rootStorage.Delete("Test"); + Assert.AreEqual(0, rootStorage.EnumerateEntries().Count()); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + Assert.AreEqual(0, rootStorage.EnumerateEntries().Count()); + } + } + + [TestMethod] + [DataRow(Version.V3)] + [DataRow(Version.V4)] + public void DeleteRedBlackTreeChildLeaf(Version version) + { + using MemoryStream memoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + rootStorage.CreateStorage("Test1"); + rootStorage.CreateStorage("Test2"); + Assert.AreEqual(2, rootStorage.EnumerateEntries().Count()); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + rootStorage.Delete("Test1"); + Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); + } + } + + [TestMethod] + [DataRow(Version.V3)] + [DataRow(Version.V4)] + public void DeleteRedBlackTreeSiblingLeaf(Version version) + { + using MemoryStream memoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + rootStorage.CreateStorage("Test1"); + rootStorage.CreateStorage("Test2"); + Assert.AreEqual(2, rootStorage.EnumerateEntries().Count()); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + rootStorage.Delete("Test2"); + Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); + } + } + + [TestMethod] + [DataRow(Version.V3)] + [DataRow(Version.V4)] + public void DeleteRedBlackTreeSibling(Version version) + { + using MemoryStream memoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + rootStorage.CreateStorage("Test1"); + rootStorage.CreateStorage("Test2"); + rootStorage.CreateStorage("Test3"); + Assert.AreEqual(3, rootStorage.EnumerateEntries().Count()); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + rootStorage.Delete("Test2"); + Assert.AreEqual(2, rootStorage.EnumerateEntries().Count()); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + Assert.AreEqual(2, rootStorage.EnumerateEntries().Count()); + } + } + + [TestMethod] + [DataRow(Version.V3)] + [DataRow(Version.V4)] + public void DeleteStorageRecursively(Version version) + { + using MemoryStream memoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + Storage storage = rootStorage.CreateStorage("Test"); + Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); + + using CfbStream stream = storage.CreateStream("Test"); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + rootStorage.Delete("Test"); + Assert.AreEqual(0, rootStorage.EnumerateEntries().Count()); + } + + using (var rootStorage = RootStorage.Open(memoryStream)) + { + Assert.AreEqual(0, rootStorage.EnumerateEntries().Count()); + } + } + + [TestMethod] + [DataRow(Version.V3)] + [DataRow(Version.V4)] + public void DeleteStream(Version version) + { + using MemoryStream memoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + rootStorage.CreateStream("Test"); + Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); + } + + using (var rootStorage = RootStorage.Create(memoryStream, version)) + { + rootStorage.Delete("Test"); + Assert.AreEqual(0, rootStorage.EnumerateEntries().Count()); + } + } } diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index 4a423d25..bcd6df4d 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -151,6 +151,8 @@ public bool Equals(DirectoryEntry? other) public void RecycleRoot() => Recycle(StorageType.Root, "Root Entry"); + public void Recycle() => Recycle(StorageType.Unallocated, string.Empty); + public void Recycle(StorageType storageType, string name) { Type = storageType; diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index c58262ec..8623589f 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -12,6 +12,7 @@ internal sealed class DirectoryTreeEnumerator : IEnumerator private DirectoryEntry? child; private readonly Stack stack = new(); private readonly DirectoryEntryEnumerator directoryEntryEnumerator; + DirectoryEntry parent; DirectoryEntry? current; internal DirectoryTreeEnumerator(IOContext ioContext, DirectoryEntry root) @@ -20,6 +21,7 @@ internal DirectoryTreeEnumerator(IOContext ioContext, DirectoryEntry root) this.root = root; if (root.ChildId != StreamId.NoStream) child = directoryEntryEnumerator.GetDictionaryEntry(root.ChildId); + parent = root; PushLeft(child); } @@ -49,10 +51,12 @@ public bool MoveNext() if (stack.Count == 0) { current = null; + parent = root; return false; } current = stack.Pop(); + parent = stack.Count == 0 ? root : stack.Peek(); if (current.RightSiblingId != StreamId.NoStream) { DirectoryEntry rightSibling = directoryEntryEnumerator.GetDictionaryEntry(current.RightSiblingId); @@ -66,6 +70,7 @@ public bool MoveNext() public void Reset() { current = null; + parent = root; stack.Clear(); PushLeft(child); } @@ -79,29 +84,29 @@ private void PushLeft(DirectoryEntry? node) } } - public bool MoveTo(StorageType type, string name) + public bool MoveTo(string name) { Reset(); while (MoveNext()) { - if (Current.Type == type && Current.Name == name) + if (Current.Name == name) return true; } return false; } - public DirectoryEntry? TryGetDirectoryEntry(StorageType type, string name) + public DirectoryEntry? TryGetDirectoryEntry(string name) { - if (MoveTo(type, name)) + if (MoveTo(name)) return Current; return null; } public DirectoryEntry Add(StorageType storageType, string name) { - if (MoveTo(storageType, name)) + if (MoveTo(name)) throw new IOException($"{storageType} \"{name}\" already exists."); DirectoryEntry entry = directoryEntryEnumerator.CreateOrRecycleDirectoryEntry(); @@ -116,6 +121,7 @@ void Add(DirectoryEntry entry) { Reset(); + // TODO: Implement balancing (all-black for now) entry.Color = NodeColor.Black; directoryEntryEnumerator.Write(entry); @@ -136,4 +142,35 @@ void Add(DirectoryEntry entry) directoryEntryEnumerator.Write(node); } } + + public void Remove(DirectoryEntry entry) + { + if (child is null) + throw new KeyNotFoundException("DirectoryEntry has no children"); + + if (root.ChildId == entry.Id) + { + root.ChildId = entry.LeftSiblingId; + directoryEntryEnumerator.Write(root); + if (root.ChildId == StreamId.NoStream) + child = null; + return; + } + + Reset(); + + while (MoveNext()) + { + if (current!.Id == entry.Id) + { + if (parent.LeftSiblingId == entry.Id) + parent.LeftSiblingId = entry.LeftSiblingId; + directoryEntryEnumerator.Write(parent); + + entry.Recycle(); + directoryEntryEnumerator.Write(entry); + break; + } + } + } } diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index b3b3a347..e358c51b 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -11,6 +11,9 @@ public class Storage internal Storage(IOContext ioContext, DirectoryEntry directoryEntry) { + if (directoryEntry.Type is not StorageType.Storage and not StorageType.Root) + throw new ArgumentException("DirectoryEntry must be a Storage or Root.", nameof(directoryEntry)); + this.ioContext = ioContext; DirectoryEntry = directoryEntry; } @@ -43,10 +46,10 @@ IEnumerable EnumerateDirectoryEntries() IEnumerable EnumerateDirectoryEntries(StorageType type) => EnumerateDirectoryEntries() .Where(e => e.Type == type); - DirectoryEntry? TryGetDirectoryEntry(StorageType storageType, string name) + DirectoryEntry? TryGetDirectoryEntry(string name) { using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext, DirectoryEntry); - return directoryTreeEnumerator.TryGetDirectoryEntry(storageType, name); + return directoryTreeEnumerator.TryGetDirectoryEntry(name); } DirectoryEntry AddDirectoryEntry(StorageType storageType, string name) @@ -82,8 +85,9 @@ public Storage OpenStorage(string name) this.ThrowIfDisposed(ioContext.IsDisposed); - DirectoryEntry entry = TryGetDirectoryEntry(StorageType.Storage, name) - ?? throw new DirectoryNotFoundException($"Storage not found: {name}."); + DirectoryEntry? entry = TryGetDirectoryEntry(name); + if (entry is null || entry.Type is not StorageType.Storage) + throw new DirectoryNotFoundException($"Storage not found: {name}."); return new Storage(ioContext, entry); } @@ -93,10 +97,48 @@ public CfbStream OpenStream(string name) this.ThrowIfDisposed(ioContext.IsDisposed); - DirectoryEntry? entry = TryGetDirectoryEntry(StorageType.Stream, name) - ?? throw new FileNotFoundException($"Stream not found: {name}.", name); + DirectoryEntry? entry = TryGetDirectoryEntry(name); + if (entry is null || entry.Type is not StorageType.Stream) + throw new FileNotFoundException($"Stream not found: {name}.", name); // TODO: Return a Stream that can transition between FAT and mini FAT return new CfbStream(ioContext, entry); } + + public void Delete(string name) + { + ThrowHelper.ThrowIfNameIsInvalid(name); + + this.ThrowIfDisposed(ioContext.IsDisposed); + + using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext, DirectoryEntry); + DirectoryEntry? entry = directoryTreeEnumerator.TryGetDirectoryEntry(name); + if (entry is null) + return; + + if (entry.Type is StorageType.Storage && entry.ChildId is not StreamId.NoStream) + { + Storage storage = new(ioContext, entry); + foreach (EntryInfo childEntry in storage.EnumerateEntries()) + { + storage.Delete(childEntry.Name); + }; + } + + if (entry.Type is StorageType.Stream && entry.StartSectorId is not StreamId.NoStream) + { + if (entry.StreamLength < Header.MiniStreamCutoffSize) + { + using MiniFatChainEnumerator miniFatChainEnumerator = new(ioContext, entry.StartSectorId); + miniFatChainEnumerator.Shrink(0); + } + else + { + using FatChainEnumerator fatChainEnumerator = new(ioContext, entry.StartSectorId); + fatChainEnumerator.Shrink(0); + } + } + + directoryTreeEnumerator.Remove(entry); + } } From 0ba7e811dbd8408d8b49d9ccf0068302428b454c Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 7 Nov 2024 09:56:03 +1300 Subject: [PATCH 069/134] Allow tracing directory entries --- OpenMcdf3.Tests/DebugWriter.cs | 2 ++ OpenMcdf3/DirectoryEntry.cs | 7 +++++++ OpenMcdf3/DirectoryTreeEnumerator.cs | 12 ++++++++++++ OpenMcdf3/Storage.cs | 6 ++++++ 4 files changed, 27 insertions(+) diff --git a/OpenMcdf3.Tests/DebugWriter.cs b/OpenMcdf3.Tests/DebugWriter.cs index f795da38..c1b66277 100644 --- a/OpenMcdf3.Tests/DebugWriter.cs +++ b/OpenMcdf3.Tests/DebugWriter.cs @@ -5,6 +5,8 @@ namespace OpenMcdf3.Tests; internal sealed class DebugWriter : TextWriter { + public static DebugWriter Default { get; } = new(); + public override Encoding Encoding => Encoding.Unicode; public override void Write(char value) => Debug.Write(value); diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index bcd6df4d..f08cf2ce 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -130,6 +130,13 @@ public DateTime ModifiedTime /// public long StreamLength { get; set; } + internal char ColorChar => Color switch + { + NodeColor.Red => 'R', + NodeColor.Black => 'B', + _ => '?' + }; + public override bool Equals(object? obj) => Equals(obj as DirectoryEntry); public bool Equals(DirectoryEntry? other) diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index 8623589f..0101429c 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -173,4 +173,16 @@ public void Remove(DirectoryEntry entry) } } } + + internal void PrintTrace(TextWriter writer) + { + Reset(); + + while (MoveNext()) + { + for (int i = 0; i < stack.Count; i++) + writer.Write(" "); + writer.WriteLine($"{Current.ColorChar} {Current}"); + } + } } diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index e358c51b..8c808a6d 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -141,4 +141,10 @@ public void Delete(string name) directoryTreeEnumerator.Remove(entry); } + + internal void TraceDirectoryEntries(TextWriter writer) + { + using DirectoryTreeEnumerator treeEnumerator = new(ioContext, DirectoryEntry); + treeEnumerator.PrintTrace(writer); + } } From ba9e15ccbae874a9ef57b9457cd2c9d8b26cbb25 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 7 Nov 2024 10:59:16 +1300 Subject: [PATCH 070/134] Add directories class --- OpenMcdf3/Directories.cs | 95 +++++++++++++++++++++++++++ OpenMcdf3/DirectoryEntryEnumerator.cs | 88 ++----------------------- OpenMcdf3/DirectoryTreeEnumerator.cs | 29 ++++---- OpenMcdf3/FatStream.cs | 2 +- OpenMcdf3/IOContext.cs | 22 +++---- OpenMcdf3/MiniFatStream.cs | 2 +- OpenMcdf3/Storage.cs | 10 +-- 7 files changed, 131 insertions(+), 117 deletions(-) create mode 100644 OpenMcdf3/Directories.cs diff --git a/OpenMcdf3/Directories.cs b/OpenMcdf3/Directories.cs new file mode 100644 index 00000000..186043ab --- /dev/null +++ b/OpenMcdf3/Directories.cs @@ -0,0 +1,95 @@ +namespace OpenMcdf3; + +internal sealed class Directories : IDisposable +{ + private readonly IOContext ioContext; + private readonly FatChainEnumerator fatChainEnumerator; + private readonly DirectoryEntryEnumerator directoryEntryEnumerator; + private readonly int entriesPerSector; + + public Directories(IOContext ioContext) + { + this.ioContext = ioContext; + fatChainEnumerator = new FatChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); + directoryEntryEnumerator = new DirectoryEntryEnumerator(this); + entriesPerSector = ioContext.SectorSize / DirectoryEntry.Length; + } + + public void Dispose() + { + fatChainEnumerator.Dispose(); + } + + /// + /// Gets the for the specified stream ID. + /// + public DirectoryEntry GetDictionaryEntry(uint streamId) + { + if (!TryGetDictionaryEntry(streamId, out DirectoryEntry? entry)) + throw new KeyNotFoundException($"Directory entry {streamId} was not found."); + return entry!; + } + + public bool TryGetDictionaryEntry(uint streamId, out DirectoryEntry? entry) + { + if (streamId > StreamId.Maximum) + throw new ArgumentException($"Invalid directory entry stream ID: ${streamId:X8}.", nameof(streamId)); + + uint chainIndex = (uint)Math.DivRem(streamId, entriesPerSector, out long entryIndex); + if (!fatChainEnumerator.MoveTo(chainIndex)) + { + entry = null; + return false; + } + + ioContext.Reader.Position = fatChainEnumerator.CurrentSector.Position + (entryIndex * DirectoryEntry.Length); + entry = ioContext.Reader.ReadDirectoryEntry(ioContext.Version, streamId); + return true; + } + + public DirectoryEntry CreateOrRecycleDirectoryEntry() + { + DirectoryEntry? entry = TryRecycleDirectoryEntry(); + if (entry is not null) + return entry; + + CfbBinaryWriter writer = ioContext.Writer; + uint id = fatChainEnumerator.Extend(); + if (ioContext.Header.FirstDirectorySectorId == SectorType.EndOfChain) + ioContext.Header.FirstDirectorySectorId = id; + + Sector sector = new(id, ioContext.SectorSize); + writer.Position = sector.Position; + for (int i = 0; i < entriesPerSector; i++) + writer.Write(DirectoryEntry.Unallocated); + + entry = TryRecycleDirectoryEntry() + ?? throw new InvalidOperationException("Failed to add or recycle directory entry."); + return entry; + } + + private DirectoryEntry? TryRecycleDirectoryEntry() + { + directoryEntryEnumerator.Reset(); + + while (directoryEntryEnumerator.MoveNext()) + { + DirectoryEntry current = directoryEntryEnumerator.Current; + if (directoryEntryEnumerator.Current.Type is StorageType.Unallocated) + return current; + } + + return null; + } + + public void Write(DirectoryEntry entry) + { + uint chainIndex = (uint)Math.DivRem(entry.Id, entriesPerSector, out long entryIndex); + if (!fatChainEnumerator.MoveTo(chainIndex)) + throw new KeyNotFoundException($"Directory entry {entry.Id} was not found."); + + CfbBinaryWriter writer = ioContext.Writer; + writer.Position = fatChainEnumerator.CurrentSector.Position + (entryIndex * DirectoryEntry.Length); + writer.Write(entry); + } +} diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index 39f858a4..05880a6e 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -8,26 +8,21 @@ namespace OpenMcdf3; /// internal sealed class DirectoryEntryEnumerator : IEnumerator { - private readonly IOContext ioContext; - private readonly FatChainEnumerator fatChainEnumerator; + private readonly Directories directories; private bool start = true; private uint index = uint.MaxValue; private DirectoryEntry? current; - public DirectoryEntryEnumerator(IOContext ioContext) + public DirectoryEntryEnumerator(Directories directories) { - this.ioContext = ioContext; - fatChainEnumerator = new FatChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); + this.directories = directories; } /// public void Dispose() { - fatChainEnumerator.Dispose(); } - private int EntriesPerSector => ioContext.SectorSize / DirectoryEntry.Length; - /// public DirectoryEntry Current { @@ -48,92 +43,23 @@ public bool MoveNext() if (start) { start = false; - index = 0; + index = uint.MaxValue; } - uint chainIndex = (uint)Math.DivRem(index, EntriesPerSector, out long entryIndex); - if (!fatChainEnumerator.MoveTo(chainIndex)) + uint nextIndex = index + 1; + if (!directories.TryGetDictionaryEntry(nextIndex, out current)) { - current = null; index = uint.MaxValue; return false; } - ioContext.Reader.Position = fatChainEnumerator.CurrentSector.Position + (entryIndex * DirectoryEntry.Length); - current = ioContext.Reader.ReadDirectoryEntry(ioContext.Version, index); - index++; + index = nextIndex; return true; } - public DirectoryEntry CreateOrRecycleDirectoryEntry() - { - DirectoryEntry? entry = TryRecycleDirectoryEntry(); - if (entry is not null) - return entry; - - CfbBinaryWriter writer = ioContext.Writer; - uint id = fatChainEnumerator.Extend(); - if (ioContext.Header.FirstDirectorySectorId == SectorType.EndOfChain) - ioContext.Header.FirstDirectorySectorId = id; - - Sector sector = new(id, ioContext.SectorSize); - writer.Position = sector.Position; - int directoryEntriesPerSector = EntriesPerSector; - for (int i = 0; i < directoryEntriesPerSector; i++) - writer.Write(DirectoryEntry.Unallocated); - - entry = TryRecycleDirectoryEntry() - ?? throw new InvalidOperationException("Failed to add or recycle directory entry."); - return entry; - } - - private DirectoryEntry? TryRecycleDirectoryEntry() - { - Reset(); - - while (MoveNext()) - { - if (current!.Type == StorageType.Unallocated) - { - return current; - } - } - - return null; - } - - /// - /// Gets the for the specified stream ID. - /// - public DirectoryEntry GetDictionaryEntry(uint streamId) - { - if (streamId > StreamId.Maximum) - throw new ArgumentException($"Invalid directory entry stream ID: ${streamId:X8}.", nameof(streamId)); - - uint chainIndex = (uint)Math.DivRem(streamId, EntriesPerSector, out long entryIndex); - if (!fatChainEnumerator.MoveTo(chainIndex)) - throw new KeyNotFoundException($"Directory entry {streamId} was not found."); - - ioContext.Reader.Position = fatChainEnumerator.CurrentSector.Position + (entryIndex * DirectoryEntry.Length); - current = ioContext.Reader.ReadDirectoryEntry(ioContext.Version, streamId); - return current; - } - - public void Write(DirectoryEntry entry) - { - uint chainIndex = (uint)Math.DivRem(entry.Id, EntriesPerSector, out long entryIndex); - if (!fatChainEnumerator.MoveTo(chainIndex)) - throw new KeyNotFoundException($"Directory entry {entry.Id} was not found."); - - CfbBinaryWriter writer = ioContext.Writer; - writer.Position = fatChainEnumerator.CurrentSector.Position + (entryIndex * DirectoryEntry.Length); - writer.Write(entry); - } - /// public void Reset() { - fatChainEnumerator.Reset(); start = true; current = null; index = uint.MaxValue; diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index 0101429c..8c840901 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -8,19 +8,19 @@ namespace OpenMcdf3; /// internal sealed class DirectoryTreeEnumerator : IEnumerator { + private readonly Directories directories; private readonly DirectoryEntry root; private DirectoryEntry? child; private readonly Stack stack = new(); - private readonly DirectoryEntryEnumerator directoryEntryEnumerator; DirectoryEntry parent; DirectoryEntry? current; - internal DirectoryTreeEnumerator(IOContext ioContext, DirectoryEntry root) + internal DirectoryTreeEnumerator(Directories directories, DirectoryEntry root) { - directoryEntryEnumerator = new(ioContext); + this.directories = directories; this.root = root; if (root.ChildId != StreamId.NoStream) - child = directoryEntryEnumerator.GetDictionaryEntry(root.ChildId); + child = directories.GetDictionaryEntry(root.ChildId); parent = root; PushLeft(child); } @@ -28,7 +28,6 @@ internal DirectoryTreeEnumerator(IOContext ioContext, DirectoryEntry root) /// public void Dispose() { - directoryEntryEnumerator.Dispose(); } /// @@ -59,7 +58,7 @@ public bool MoveNext() parent = stack.Count == 0 ? root : stack.Peek(); if (current.RightSiblingId != StreamId.NoStream) { - DirectoryEntry rightSibling = directoryEntryEnumerator.GetDictionaryEntry(current.RightSiblingId); + DirectoryEntry rightSibling = directories.GetDictionaryEntry(current.RightSiblingId); PushLeft(rightSibling); } @@ -80,7 +79,7 @@ private void PushLeft(DirectoryEntry? node) while (node is not null) { stack.Push(node); - node = node.LeftSiblingId == StreamId.NoStream ? null : directoryEntryEnumerator.GetDictionaryEntry(node.LeftSiblingId); + node = node.LeftSiblingId == StreamId.NoStream ? null : directories.GetDictionaryEntry(node.LeftSiblingId); } } @@ -109,7 +108,7 @@ public DirectoryEntry Add(StorageType storageType, string name) if (MoveTo(name)) throw new IOException($"{storageType} \"{name}\" already exists."); - DirectoryEntry entry = directoryEntryEnumerator.CreateOrRecycleDirectoryEntry(); + DirectoryEntry entry = directories.CreateOrRecycleDirectoryEntry(); entry.Recycle(storageType, name); Add(entry); @@ -123,13 +122,13 @@ void Add(DirectoryEntry entry) // TODO: Implement balancing (all-black for now) entry.Color = NodeColor.Black; - directoryEntryEnumerator.Write(entry); + directories.Write(entry); if (root.ChildId == StreamId.NoStream) { Debug.Assert(child is null); root.ChildId = entry.Id; - directoryEntryEnumerator.Write(root); + directories.Write(root); child = entry; } else @@ -137,9 +136,9 @@ void Add(DirectoryEntry entry) Debug.Assert(child is not null); DirectoryEntry node = child!; while (node.LeftSiblingId != StreamId.NoStream) - node = directoryEntryEnumerator.GetDictionaryEntry(node.LeftSiblingId); + node = directories.GetDictionaryEntry(node.LeftSiblingId); node.LeftSiblingId = entry.Id; - directoryEntryEnumerator.Write(node); + directories.Write(node); } } @@ -151,7 +150,7 @@ public void Remove(DirectoryEntry entry) if (root.ChildId == entry.Id) { root.ChildId = entry.LeftSiblingId; - directoryEntryEnumerator.Write(root); + directories.Write(root); if (root.ChildId == StreamId.NoStream) child = null; return; @@ -165,10 +164,10 @@ public void Remove(DirectoryEntry entry) { if (parent.LeftSiblingId == entry.Id) parent.LeftSiblingId = entry.LeftSiblingId; - directoryEntryEnumerator.Write(parent); + directories.Write(parent); entry.Recycle(); - directoryEntryEnumerator.Write(entry); + directories.Write(entry); break; } } diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index 06f6aefe..05fe2591 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -65,7 +65,7 @@ public override void Flush() if (isDirty) { - ioContext.Write(DirectoryEntry); + ioContext.Directories.Write(DirectoryEntry); isDirty = false; } diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index 15fa1cfc..3c13816b 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -14,9 +14,8 @@ enum IOContextFlags internal sealed class IOContext : IDisposable { readonly Stream stream; - readonly DirectoryEntryEnumerator directoryEnumerator; readonly CfbBinaryWriter? writer; - TransactedStream? transactedStream; + readonly TransactedStream? transactedStream; MiniFat? miniFat; FatStream? miniStream; @@ -36,6 +35,8 @@ public CfbBinaryWriter Writer public Fat Fat { get; } + public Directories Directories { get; } + public DirectoryEntry RootEntry { get; } public MiniFat MiniFat @@ -96,21 +97,19 @@ public IOContext(Stream stream, Version version, IOContextFlags contextFlags = I writer = new(actualStream); Fat = new(this); - directoryEnumerator = new(this); + Directories = new(this); if (contextFlags.HasFlag(IOContextFlags.Create)) { - RootEntry = directoryEnumerator.CreateOrRecycleDirectoryEntry(); + RootEntry = Directories.CreateOrRecycleDirectoryEntry(); RootEntry.RecycleRoot(); WriteHeader(); - Write(RootEntry); + Directories.Write(RootEntry); } else { - if (!directoryEnumerator.MoveNext()) - throw new FormatException("Root directory entry not found."); - RootEntry = directoryEnumerator.Current; + RootEntry = Directories.GetDictionaryEntry(0); } } @@ -127,7 +126,7 @@ public void Dispose() miniStream?.Dispose(); miniFat?.Dispose(); - directoryEnumerator.Dispose(); + Directories.Dispose(); Fat.Dispose(); writer?.Dispose(); Reader.Dispose(); @@ -148,11 +147,6 @@ public void WriteHeader() writer.Write(Header); } - public void Write(DirectoryEntry entry) - { - directoryEnumerator.Write(entry); - } - public void Commit() { if (writer is null || transactedStream is null) diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index bddf78e6..cea8cdcf 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -55,7 +55,7 @@ public override void Flush() if (isDirty) { - ioContext.Write(DirectoryEntry); + ioContext.Directories.Write(DirectoryEntry); isDirty = false; } diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index 8c808a6d..4182f093 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -36,7 +36,7 @@ public IEnumerable EnumerateEntries(StorageType type) IEnumerable EnumerateDirectoryEntries() { - using DirectoryTreeEnumerator treeEnumerator = new(ioContext, DirectoryEntry); + using DirectoryTreeEnumerator treeEnumerator = new(ioContext.Directories, DirectoryEntry); while (treeEnumerator.MoveNext()) { yield return treeEnumerator.Current; @@ -48,13 +48,13 @@ IEnumerable EnumerateDirectoryEntries(StorageType type) => Enume DirectoryEntry? TryGetDirectoryEntry(string name) { - using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext, DirectoryEntry); + using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext.Directories, DirectoryEntry); return directoryTreeEnumerator.TryGetDirectoryEntry(name); } DirectoryEntry AddDirectoryEntry(StorageType storageType, string name) { - using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext, DirectoryEntry); + using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext.Directories, DirectoryEntry); return directoryTreeEnumerator.Add(storageType, name); } @@ -111,7 +111,7 @@ public void Delete(string name) this.ThrowIfDisposed(ioContext.IsDisposed); - using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext, DirectoryEntry); + using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext.Directories, DirectoryEntry); DirectoryEntry? entry = directoryTreeEnumerator.TryGetDirectoryEntry(name); if (entry is null) return; @@ -144,7 +144,7 @@ public void Delete(string name) internal void TraceDirectoryEntries(TextWriter writer) { - using DirectoryTreeEnumerator treeEnumerator = new(ioContext, DirectoryEntry); + using DirectoryTreeEnumerator treeEnumerator = new(ioContext.Directories, DirectoryEntry); treeEnumerator.PrintTrace(writer); } } From cb689e7b9d5b91a79a7cbec338ae7fff0cb41329 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 7 Nov 2024 11:53:31 +1300 Subject: [PATCH 071/134] Improve tracing --- OpenMcdf3.Tests/StreamTests.cs | 2 ++ OpenMcdf3/Fat.cs | 25 ++++++++++++++++++++++++- OpenMcdf3/FatEnumerator.cs | 29 ----------------------------- OpenMcdf3/MiniFat.cs | 6 +++++- OpenMcdf3/MiniFatEnumerator.cs | 10 ---------- 5 files changed, 31 insertions(+), 41 deletions(-) diff --git a/OpenMcdf3.Tests/StreamTests.cs b/OpenMcdf3.Tests/StreamTests.cs index bbf9d460..54580eec 100644 --- a/OpenMcdf3.Tests/StreamTests.cs +++ b/OpenMcdf3.Tests/StreamTests.cs @@ -132,6 +132,8 @@ public void Write(Version version, int length) Assert.AreEqual(length, stream.Length); Assert.AreEqual(length, stream.Position); + rootStorage.Trace(DebugWriter.Default); + byte[] actualBuffer = new byte[length]; stream.Position = 0; stream.ReadExactly(actualBuffer); diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs index 70baf9d2..3d7bf2c1 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf3/Fat.cs @@ -147,6 +147,29 @@ public uint Add(FatEnumerator fatEnumerator, uint startIndex) internal void Trace(TextWriter writer) { using FatEnumerator fatEnumerator = new(ioContext); - fatEnumerator.Trace(writer); + + byte[] data = new byte[ioContext.SectorSize]; + + Stream baseStream = ioContext.Reader.BaseStream; + + writer.WriteLine("Start of FAT ================="); + + while (fatEnumerator.MoveNext()) + { + FatEntry current = fatEnumerator.Current; + if (current.IsFree) + { + writer.WriteLine($"{current}"); + } + else + { + baseStream.Position = fatEnumerator.CurrentSector.Position; + baseStream.ReadExactly(data, 0, data.Length); + string hex = BitConverter.ToString(data); + writer.WriteLine($"{current}: {hex}"); + } + } + + writer.WriteLine("End of FAT ==================="); } } diff --git a/OpenMcdf3/FatEnumerator.cs b/OpenMcdf3/FatEnumerator.cs index d362c6e2..d64aebad 100644 --- a/OpenMcdf3/FatEnumerator.cs +++ b/OpenMcdf3/FatEnumerator.cs @@ -99,34 +99,5 @@ public void Reset() value = uint.MaxValue; } - internal void Trace(TextWriter writer) - { - Reset(); - - byte[] data = new byte[ioContext.SectorSize]; - - Stream baseStream = ioContext.Reader.BaseStream; - - writer.WriteLine("Start of FAT ================="); - - while (MoveNext()) - { - FatEntry current = Current; - if (current.IsFree) - { - writer.WriteLine($"{current}"); - } - else - { - baseStream.Position = CurrentSector.Position; - baseStream.ReadExactly(data, 0, data.Length); - string hex = BitConverter.ToString(data); - writer.WriteLine($"{current}: {hex}"); - } - } - - writer.WriteLine("End of FAT ==================="); - } - public override string ToString() => $"{Current}"; } diff --git a/OpenMcdf3/MiniFat.cs b/OpenMcdf3/MiniFat.cs index e2a526f4..8c76ddf5 100644 --- a/OpenMcdf3/MiniFat.cs +++ b/OpenMcdf3/MiniFat.cs @@ -142,6 +142,10 @@ public uint Add(MiniFatEnumerator miniFatEnumerator, uint startIndex) internal void Trace(TextWriter writer) { using MiniFatEnumerator miniFatEnumerator = new(ioContext); - miniFatEnumerator.Trace(writer); + + writer.WriteLine("Start of Mini FAT ============"); + while (miniFatEnumerator.MoveNext()) + writer.WriteLine($"{miniFatEnumerator.Current}"); + writer.WriteLine("End of Mini FAT =============="); } } diff --git a/OpenMcdf3/MiniFatEnumerator.cs b/OpenMcdf3/MiniFatEnumerator.cs index 48c9e017..4d108a1d 100644 --- a/OpenMcdf3/MiniFatEnumerator.cs +++ b/OpenMcdf3/MiniFatEnumerator.cs @@ -103,14 +103,4 @@ public void Reset() index = uint.MaxValue; value = uint.MaxValue; } - - internal void Trace(TextWriter writer) - { - Reset(); - - writer.WriteLine("Start of Mini FAT ============"); - while (MoveNext()) - writer.WriteLine($"Mini FAT entry {Current}"); - writer.WriteLine("End of Mini FAT =============="); - } } From a066e5cf06726c00a03f4bf4b1e11711710aa0f5 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 7 Nov 2024 13:39:20 +1300 Subject: [PATCH 072/134] Fix FAT sector caching when adding sector --- OpenMcdf3/Fat.cs | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs index 3d7bf2c1..e45946d2 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf3/Fat.cs @@ -14,7 +14,8 @@ internal sealed class Fat : IEnumerable, IDisposable private readonly int DifatArrayElementCount; private readonly int FatElementsPerSector; private readonly int DifatElementsPerSector; - private readonly byte[] sector; + private readonly byte[] cachedSectorBuffer; + Sector cachedSector = Sector.EndOfChain; private bool isDirty; public Fat(IOContext ioContext) @@ -24,7 +25,7 @@ public Fat(IOContext ioContext) DifatElementsPerSector = FatElementsPerSector - 1; DifatArrayElementCount = Header.DifatArrayLength * FatElementsPerSector; fatSectorEnumerator = new(ioContext); - sector = new byte[ioContext.SectorSize]; + cachedSectorBuffer = new byte[ioContext.SectorSize]; } public void Dispose() @@ -57,13 +58,27 @@ uint GetSectorIndexAndElementOffset(uint key, out long elementIndex) return (uint)Math.DivRem(key - DifatArrayElementCount, DifatElementsPerSector, out elementIndex); } + void CacheCurrentSector() + { + Sector current = fatSectorEnumerator.Current; + if (cachedSector.Id == current.Id) + return; + + Flush(); + + CfbBinaryReader reader = ioContext.Reader; + reader.Position = current.Position; + reader.Read(cachedSectorBuffer); + cachedSector = current; + } + public void Flush() { if (isDirty) { CfbBinaryWriter writer = ioContext.Writer; - writer.Position = fatSectorEnumerator.Current.Position; - writer.Write(sector); + writer.Position = cachedSector.Position; + writer.Write(cachedSectorBuffer); isDirty = false; } } @@ -71,18 +86,11 @@ public void Flush() bool TryMoveToSectorForKey(uint key, out long offset) { uint sectorId = GetSectorIndexAndElementOffset(key, out offset); - if (fatSectorEnumerator.IsAt(sectorId)) - return true; - - Flush(); - bool ok = fatSectorEnumerator.MoveTo(sectorId); if (!ok) return false; - CfbBinaryReader reader = ioContext.Reader; - reader.Position = fatSectorEnumerator.Current.Position; - reader.Read(sector); + CacheCurrentSector(); return true; } @@ -97,7 +105,7 @@ public bool TryGetValue(uint key, out uint value) return false; } - ReadOnlySpan slice = sector.AsSpan((int)elementIndex * sizeof(uint)); + ReadOnlySpan slice = cachedSectorBuffer.AsSpan((int)elementIndex * sizeof(uint)); value = BinaryPrimitives.ReadUInt32LittleEndian(slice); return true; } @@ -109,7 +117,7 @@ public bool TrySetValue(uint key, uint value) if (!TryMoveToSectorForKey(key, out long elementIndex)) return false; - Span slice = sector.AsSpan((int)elementIndex * sizeof(uint)); + Span slice = cachedSectorBuffer.AsSpan((int)elementIndex * sizeof(uint)); BinaryPrimitives.WriteUInt32LittleEndian(slice, value); isDirty = true; return true; @@ -127,11 +135,15 @@ public uint Add(FatEnumerator fatEnumerator, uint startIndex) && fatEnumerator.MoveNextFreeEntry(); if (!movedToFreeEntry) { + Flush(); + uint newSectorId = fatSectorEnumerator.Add(); // Next id must be free bool ok = fatEnumerator.MoveTo(newSectorId + 1); Debug.Assert(ok); + + CacheCurrentSector(); } FatEntry entry = fatEnumerator.Current; From 3103c0deb98e3aace6c1af3f0a26dc049f5e95b1 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 7 Nov 2024 14:18:26 +1300 Subject: [PATCH 073/134] Fix FAT sector index calculation --- OpenMcdf3/Fat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs index e45946d2..afc359a8 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf3/Fat.cs @@ -55,7 +55,7 @@ uint GetSectorIndexAndElementOffset(uint key, out long elementIndex) { if (key < DifatArrayElementCount) return (uint)Math.DivRem(key, FatElementsPerSector, out elementIndex); - return (uint)Math.DivRem(key - DifatArrayElementCount, DifatElementsPerSector, out elementIndex); + return Header.DifatArrayLength + (uint)Math.DivRem(key - DifatArrayElementCount, DifatElementsPerSector, out elementIndex); } void CacheCurrentSector() From 76b8dfce41a319aee6598f3d70c2cdf918d43bcd Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 7 Nov 2024 14:27:55 +1300 Subject: [PATCH 074/134] Validate FAT in tests --- OpenMcdf3.Benchmarks/InMemory.cs | 2 +- OpenMcdf3.Tests/StreamTests.cs | 11 ++++++++++- OpenMcdf3/Fat.cs | 16 +++++++++++++++- OpenMcdf3/FatSectorEnumerator.cs | 1 - OpenMcdf3/MiniFat.cs | 14 ++++++++++++++ OpenMcdf3/RootStorage.cs | 8 +++++++- 6 files changed, 47 insertions(+), 5 deletions(-) diff --git a/OpenMcdf3.Benchmarks/InMemory.cs b/OpenMcdf3.Benchmarks/InMemory.cs index 90ca4db4..209088ab 100644 --- a/OpenMcdf3.Benchmarks/InMemory.cs +++ b/OpenMcdf3.Benchmarks/InMemory.cs @@ -12,7 +12,7 @@ namespace OpenMcdf3.Benchmark; [Orderer(SummaryOrderPolicy.FastestToSlowest)] public class InMemory : IDisposable { - bool inMemory = false; + bool inMemory = true; private const int Kb = 1024; private const int Mb = Kb * Kb; private const string storageName = "MyStorage"; diff --git a/OpenMcdf3.Tests/StreamTests.cs b/OpenMcdf3.Tests/StreamTests.cs index 54580eec..717444a5 100644 --- a/OpenMcdf3.Tests/StreamTests.cs +++ b/OpenMcdf3.Tests/StreamTests.cs @@ -29,6 +29,8 @@ public void ReadViaCopyTo(Version version, int length) { string fileName = $"TestStream_v{(int)version}_{length}.cfs"; using var rootStorage = RootStorage.OpenRead(fileName); + rootStorage.Validate(); + using Stream stream = rootStorage.OpenStream("TestStream"); Assert.AreEqual(length, stream.Length); @@ -132,7 +134,7 @@ public void Write(Version version, int length) Assert.AreEqual(length, stream.Length); Assert.AreEqual(length, stream.Position); - rootStorage.Trace(DebugWriter.Default); + rootStorage.Validate(); byte[] actualBuffer = new byte[length]; stream.Position = 0; @@ -188,6 +190,7 @@ public void WriteThenRead(Version version, int length) using (var rootStorage = RootStorage.Open(memoryStream)) { using CfbStream stream = rootStorage.OpenStream("TestStream"); + rootStorage.Validate(); Assert.AreEqual(length, stream.Length); byte[] actualBuffer = new byte[length]; @@ -303,6 +306,8 @@ public void Modify(Version version, int length) using (var rootStorage = RootStorage.Open(memoryStream)) { + rootStorage.Validate(); + using (CfbStream stream = rootStorage.OpenStream("TestStream1")) { Assert.AreEqual(length, stream.Length); @@ -379,6 +384,8 @@ public void ModifyCommit(Version version, int length) using (var rootStorage = RootStorage.Open(memoryStream)) { + rootStorage.Validate(); + using (CfbStream stream = rootStorage.OpenStream("TestStream1")) { Assert.AreEqual(length, stream.Length); @@ -507,6 +514,8 @@ public void ModifyRevert(Version version, int length) using (var rootStorage = RootStorage.Open(memoryStream)) { + rootStorage.Validate(); + using (CfbStream stream = rootStorage.OpenStream("TestStream1")) { Assert.AreEqual(length, stream.Length); diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs index afc359a8..adb5d6e5 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf3/Fat.cs @@ -156,7 +156,7 @@ public uint Add(FatEnumerator fatEnumerator, uint startIndex) IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - internal void Trace(TextWriter writer) + internal void WriteTrace(TextWriter writer) { using FatEnumerator fatEnumerator = new(ioContext); @@ -184,4 +184,18 @@ internal void Trace(TextWriter writer) writer.WriteLine("End of FAT ==================="); } + + internal void Validate() + { + using FatEnumerator fatEnumerator = new(ioContext); + + while (fatEnumerator.MoveNext()) + { + FatEntry current = fatEnumerator.Current; + if (current.Value <= SectorType.Maximum && fatEnumerator.CurrentSector.EndPosition > ioContext.Length) + { + throw new FormatException($"FAT entry {current} is beyond the end of the stream."); + } + } + } } diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index 2e5b625f..3f2ea9bb 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -194,7 +194,6 @@ public uint Add() } ioContext.Fat[newSector.Id] = sectorType; - return newSector.Id; } } diff --git a/OpenMcdf3/MiniFat.cs b/OpenMcdf3/MiniFat.cs index 8c76ddf5..d804f43a 100644 --- a/OpenMcdf3/MiniFat.cs +++ b/OpenMcdf3/MiniFat.cs @@ -148,4 +148,18 @@ internal void Trace(TextWriter writer) writer.WriteLine($"{miniFatEnumerator.Current}"); writer.WriteLine("End of Mini FAT =============="); } + + internal void Validate() + { + using MiniFatEnumerator miniFatEnumerator = new(ioContext); + + while (miniFatEnumerator.MoveNext()) + { + FatEntry current = miniFatEnumerator.Current; + if (current.Value <= SectorType.Maximum && miniFatEnumerator.CurrentSector.EndPosition > ioContext.MiniStream.Length) + { + throw new FormatException($"Mini FAT entry {current} is beyond the end of the mini stream."); + } + } + } } diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index 0201bcef..fcdd90c2 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -89,7 +89,13 @@ public void Revert() internal void Trace(TextWriter writer) { writer.WriteLine(ioContext.Header); - ioContext.Fat.Trace(writer); + ioContext.Fat.WriteTrace(writer); ioContext.MiniFat.Trace(writer); } + + internal void Validate() + { + ioContext.Fat.Validate(); + ioContext.MiniFat.Validate(); + } } From b407f1786964236839fc37295531613da8275a13 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Fri, 8 Nov 2024 13:48:27 +1300 Subject: [PATCH 075/134] Set Mini FAT sector count --- OpenMcdf3/FatSectorEnumerator.cs | 2 +- OpenMcdf3/MiniFat.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index 3f2ea9bb..16b7c8ef 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -162,7 +162,7 @@ public uint Add() sectorType = SectorType.Fat; header.Difat[nextIndex] = newSector.Id; - header.FatSectorCount++; // TODO: Check + header.FatSectorCount++; writer.Position = newSector.Position; writer.Write(SectorDataCache.GetFatEntryData(newSector.Length)); diff --git a/OpenMcdf3/MiniFat.cs b/OpenMcdf3/MiniFat.cs index d804f43a..bbb22d6b 100644 --- a/OpenMcdf3/MiniFat.cs +++ b/OpenMcdf3/MiniFat.cs @@ -119,8 +119,10 @@ public uint Add(MiniFatEnumerator miniFatEnumerator, uint startIndex) writer.Position = sector.Position; writer.Write(SectorDataCache.GetFatEntryData(sector.Length)); - if (ioContext.Header.FirstMiniFatSectorId == SectorType.EndOfChain) - ioContext.Header.FirstMiniFatSectorId = newSectorIndex; + Header header = ioContext.Header; + if (header.FirstMiniFatSectorId == SectorType.EndOfChain) + header.FirstMiniFatSectorId = newSectorIndex; + header.MiniFatSectorCount++; miniFatEnumerator.Reset(); // TODO: Jump closer to the new sector From 02c236c8ce7da88a4eea3bf6e3acba1a1216cd05 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Fri, 8 Nov 2024 13:51:21 +1300 Subject: [PATCH 076/134] Set directory sector count for V4 --- OpenMcdf3/Directories.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/OpenMcdf3/Directories.cs b/OpenMcdf3/Directories.cs index 186043ab..a90d57f6 100644 --- a/OpenMcdf3/Directories.cs +++ b/OpenMcdf3/Directories.cs @@ -55,8 +55,11 @@ public DirectoryEntry CreateOrRecycleDirectoryEntry() CfbBinaryWriter writer = ioContext.Writer; uint id = fatChainEnumerator.Extend(); - if (ioContext.Header.FirstDirectorySectorId == SectorType.EndOfChain) - ioContext.Header.FirstDirectorySectorId = id; + Header header = ioContext.Header; + if (header.FirstDirectorySectorId == SectorType.EndOfChain) + header.FirstDirectorySectorId = id; + if (ioContext.Version == Version.V4) + header.DirectorySectorCount++; Sector sector = new(id, ioContext.SectorSize); writer.Position = sector.Position; From cb9cb9d7cb0d5fa8e12577631f106dd810fb1966 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 7 Nov 2024 10:19:24 +1300 Subject: [PATCH 077/134] Use byte[] for directory entry name --- OpenMcdf3.Tests/BinaryWriterTests.cs | 8 +++- OpenMcdf3/CfbBinaryReader.cs | 9 ++--- OpenMcdf3/CfbBinaryWriter.cs | 6 +-- OpenMcdf3/DirectoryEntry.cs | 56 +++++++++++++++++++--------- OpenMcdf3/DirectoryEntryComparer.cs | 46 +++++++++++++++++++++++ OpenMcdf3/DirectoryTreeEnumerator.cs | 4 +- 6 files changed, 100 insertions(+), 29 deletions(-) create mode 100644 OpenMcdf3/DirectoryEntryComparer.cs diff --git a/OpenMcdf3.Tests/BinaryWriterTests.cs b/OpenMcdf3.Tests/BinaryWriterTests.cs index 6e5f4722..cd45cff0 100644 --- a/OpenMcdf3.Tests/BinaryWriterTests.cs +++ b/OpenMcdf3.Tests/BinaryWriterTests.cs @@ -1,4 +1,6 @@ -namespace OpenMcdf3.Tests; +using System.Text; + +namespace OpenMcdf3.Tests; [TestClass] public sealed class BinaryWriterTests @@ -44,7 +46,6 @@ public void WriteDirectoryEntry() { DirectoryEntry expected = new() { - Name = "Root Entry", Type = StorageType.Storage, Color = NodeColor.Red, LeftSiblingId = 2, @@ -58,6 +59,9 @@ public void WriteDirectoryEntry() StreamLength = 7 }; + string name = "Root Entry"; + expected.NameLength = (ushort)Encoding.Unicode.GetBytes(name, 0, name.Length, expected.Name, 0); + using MemoryStream stream = new(); using CfbBinaryWriter writer = new(stream); writer.Write(expected); diff --git a/OpenMcdf3/CfbBinaryReader.cs b/OpenMcdf3/CfbBinaryReader.cs index c9c6b0f6..51cbf2ae 100644 --- a/OpenMcdf3/CfbBinaryReader.cs +++ b/OpenMcdf3/CfbBinaryReader.cs @@ -123,15 +123,12 @@ public DirectoryEntry ReadDirectoryEntry(Version version, uint sid) if (version is not Version.V3 and not Version.V4) throw new ArgumentException($"Unsupported version: {version}.", nameof(version)); - Read(buffer, 0, DirectoryEntry.NameFieldLength); // TODO - ushort nameLength = ReadUInt16(); - int clampedNameLength = Math.Max(0, Math.Min(DirectoryEntry.NameFieldLength, nameLength - 2)); - string name = Encoding.Unicode.GetString(buffer, 0, clampedNameLength); + Read(buffer, 0, DirectoryEntry.NameFieldLength); DirectoryEntry entry = new() { Id = sid, - Name = name, + NameLength = ReadUInt16(), Type = ReadStorageType(), Color = ReadColor(), LeftSiblingId = ReadUInt32(), @@ -144,6 +141,8 @@ public DirectoryEntry ReadDirectoryEntry(Version version, uint sid) StartSectorId = ReadUInt32() }; + Buffer.BlockCopy(buffer, 0, entry.Name, 0, DirectoryEntry.NameFieldLength); + if (version == Version.V3) { entry.StreamLength = ReadUInt32(); diff --git a/OpenMcdf3/CfbBinaryWriter.cs b/OpenMcdf3/CfbBinaryWriter.cs index 73e65954..5ff8c4a6 100644 --- a/OpenMcdf3/CfbBinaryWriter.cs +++ b/OpenMcdf3/CfbBinaryWriter.cs @@ -69,10 +69,8 @@ public void Write(Header header) public void Write(DirectoryEntry entry) { - buffer.AsSpan().Clear(); - int nameLength = Encoding.Unicode.GetBytes(entry.Name, 0, entry.Name.Length, buffer, 0); - Write(buffer, 0, DirectoryEntry.NameFieldLength); - Write((short)(nameLength + 2)); + Write(entry.Name, 0, DirectoryEntry.NameFieldLength); + Write(entry.NameLength); Write((byte)entry.Type); Write((byte)entry.Color); Write(entry.LeftSiblingId); diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index f08cf2ce..081fc99c 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -1,4 +1,7 @@ -namespace OpenMcdf3; +using System.Runtime.InteropServices; +using System.Text; + +namespace OpenMcdf3; /// /// The storage type of a . @@ -42,21 +45,14 @@ internal sealed class DirectoryEntry : IEquatable internal static readonly byte[] Unallocated = new byte[128]; - string name = string.Empty; DateTime creationTime; DateTime modifiedTime; public uint Id { get; set; } - public string Name - { - get => name; - set - { - ThrowHelper.ThrowIfNameIsInvalid(value); - name = value; - } - } + public byte[] Name { get; } = new byte[NameFieldLength]; + + public ushort NameLength { get; set; } /// /// The type of the storage object. @@ -137,12 +133,34 @@ public DateTime ModifiedTime _ => '?' }; + public ReadOnlySpan NameByteSpan + { + get + { + int clampedNameLength = Math.Max(0, Math.Min(NameFieldLength, NameLength - 2)); + return Name.AsSpan(0, clampedNameLength); + } + } + + public ReadOnlySpan NameCharSpan => MemoryMarshal.Cast(NameByteSpan); + + public string NameString + { + get + { + int clampedNameLength = Math.Max(0, Math.Min(NameFieldLength, NameLength - 2)); + return Encoding.Unicode.GetString(Name, 0, clampedNameLength); + } + set => NameLength = (ushort)(Encoding.Unicode.GetBytes(value, 0, value.Length, Name, 0) + 2); + } + public override bool Equals(object? obj) => Equals(obj as DirectoryEntry); public bool Equals(DirectoryEntry? other) { return other is not null - && Name == other.Name + && Name.SequenceEqual(other.Name) + && NameLength == other.NameLength && Type == other.Type && Color == other.Color && LeftSiblingId == other.LeftSiblingId @@ -163,8 +181,8 @@ public bool Equals(DirectoryEntry? other) public void Recycle(StorageType storageType, string name) { Type = storageType; + NameString = name; Color = NodeColor.Black; - Name = name; LeftSiblingId = StreamId.NoStream; RightSiblingId = StreamId.NoStream; ChildId = StreamId.NoStream; @@ -189,16 +207,16 @@ public void Recycle(StorageType storageType, string name) } } - public EntryInfo ToEntryInfo() => new(Name, StreamLength); + public EntryInfo ToEntryInfo() => new(NameString, StreamLength); - public override string ToString() => $"{Id}: \"{Name}\""; + public override string ToString() => $"{Id}: \"{NameString}\""; public DirectoryEntry Clone() { - return new DirectoryEntry + DirectoryEntry clone = new() { Id = Id, - Name = Name, + NameLength = NameLength, Type = Type, Color = Color, LeftSiblingId = LeftSiblingId, @@ -211,5 +229,9 @@ public DirectoryEntry Clone() StartSectorId = StreamId.NoStream, StreamLength = 0 }; + + Array.Copy(Name, clone.Name, Name.Length); + + return clone; } } diff --git a/OpenMcdf3/DirectoryEntryComparer.cs b/OpenMcdf3/DirectoryEntryComparer.cs new file mode 100644 index 00000000..f4799b15 --- /dev/null +++ b/OpenMcdf3/DirectoryEntryComparer.cs @@ -0,0 +1,46 @@ +using System.Diagnostics; + +namespace OpenMcdf3; + +internal class DirectoryEntryComparer : IComparer +{ + public static DirectoryEntryComparer Default { get; } = new(); + + public static int Compare(ReadOnlySpan x, ReadOnlySpan y) + { + if (x.Length < y.Length) + return -1; + + if (x.Length > y.Length) + return 1; + + for (int i = 0; i < x.Length; i++) + { + char xChar = char.ToUpperInvariant(x[i]); + char yChar = char.ToUpperInvariant(y[i]); + + if (xChar < yChar) + return -1; + if (xChar > yChar) + return 1; + } + + return 0; + } + + public int Compare(DirectoryEntry? x, DirectoryEntry? y) + { + Debug.Assert(x is not null && y is not null); + + if (x == null && y == null) + return 0; + + if (x is null) + return -1; + + if (y is null) + return 1; + + return Compare(x.NameCharSpan, y.NameCharSpan); + } +} diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index 8c840901..0fec3c74 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -87,9 +87,11 @@ public bool MoveTo(string name) { Reset(); + ReadOnlySpan nameSpan = name.AsSpan(); + while (MoveNext()) { - if (Current.Name == name) + if (DirectoryEntryComparer.Compare(Current.NameCharSpan, nameSpan) == 0) return true; } From 92cc9d74b7c4a63cf78625fbeaa5fa9207a01344 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 7 Nov 2024 22:18:16 +1300 Subject: [PATCH 078/134] Add initial DirectoryTree implementation --- OpenMcdf3.Tests/StorageTests.cs | 4 + OpenMcdf3/Directories.cs | 6 + OpenMcdf3/DirectoryTree.cs | 229 +++++++++++++++++++++++++++ OpenMcdf3/DirectoryTreeEnumerator.cs | 129 ++------------- OpenMcdf3/Storage.cs | 26 ++- 5 files changed, 259 insertions(+), 135 deletions(-) create mode 100644 OpenMcdf3/DirectoryTree.cs diff --git a/OpenMcdf3.Tests/StorageTests.cs b/OpenMcdf3.Tests/StorageTests.cs index 003a4b87..62f16f77 100644 --- a/OpenMcdf3.Tests/StorageTests.cs +++ b/OpenMcdf3.Tests/StorageTests.cs @@ -31,6 +31,7 @@ public void CreateStorage(Version version, int subStorageCount) { for (int i = 0; i < subStorageCount; i++) rootStorage.CreateStorage($"Test{i}"); + rootStorage.TraceDirectoryEntries(DebugWriter.Default); } memoryStream.Position = 0; @@ -38,6 +39,9 @@ public void CreateStorage(Version version, int subStorageCount) { IEnumerable entries = rootStorage.EnumerateEntries(); Assert.AreEqual(subStorageCount, entries.Count()); + + for (int i = 0; i < subStorageCount; i++) + rootStorage.OpenStorage($"Test{i}"); } } diff --git a/OpenMcdf3/Directories.cs b/OpenMcdf3/Directories.cs index a90d57f6..f96513d9 100644 --- a/OpenMcdf3/Directories.cs +++ b/OpenMcdf3/Directories.cs @@ -32,6 +32,12 @@ public DirectoryEntry GetDictionaryEntry(uint streamId) public bool TryGetDictionaryEntry(uint streamId, out DirectoryEntry? entry) { + if (streamId == StreamId.NoStream) + { + entry = null; + return false; + } + if (streamId > StreamId.Maximum) throw new ArgumentException($"Invalid directory entry stream ID: ${streamId:X8}.", nameof(streamId)); diff --git a/OpenMcdf3/DirectoryTree.cs b/OpenMcdf3/DirectoryTree.cs new file mode 100644 index 00000000..c8077b9a --- /dev/null +++ b/OpenMcdf3/DirectoryTree.cs @@ -0,0 +1,229 @@ +using System.Diagnostics; + +namespace OpenMcdf3; + +internal class DirectoryTree +{ + internal enum RelationType + { + Previous, + Next, + Directory, + } + + private readonly Directories directories; + private readonly DirectoryEntry root; + + public DirectoryTree(Directories directories, DirectoryEntry root) + { + this.directories = directories; + this.root = root; + } + + public bool TryGetDirectoryEntry(string name, out DirectoryEntry? entry) + { + if (!directories.TryGetDictionaryEntry(root.ChildId, out DirectoryEntry? child)) + { + entry = null; + return false; + } + + ReadOnlySpan nameSpan = name.AsSpan(); + while (child is not null) + { + int compare = DirectoryEntryComparer.Compare(nameSpan, child.NameCharSpan); + if (compare < 0) + { + directories.TryGetDictionaryEntry(child.LeftSiblingId, out child); + } + else if (compare > 0) + { + directories.TryGetDictionaryEntry(child.RightSiblingId, out child); + } + else + { + entry = child; + return true; + } + } + + entry = null; + return false; + } + + public DirectoryEntry GetParent(DirectoryEntry entry, out RelationType relation) + { + if (!TryGetParent(entry, out DirectoryEntry? parent, out relation)) + throw new KeyNotFoundException($"DirectoryEntry {entry} has no parent."); + return parent!; + } + + public bool TryGetParent(DirectoryEntry entry, out DirectoryEntry? parent, out RelationType relation) + { + if (!directories.TryGetDictionaryEntry(root.ChildId, out DirectoryEntry? child)) + { + parent = null; + relation = RelationType.Directory; + return false; + } + + parent = root; + relation = RelationType.Directory; + while (child is not null) + { + int compare = DirectoryEntryComparer.Compare(entry.NameCharSpan, child.NameCharSpan); + if (compare < 0) + { + parent = child; + relation = RelationType.Previous; + directories.TryGetDictionaryEntry(child.LeftSiblingId, out child); + } + else if (compare > 0) + { + parent = child; + relation = RelationType.Next; + directories.TryGetDictionaryEntry(child.RightSiblingId, out child); + } + else + { + return true; + } + } + + return false; + } + + public void Add(DirectoryEntry entry) + { + if (!directories.TryGetDictionaryEntry(root.ChildId, out DirectoryEntry? currentEntry)) + { + root.ChildId = entry.Id; + directories.Write(root); + directories.Write(entry); + return; + } + + uint previous = currentEntry!.LeftSiblingId; + uint next = currentEntry.RightSiblingId; + + while (true) + { + int compare = DirectoryEntryComparer.Compare(entry.NameCharSpan, currentEntry!.NameCharSpan); + if (compare < 0) + { + if (previous == StreamId.NoStream) + { + currentEntry.LeftSiblingId = entry.Id; + directories.Write(currentEntry); + directories.Write(entry); + return; + } + + currentEntry = directories.GetDictionaryEntry(previous); + } + else if (compare > 0) + { + if (next == StreamId.NoStream) + { + currentEntry.RightSiblingId = entry.Id; + directories.Write(currentEntry); + directories.Write(entry); + return; + } + + currentEntry = directories.GetDictionaryEntry(next); + } + else + { + throw new IOException($"{entry.Type} \"{entry.NameString}\" already exists."); + } + + previous = currentEntry!.LeftSiblingId; + next = currentEntry!.RightSiblingId; + } + } + + void SetRelation(DirectoryEntry entry, RelationType relation, uint value) + { + switch (relation) + { + case RelationType.Previous: + entry.LeftSiblingId = value; + break; + case RelationType.Next: + entry.RightSiblingId = value; + break; + case RelationType.Directory: + root.ChildId = value; + break; + } + } + + public void Remove(DirectoryEntry entry) + { + DirectoryEntry parent = GetParent(entry, out RelationType relation); + + if (entry.LeftSiblingId == StreamId.NoStream) + { + SetRelation(parent, relation, entry.RightSiblingId); + directories.Write(parent); + } + else + { + SetRelation(parent, relation, entry.LeftSiblingId); + directories.Write(parent); + + if (entry.RightSiblingId != StreamId.NoStream) + { + uint newRightChildParent = entry.LeftSiblingId; + DirectoryEntry newRightChildParentEntry; + for (; ; ) + { + newRightChildParentEntry = directories.GetDictionaryEntry(newRightChildParent); + if (newRightChildParentEntry.RightSiblingId != StreamId.NoStream) + { + break; + } + }; + + newRightChildParentEntry.RightSiblingId = entry.RightSiblingId; + directories.Write(newRightChildParentEntry); + } + } + } + + public void WriteTrace(TextWriter writer) + { + if (root is null) + { + Trace.WriteLine(""); + return; + } + + Stack<(DirectoryEntry node, int indentLevel)> stack = new Stack<(DirectoryEntry, int)>(); + directories.TryGetDictionaryEntry(root.ChildId, out DirectoryEntry? current); + int currentIndentLevel = 0; + + while (stack.Count > 0 || current is not null) + { + if (current != null) + { + stack.Push((current, currentIndentLevel)); + directories.TryGetDictionaryEntry(current.RightSiblingId, out current); + currentIndentLevel++; + } + else + { + (DirectoryEntry node, int indentLevel) = stack.Pop(); + currentIndentLevel = indentLevel; + + for (int i = 0; i < indentLevel; i++) + writer.Write(" "); + writer.WriteLine(node.Color == NodeColor.Black ? $" {node} " : $"<{node}>"); + + directories.TryGetDictionaryEntry(node.LeftSiblingId, out current); + currentIndentLevel++; + } + } + } +} diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index 0fec3c74..91b93b9d 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -10,19 +10,14 @@ internal sealed class DirectoryTreeEnumerator : IEnumerator { private readonly Directories directories; private readonly DirectoryEntry root; - private DirectoryEntry? child; private readonly Stack stack = new(); - DirectoryEntry parent; DirectoryEntry? current; internal DirectoryTreeEnumerator(Directories directories, DirectoryEntry root) { this.directories = directories; this.root = root; - if (root.ChildId != StreamId.NoStream) - child = directories.GetDictionaryEntry(root.ChildId); - parent = root; - PushLeft(child); + Reset(); } /// @@ -50,17 +45,12 @@ public bool MoveNext() if (stack.Count == 0) { current = null; - parent = root; return false; } current = stack.Pop(); - parent = stack.Count == 0 ? root : stack.Peek(); - if (current.RightSiblingId != StreamId.NoStream) - { - DirectoryEntry rightSibling = directories.GetDictionaryEntry(current.RightSiblingId); - PushLeft(rightSibling); - } + if (directories.TryGetDictionaryEntry(current.RightSiblingId, out DirectoryEntry? rightSibling)) + PushLeft(rightSibling!); return true; } @@ -69,9 +59,12 @@ public bool MoveNext() public void Reset() { current = null; - parent = root; stack.Clear(); - PushLeft(child); + if (root.ChildId != StreamId.NoStream) + { + DirectoryEntry child = directories.GetDictionaryEntry(root.ChildId); + PushLeft(child); + } } private void PushLeft(DirectoryEntry? node) @@ -79,111 +72,7 @@ private void PushLeft(DirectoryEntry? node) while (node is not null) { stack.Push(node); - node = node.LeftSiblingId == StreamId.NoStream ? null : directories.GetDictionaryEntry(node.LeftSiblingId); - } - } - - public bool MoveTo(string name) - { - Reset(); - - ReadOnlySpan nameSpan = name.AsSpan(); - - while (MoveNext()) - { - if (DirectoryEntryComparer.Compare(Current.NameCharSpan, nameSpan) == 0) - return true; - } - - return false; - } - - public DirectoryEntry? TryGetDirectoryEntry(string name) - { - if (MoveTo(name)) - return Current; - return null; - } - - public DirectoryEntry Add(StorageType storageType, string name) - { - if (MoveTo(name)) - throw new IOException($"{storageType} \"{name}\" already exists."); - - DirectoryEntry entry = directories.CreateOrRecycleDirectoryEntry(); - entry.Recycle(storageType, name); - - Add(entry); - - return entry; - } - - void Add(DirectoryEntry entry) - { - Reset(); - - // TODO: Implement balancing (all-black for now) - entry.Color = NodeColor.Black; - directories.Write(entry); - - if (root.ChildId == StreamId.NoStream) - { - Debug.Assert(child is null); - root.ChildId = entry.Id; - directories.Write(root); - child = entry; - } - else - { - Debug.Assert(child is not null); - DirectoryEntry node = child!; - while (node.LeftSiblingId != StreamId.NoStream) - node = directories.GetDictionaryEntry(node.LeftSiblingId); - node.LeftSiblingId = entry.Id; - directories.Write(node); - } - } - - public void Remove(DirectoryEntry entry) - { - if (child is null) - throw new KeyNotFoundException("DirectoryEntry has no children"); - - if (root.ChildId == entry.Id) - { - root.ChildId = entry.LeftSiblingId; - directories.Write(root); - if (root.ChildId == StreamId.NoStream) - child = null; - return; - } - - Reset(); - - while (MoveNext()) - { - if (current!.Id == entry.Id) - { - if (parent.LeftSiblingId == entry.Id) - parent.LeftSiblingId = entry.LeftSiblingId; - directories.Write(parent); - - entry.Recycle(); - directories.Write(entry); - break; - } - } - } - - internal void PrintTrace(TextWriter writer) - { - Reset(); - - while (MoveNext()) - { - for (int i = 0; i < stack.Count; i++) - writer.Write(" "); - writer.WriteLine($"{Current.ColorChar} {Current}"); + directories.TryGetDictionaryEntry(node.LeftSiblingId, out node); } } } diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index 4182f093..33cf3b76 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -6,6 +6,7 @@ public class Storage { internal readonly IOContext ioContext; + internal readonly DirectoryTree directoryTree; internal DirectoryEntry DirectoryEntry { get; } @@ -15,6 +16,7 @@ internal Storage(IOContext ioContext, DirectoryEntry directoryEntry) throw new ArgumentException("DirectoryEntry must be a Storage or Root.", nameof(directoryEntry)); this.ioContext = ioContext; + directoryTree = new(ioContext.Directories, directoryEntry); DirectoryEntry = directoryEntry; } @@ -46,16 +48,12 @@ IEnumerable EnumerateDirectoryEntries() IEnumerable EnumerateDirectoryEntries(StorageType type) => EnumerateDirectoryEntries() .Where(e => e.Type == type); - DirectoryEntry? TryGetDirectoryEntry(string name) - { - using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext.Directories, DirectoryEntry); - return directoryTreeEnumerator.TryGetDirectoryEntry(name); - } - DirectoryEntry AddDirectoryEntry(StorageType storageType, string name) { - using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext.Directories, DirectoryEntry); - return directoryTreeEnumerator.Add(storageType, name); + DirectoryEntry entry = ioContext.Directories.CreateOrRecycleDirectoryEntry(); + entry.Recycle(storageType, name); + directoryTree.Add(entry); + return entry; } public Storage CreateStorage(string name) @@ -85,7 +83,7 @@ public Storage OpenStorage(string name) this.ThrowIfDisposed(ioContext.IsDisposed); - DirectoryEntry? entry = TryGetDirectoryEntry(name); + directoryTree.TryGetDirectoryEntry(name, out DirectoryEntry? entry); if (entry is null || entry.Type is not StorageType.Storage) throw new DirectoryNotFoundException($"Storage not found: {name}."); return new Storage(ioContext, entry); @@ -97,7 +95,7 @@ public CfbStream OpenStream(string name) this.ThrowIfDisposed(ioContext.IsDisposed); - DirectoryEntry? entry = TryGetDirectoryEntry(name); + directoryTree.TryGetDirectoryEntry(name, out DirectoryEntry? entry); if (entry is null || entry.Type is not StorageType.Stream) throw new FileNotFoundException($"Stream not found: {name}.", name); @@ -111,8 +109,7 @@ public void Delete(string name) this.ThrowIfDisposed(ioContext.IsDisposed); - using DirectoryTreeEnumerator directoryTreeEnumerator = new(ioContext.Directories, DirectoryEntry); - DirectoryEntry? entry = directoryTreeEnumerator.TryGetDirectoryEntry(name); + directoryTree.TryGetDirectoryEntry(name, out DirectoryEntry? entry); if (entry is null) return; @@ -139,12 +136,11 @@ public void Delete(string name) } } - directoryTreeEnumerator.Remove(entry); + directoryTree.Remove(entry); } internal void TraceDirectoryEntries(TextWriter writer) { - using DirectoryTreeEnumerator treeEnumerator = new(ioContext.Directories, DirectoryEntry); - treeEnumerator.PrintTrace(writer); + directoryTree.WriteTrace(writer); } } From 59e907ffc51df34228cbff60689d381afd38319d Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Fri, 8 Nov 2024 11:56:39 +1300 Subject: [PATCH 079/134] Add structured storage reference tests --- OpenMcdf3.Tests/OpenMcdf3.Tests.csproj | 6 +- OpenMcdf3.Tests/StorageTests.cs | 55 ++- OpenMcdf3.Tests/StreamTests.cs | 16 +- OpenMcdf3.sln | 8 +- OpenMcdf3/RootStorage.cs | 2 +- StructuredStorage/LockBytes.cs | 50 +++ StructuredStorage/NativeMethods.json | 6 + StructuredStorage/NativeMethods.txt | 38 +++ StructuredStorage/PropertySetStorage.cs | 50 +++ StructuredStorage/PropertyStorage.cs | 167 ++++++++++ StructuredStorage/Storage.cs | 370 +++++++++++++++++++++ StructuredStorage/Stream.cs | 185 +++++++++++ StructuredStorage/StructuredStorage.csproj | 18 + 13 files changed, 944 insertions(+), 27 deletions(-) create mode 100644 StructuredStorage/LockBytes.cs create mode 100644 StructuredStorage/NativeMethods.json create mode 100644 StructuredStorage/NativeMethods.txt create mode 100644 StructuredStorage/PropertySetStorage.cs create mode 100644 StructuredStorage/PropertyStorage.cs create mode 100644 StructuredStorage/Storage.cs create mode 100644 StructuredStorage/Stream.cs create mode 100644 StructuredStorage/StructuredStorage.csproj diff --git a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj index 1a72e048..7b0ea833 100644 --- a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj +++ b/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj @@ -1,7 +1,7 @@ - net48;net8.0 + net48;net8.0-windows Exe 11.0 enable @@ -17,6 +17,10 @@ + + + + diff --git a/OpenMcdf3.Tests/StorageTests.cs b/OpenMcdf3.Tests/StorageTests.cs index 62f16f77..2390ee51 100644 --- a/OpenMcdf3.Tests/StorageTests.cs +++ b/OpenMcdf3.Tests/StorageTests.cs @@ -10,9 +10,19 @@ public sealed class StorageTests [DataRow("MultipleStorage4.cfs", 1)] public void Read(string fileName, long storageCount) { - using var rootStorage = RootStorage.OpenRead(fileName); - IEnumerable storageEntries = rootStorage.EnumerateEntries(StorageType.Storage); - Assert.AreEqual(storageCount, storageEntries.Count()); + using (var rootStorage = RootStorage.OpenRead(fileName, StorageModeFlags.LeaveOpen)) + { + IEnumerable storageEntries = rootStorage.EnumerateEntries(StorageType.Storage); + Assert.AreEqual(storageCount, storageEntries.Count()); + } + +#if WINDOWS + using (var rootStorage = StructuredStorage.Storage.Open(fileName)) + { + IEnumerable entries = rootStorage.EnumerateEntries(); + Assert.AreEqual(storageCount, entries.Count()); + } +#endif } [TestMethod] @@ -27,7 +37,7 @@ public void Read(string fileName, long storageCount) public void CreateStorage(Version version, int subStorageCount) { using MemoryStream memoryStream = new(); - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) { for (int i = 0; i < subStorageCount; i++) rootStorage.CreateStorage($"Test{i}"); @@ -35,7 +45,7 @@ public void CreateStorage(Version version, int subStorageCount) } memoryStream.Position = 0; - using (var rootStorage = RootStorage.Open(memoryStream)) + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.LeaveOpen)) { IEnumerable entries = rootStorage.EnumerateEntries(); Assert.AreEqual(subStorageCount, entries.Count()); @@ -43,6 +53,19 @@ public void CreateStorage(Version version, int subStorageCount) for (int i = 0; i < subStorageCount; i++) rootStorage.OpenStorage($"Test{i}"); } + +#if WINDOWS + using (var rootStorage = StructuredStorage.Storage.Open(memoryStream)) + { + IEnumerable entries = rootStorage.EnumerateEntries(); + Assert.AreEqual(subStorageCount, entries.Count()); + + for (int i = 0; i < subStorageCount; i++) + { + using StructuredStorage.Storage storage = rootStorage.OpenStorage($"Test{i}"); + } + } +#endif } [TestMethod] @@ -62,13 +85,13 @@ public void CreateDuplicateStorageThrowsException(Version version) public void DeleteSingleStorage(Version version) { using MemoryStream memoryStream = new(); - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) { rootStorage.CreateStorage("Test"); Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); } - using (var rootStorage = RootStorage.Open(memoryStream)) + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.LeaveOpen)) { rootStorage.Delete("Test"); Assert.AreEqual(0, rootStorage.EnumerateEntries().Count()); @@ -86,14 +109,14 @@ public void DeleteSingleStorage(Version version) public void DeleteRedBlackTreeChildLeaf(Version version) { using MemoryStream memoryStream = new(); - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) { rootStorage.CreateStorage("Test1"); rootStorage.CreateStorage("Test2"); Assert.AreEqual(2, rootStorage.EnumerateEntries().Count()); } - using (var rootStorage = RootStorage.Open(memoryStream)) + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.LeaveOpen)) { rootStorage.Delete("Test1"); Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); @@ -111,14 +134,14 @@ public void DeleteRedBlackTreeChildLeaf(Version version) public void DeleteRedBlackTreeSiblingLeaf(Version version) { using MemoryStream memoryStream = new(); - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) { rootStorage.CreateStorage("Test1"); rootStorage.CreateStorage("Test2"); Assert.AreEqual(2, rootStorage.EnumerateEntries().Count()); } - using (var rootStorage = RootStorage.Open(memoryStream)) + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.LeaveOpen)) { rootStorage.Delete("Test2"); Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); @@ -136,7 +159,7 @@ public void DeleteRedBlackTreeSiblingLeaf(Version version) public void DeleteRedBlackTreeSibling(Version version) { using MemoryStream memoryStream = new(); - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) { rootStorage.CreateStorage("Test1"); rootStorage.CreateStorage("Test2"); @@ -144,7 +167,7 @@ public void DeleteRedBlackTreeSibling(Version version) Assert.AreEqual(3, rootStorage.EnumerateEntries().Count()); } - using (var rootStorage = RootStorage.Open(memoryStream)) + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.LeaveOpen)) { rootStorage.Delete("Test2"); Assert.AreEqual(2, rootStorage.EnumerateEntries().Count()); @@ -162,7 +185,7 @@ public void DeleteRedBlackTreeSibling(Version version) public void DeleteStorageRecursively(Version version) { using MemoryStream memoryStream = new(); - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) { Storage storage = rootStorage.CreateStorage("Test"); Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); @@ -170,7 +193,7 @@ public void DeleteStorageRecursively(Version version) using CfbStream stream = storage.CreateStream("Test"); } - using (var rootStorage = RootStorage.Open(memoryStream)) + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.LeaveOpen)) { rootStorage.Delete("Test"); Assert.AreEqual(0, rootStorage.EnumerateEntries().Count()); @@ -188,7 +211,7 @@ public void DeleteStorageRecursively(Version version) public void DeleteStream(Version version) { using MemoryStream memoryStream = new(); - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) { rootStorage.CreateStream("Test"); Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); diff --git a/OpenMcdf3.Tests/StreamTests.cs b/OpenMcdf3.Tests/StreamTests.cs index 717444a5..7a0f030f 100644 --- a/OpenMcdf3.Tests/StreamTests.cs +++ b/OpenMcdf3.Tests/StreamTests.cs @@ -179,7 +179,7 @@ public void WriteThenRead(Version version, int length) expectedBuffer[i] = (byte)i; using MemoryStream memoryStream = new(); - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) { using CfbStream stream = rootStorage.CreateStream("TestStream"); Assert.AreEqual(0, stream.Length); @@ -288,7 +288,7 @@ public void Modify(Version version, int length) expectedBuffer[i] = (byte)i; using MemoryStream memoryStream = new(); - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) { using CfbStream stream = rootStorage.CreateStream("TestStream1"); Assert.AreEqual(0, stream.Length); @@ -296,7 +296,7 @@ public void Modify(Version version, int length) stream.Write(expectedBuffer, 0, expectedBuffer.Length); } - using (var rootStorage = RootStorage.Open(memoryStream)) + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.LeaveOpen)) { using CfbStream stream = rootStorage.CreateStream("TestStream2"); Assert.AreEqual(0, stream.Length); @@ -364,7 +364,7 @@ public void ModifyCommit(Version version, int length) expectedBuffer[i] = (byte)i; using MemoryStream memoryStream = new(); - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) { using CfbStream stream = rootStorage.CreateStream("TestStream1"); Assert.AreEqual(0, stream.Length); @@ -372,7 +372,7 @@ public void ModifyCommit(Version version, int length) stream.Write(expectedBuffer, 0, expectedBuffer.Length); } - using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.Transacted)) + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.LeaveOpen | StorageModeFlags.Transacted)) { using CfbStream stream = rootStorage.CreateStream("TestStream2"); Assert.AreEqual(0, stream.Length); @@ -442,7 +442,7 @@ public void TransactedRead(Version version, int length) expectedBuffer[i] = (byte)i; using MemoryStream memoryStream = new(); - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) { using CfbStream stream = rootStorage.CreateStream("TestStream1"); Assert.AreEqual(0, stream.Length); @@ -495,7 +495,7 @@ public void ModifyRevert(Version version, int length) expectedBuffer[i] = (byte)i; using MemoryStream memoryStream = new(); - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) { using CfbStream stream = rootStorage.CreateStream("TestStream1"); Assert.AreEqual(0, stream.Length); @@ -503,7 +503,7 @@ public void ModifyRevert(Version version, int length) stream.Write(expectedBuffer, 0, expectedBuffer.Length); } - using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.Transacted)) + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.LeaveOpen | StorageModeFlags.Transacted)) { using CfbStream stream = rootStorage.CreateStream("TestStream2"); Assert.AreEqual(0, stream.Length); diff --git a/OpenMcdf3.sln b/OpenMcdf3.sln index 6d425491..7f045797 100644 --- a/OpenMcdf3.sln +++ b/OpenMcdf3.sln @@ -15,7 +15,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3.Benchmarks", "OpenMcdf3.Benchmarks\OpenMcdf3.Benchmarks.csproj", "{44C718AD-F7FE-4733-80A8-636E5E7E63F3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenMcdf3.Perf", "OpenMcdf3.Perf\OpenMcdf3.Perf.csproj", "{8167F453-A244-4FE2-9B33-A7B80B1B7AB1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3.Perf", "OpenMcdf3.Perf\OpenMcdf3.Perf.csproj", "{8167F453-A244-4FE2-9B33-A7B80B1B7AB1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructuredStorage", "StructuredStorage\StructuredStorage.csproj", "{D7861D73-B42C-403E-9B9E-F921BC70F0D3}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,6 +41,10 @@ Global {8167F453-A244-4FE2-9B33-A7B80B1B7AB1}.Debug|Any CPU.Build.0 = Debug|Any CPU {8167F453-A244-4FE2-9B33-A7B80B1B7AB1}.Release|Any CPU.ActiveCfg = Release|Any CPU {8167F453-A244-4FE2-9B33-A7B80B1B7AB1}.Release|Any CPU.Build.0 = Release|Any CPU + {D7861D73-B42C-403E-9B9E-F921BC70F0D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7861D73-B42C-403E-9B9E-F921BC70F0D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7861D73-B42C-403E-9B9E-F921BC70F0D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7861D73-B42C-403E-9B9E-F921BC70F0D3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index fcdd90c2..3d452d30 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -48,7 +48,7 @@ public static RootStorage Open(string fileName, FileMode mode, StorageModeFlags return Open(stream); } - public static RootStorage OpenRead(string fileName) + public static RootStorage OpenRead(string fileName, StorageModeFlags flags = StorageModeFlags.None) { FileStream stream = File.OpenRead(fileName); return Open(stream); diff --git a/StructuredStorage/LockBytes.cs b/StructuredStorage/LockBytes.cs new file mode 100644 index 00000000..a9688958 --- /dev/null +++ b/StructuredStorage/LockBytes.cs @@ -0,0 +1,50 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Com.StructuredStorage; + +namespace StructuredStorage; + +/// +/// Encapsulates ILockBytes over an HGlobal allocation. +/// +internal sealed class LockBytes : IDisposable +{ + readonly ILockBytes lockBytes; + private bool disposedValue; + + public LockBytes(int count) + { + IntPtr hGlobal = Marshal.AllocHGlobal(count); + HRESULT hr = PInvoke.CreateILockBytesOnHGlobal((HGLOBAL)hGlobal, true, out lockBytes); + hr.ThrowOnFailure(); + } + + public LockBytes(MemoryStream stream) + { + var hGlobal = (HGLOBAL)Marshal.AllocHGlobal((int)stream.Length); + Marshal.Copy(stream.GetBuffer(), 0, hGlobal, (int)stream.Length); + HRESULT hr = PInvoke.CreateILockBytesOnHGlobal(hGlobal, true, out lockBytes); + hr.ThrowOnFailure(); + } + + public void Dispose() + { + if (disposedValue) + return; + + int count = Marshal.ReleaseComObject(lockBytes); + Debug.Assert(count == 0); + + disposedValue = true; + GC.SuppressFinalize(this); + } + + ~LockBytes() + { + Dispose(); + } + + internal ILockBytes ILockBytes => lockBytes; +} diff --git a/StructuredStorage/NativeMethods.json b/StructuredStorage/NativeMethods.json new file mode 100644 index 00000000..66108bf1 --- /dev/null +++ b/StructuredStorage/NativeMethods.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "wideCharOnly": true, + "emitSingleFile": true, + "public": false +} diff --git a/StructuredStorage/NativeMethods.txt b/StructuredStorage/NativeMethods.txt new file mode 100644 index 00000000..831d6ca4 --- /dev/null +++ b/StructuredStorage/NativeMethods.txt @@ -0,0 +1,38 @@ +// Structured storage + +// Functions +CreateILockBytesOnHGlobal +PropVariantToVariant +ReadClassStg +ReadClassStm +SHCreateItemFromParsingName +SHCreateStreamOnFile +StgCreateDocfileOnILockBytes +StgCreateStorageEx +StgIsStorageFile +StgOpenStorage +StgOpenStorageEx +StgOpenStorageOnILockBytes +VariantClear +VariantToPropVariant +WriteClassStg +WriteClassStm + +// Interfaces +IEnumSTATPROPSETSTG +IEnumSTATPROPSTG +IEnumSTATSTG +ILockBytes +IPropertySetStorage +IPropertyStorage +IRootStorage +IStorage +IStream + +// Enumerations +PROPSETFLAG_* +STGTY + +// Constants +STG_E_* +PROPSETFLAG_* diff --git a/StructuredStorage/PropertySetStorage.cs b/StructuredStorage/PropertySetStorage.cs new file mode 100644 index 00000000..7c6df922 --- /dev/null +++ b/StructuredStorage/PropertySetStorage.cs @@ -0,0 +1,50 @@ +using Windows.Win32; +using Windows.Win32.System.Com.StructuredStorage; + +namespace StructuredStorage; + +/// +/// Wraps IPropertySetStorage. +/// +public sealed class PropertySetStorage +{ + /// + /// PROPSETFLAG constants. + /// + [Flags] +#pragma warning disable CA1008 + public enum Flags + { + Default = (int)PInvoke.PROPSETFLAG_DEFAULT, + NonSimple = (int)PInvoke.PROPSETFLAG_NONSIMPLE, + ANSI = (int)PInvoke.PROPSETFLAG_ANSI, + Unbuffered = (int)PInvoke.PROPSETFLAG_UNBUFFERED, + CaseSensitive = (int)PInvoke.PROPSETFLAG_CASE_SENSITIVE, + } +#pragma warning restore CA1008 + + private readonly IPropertySetStorage propSet; // Cast of IStorage does not need disposal + + internal PropertySetStorage(IStorage storage) + { + propSet = (IPropertySetStorage)storage; + } + + public PropertyStorage Create(Guid formatID, StorageModes mode) => Create(formatID, Flags.Default, mode, Guid.Empty); + + public PropertyStorage Create(Guid formatID, Flags flags = Flags.Default, StorageModes mode = StorageModes.ShareExclusive | StorageModes.AccessReadWrite) => Create(formatID, flags, mode, Guid.Empty); + + public unsafe PropertyStorage Create(Guid formatID, Flags flags, StorageModes mode, Guid classID) + { + propSet.Create(&formatID, &classID, (uint)flags, (uint)mode, out IPropertyStorage stg); + return new(stg); + } + + public unsafe PropertyStorage Open(Guid formatID, StorageModes mode = StorageModes.ShareExclusive | StorageModes.AccessReadWrite) + { + propSet.Open(&formatID, (uint)mode, out IPropertyStorage propStorage); + return new(propStorage); + } + + public unsafe void Remove(Guid formatID) => propSet.Delete(&formatID); +} diff --git a/StructuredStorage/PropertyStorage.cs b/StructuredStorage/PropertyStorage.cs new file mode 100644 index 00000000..d713aca0 --- /dev/null +++ b/StructuredStorage/PropertyStorage.cs @@ -0,0 +1,167 @@ +using System.Collections; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Com.StructuredStorage; + +namespace StructuredStorage; + +/// +/// Enumerates STATPROPSTG elements from a PropertyStorage. +/// +internal sealed class StatPropStgEnumerator : IEnumerator +{ + readonly IEnumSTATPROPSTG enumerator; + STATPROPSTG propStat; + + public STATPROPSTG Current => propStat; + + object IEnumerator.Current => propStat; + + public unsafe StatPropStgEnumerator(IPropertyStorage propertyStorage) + { + propertyStorage.Enum(out enumerator); + } + + public unsafe void Dispose() + { + FreeName(); + + Marshal.ReleaseComObject(enumerator); + } + + private unsafe void FreeName() + { + Marshal.FreeCoTaskMem((nint)propStat.lpwstrName.Value); + propStat.lpwstrName = null; + } + + public unsafe bool MoveNext() + { + FreeName(); + + fixed (STATPROPSTG* statPtr = &propStat) + { + uint fetched; + enumerator.Next(1, statPtr, &fetched); + return fetched > 0; + } + } + + public void Reset() + { + FreeName(); + + enumerator.Reset(); + } +} + +/// +/// Creates an enumerator for STATPROPSTG elements from a PropertyStorage. +/// +internal sealed class StatPropStgCollection : IEnumerable +{ + readonly IPropertyStorage propertyStorage; + + public StatPropStgCollection(IPropertyStorage propertyStorage) + { + this.propertyStorage = propertyStorage; + } + + public IEnumerator GetEnumerator() => new StatPropStgEnumerator(propertyStorage); + + IEnumerator IEnumerable.GetEnumerator() => new StatPropStgEnumerator(propertyStorage); +} + +/// +/// Wraps IPropertyStorage. +/// +public sealed class PropertyStorage : IDisposable +{ + private readonly IPropertyStorage propertyStorage; + private bool disposed; + + internal unsafe PropertyStorage(IPropertyStorage propertyStorage) + { + this.propertyStorage = propertyStorage; + StatPropStgCollection = new(propertyStorage); + + STATPROPSETSTG prop; + this.propertyStorage.Stat(&prop); + } + + #region IDisposable Members + + public void Dispose() + { + if (disposed) + return; + + int count = Marshal.ReleaseComObject(propertyStorage); + Debug.Assert(count == 0); + + disposed = true; + } + + #endregion + + internal StatPropStgCollection StatPropStgCollection { get; } + + public void Flush(CommitFlags flags = CommitFlags.Default) => propertyStorage.Commit((uint)flags); + + public unsafe void Remove(int propertyID) + { + PROPSPEC propspec = new() + { + ulKind = PROPSPEC_KIND.PRSPEC_PROPID, + Anonymous = new PROPSPEC._Anonymous_e__Union() + { + propid = (uint)propertyID, + }, + }; + propertyStorage.DeleteMultiple(1, &propspec); + } + + public void Revert() => propertyStorage.Revert(); + + public unsafe object? this[int propertyID] + { + get + { + PROPSPEC spec = PropVariantExtensions.CreatePropSpec(PROPSPEC_KIND.PRSPEC_PROPID, propertyID); + + var variants = new PROPVARIANT[1]; + propertyStorage.ReadMultiple(1, &spec, variants); + HRESULT hr = PInvoke.PropVariantToVariant(variants[0], out object variant); + hr.ThrowOnFailure(); + return variant; + } + + set + { + PROPSPEC spec = PropVariantExtensions.CreatePropSpec(PROPSPEC_KIND.PRSPEC_PROPID, propertyID); + + HRESULT hr = PInvoke.VariantToPropVariant(value, out PROPVARIANT pv); + hr.ThrowOnFailure(); + + PROPVARIANT[] pvs = [pv]; + propertyStorage.WriteMultiple(1, &spec, pvs, 2); + } + } +} + +static class PropVariantExtensions +{ + public static PROPSPEC CreatePropSpec(PROPSPEC_KIND kind, int propertyID) + { + return new PROPSPEC + { + ulKind = kind, + Anonymous = new PROPSPEC._Anonymous_e__Union + { + propid = (uint)propertyID, + }, + }; + } +} diff --git a/StructuredStorage/Storage.cs b/StructuredStorage/Storage.cs new file mode 100644 index 00000000..369df70b --- /dev/null +++ b/StructuredStorage/Storage.cs @@ -0,0 +1,370 @@ +using System.Collections; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Security; +using Windows.Win32.System.Com; +using Windows.Win32.System.Com.StructuredStorage; + +namespace StructuredStorage; + +#pragma warning disable CA1069 // Enums values should not be duplicated +#pragma warning disable CA1724 // Type names should not match namespaces +#pragma warning disable CA1028 // Enum storage should be Int32 +#pragma warning disable CA1008 // Enums should have zero value + +/// +/// STGC constants. +/// +[Flags] +public enum CommitFlags : uint +{ + Default = STGC.STGC_DEFAULT, + Overwrite = STGC.STGC_OVERWRITE, + OnlyIfCurrent = STGC.STGC_ONLYIFCURRENT, + DangerouslyCommitMerelyToDiskCache = STGC.STGC_DANGEROUSLYCOMMITMERELYTODISKCACHE, + Consolidate = STGC.STGC_CONSOLIDATE, +} + +/// +/// STGM constants. +/// +[Flags] +public enum StorageModes : uint +{ + FailIfThere = STGM.STGM_FAILIFTHERE, + Direct = STGM.STGM_DIRECT, + AccessRead = STGM.STGM_READ, + AccessWrite = STGM.STGM_WRITE, + AccessReadWrite = STGM.STGM_READWRITE, + ShareExclusive = STGM.STGM_SHARE_EXCLUSIVE, + ShareDenyWrite = STGM.STGM_SHARE_DENY_WRITE, + ShareDenyRead = STGM.STGM_SHARE_DENY_READ, + ShareDenyNone = STGM.STGM_SHARE_DENY_NONE, + Create = STGM.STGM_CREATE, + Transacted = STGM.STGM_TRANSACTED, + Convert = STGM.STGM_CONVERT, + Priority = STGM.STGM_PRIORITY, + NoScratch = STGM.STGM_NOSCRATCH, + NoSnapShot = STGM.STGM_NOSNAPSHOT, + DirectSWMR = STGM.STGM_DIRECT_SWMR, + DeleteOnRelease = STGM.STGM_DELETEONRELEASE, + ModeSimple = STGM.STGM_SIMPLE, +} + +/// +/// Enumerates STATSTG elements from a Storage. +/// +internal sealed class StatStgEnumerator : IEnumerator +{ + readonly IEnumSTATSTG enumerator; + STATSTG stat; + + public STATSTG Current => stat; + + object IEnumerator.Current => stat; + + public unsafe StatStgEnumerator(IStorage storage) + { + storage.EnumElements(0, null, 0, out enumerator); + } + + public unsafe void Dispose() + { + FreeName(); + + Marshal.ReleaseComObject(enumerator); + } + + private unsafe void FreeName() + { + Marshal.FreeCoTaskMem((nint)stat.pwcsName.Value); + stat.pwcsName = null; + } + + public unsafe bool MoveNext() + { + FreeName(); + + fixed (STATSTG* statPtr = &stat) + { + uint fetched; + enumerator.Next(1, statPtr, &fetched); + return fetched > 0; + } + } + + public void Reset() + { + FreeName(); + + enumerator.Reset(); + } +} + +/// +/// Creates an enumerator for STATSTG elements from a Storage. +/// +internal sealed class StatStgCollection : IEnumerable +{ + readonly IStorage storage; + + public StatStgCollection(IStorage storage) + { + this.storage = storage; + } + + public IEnumerator GetEnumerator() => new StatStgEnumerator(storage); + + IEnumerator IEnumerable.GetEnumerator() => new StatStgEnumerator(storage); +} + +/// +/// Wraps a COM structured storage object. +/// +public sealed class Storage : IDisposable +{ + static readonly Guid IStorageGuid = typeof(IStorage).GUID; + + static STGOPTIONS V3Options => new() + { + usVersion = 2, + reserved = 0, + ulSectorSize = 512, + pwcsTemplateFile = null, + }; + + static STGOPTIONS V4Options => new() + { + usVersion = 2, + reserved = 0, + ulSectorSize = 4096, + pwcsTemplateFile = null, + }; + + readonly IStorage storage; + readonly LockBytes? lockBytes; // Prevents garbage collection of in-memory storage + + public Storage? Parent { get; } + + public PropertySetStorage PropertySetStorage { get; } + + internal StatStgCollection StatStgCollection { get; } + + bool disposed; + + // Methods + internal Storage(IStorage storage, Storage? parent = null, LockBytes? lockBytes = null) + { + this.storage = storage; + Parent = parent; + this.lockBytes = lockBytes; + PropertySetStorage = new(storage); + StatStgCollection = new StatStgCollection(storage); + } + + public static unsafe Storage Create(string fileName, StorageModes modes = StorageModes.ShareExclusive | StorageModes.AccessReadWrite, bool v4 = true) + { + STGOPTIONS opts = v4 ? V4Options : V3Options; + HRESULT hr = PInvoke.StgCreateStorageEx(fileName, (STGM)modes, STGFMT.STGFMT_DOCFILE, 0, &opts, (PSECURITY_DESCRIPTOR)null, IStorageGuid, out void* ptr); + hr.ThrowOnFailure(); + + var iStorage = (IStorage)Marshal.GetObjectForIUnknown((nint)ptr); + Marshal.Release((nint)ptr); + return new(iStorage); + } + + public static Storage CreateInMemory(int capacity) + { + LockBytes lockBytes = new(capacity); + HRESULT hr = PInvoke.StgCreateDocfileOnILockBytes(lockBytes.ILockBytes, STGM.STGM_READWRITE | STGM.STGM_SHARE_EXCLUSIVE | STGM.STGM_CREATE, 0, out IStorage storage); + hr.ThrowOnFailure(); + return new(storage, null, lockBytes); + } + + public static unsafe Storage Open(MemoryStream stream, StorageModes modes = StorageModes.ShareExclusive | StorageModes.AccessReadWrite) + { + LockBytes lockBytes = new(stream); + HRESULT hr = PInvoke.StgOpenStorageOnILockBytes(lockBytes.ILockBytes, null, (STGM)modes, null, out IStorage storage); + hr.ThrowOnFailure(); + return new(storage); + } + + public static unsafe Storage Open(string fileName, StorageModes modes = StorageModes.ShareExclusive | StorageModes.AccessReadWrite) + { + STGOPTIONS opts = V4Options; + HRESULT hr = PInvoke.StgOpenStorageEx(fileName, (STGM)modes, STGFMT.STGFMT_DOCFILE, 0, &opts, (PSECURITY_DESCRIPTOR)null, IStorageGuid, out void* ptr); + if (hr == HRESULT.STG_E_FILENOTFOUND) + throw new FileNotFoundException(null, fileName); + if (hr == HRESULT.STG_E_FILEALREADYEXISTS) + hr = HRESULT.STG_E_DOCFILECORRUPT; + hr.ThrowOnFailure(); + + var iStorage = (IStorage)Marshal.GetObjectForIUnknown((nint)ptr); + Marshal.Release((nint)ptr); + return new(iStorage); + } + + #region IDisposable Members + + public void Dispose() + { + if (disposed) + return; + + int count = Marshal.ReleaseComObject(storage); + Debug.Assert(count == 0); + + lockBytes?.Dispose(); + + disposed = true; + } + + #endregion + + public unsafe Storage CreateStorage(string name, StorageModes flags = StorageModes.Create | StorageModes.ShareExclusive | StorageModes.AccessReadWrite) + { + ObjectDisposedException.ThrowIf(disposed, this); + + fixed (char* namePtr = name) + { + storage.CreateStorage(namePtr, (STGM)flags, 0, 0, out IStorage childStorage); + return new Storage(childStorage, this); + } + } + + public unsafe Stream CreateStream(string name, StorageModes flags = StorageModes.Create | StorageModes.ShareExclusive | StorageModes.AccessReadWrite) + { + ObjectDisposedException.ThrowIf(disposed, this); + + fixed (char* namePtr = name) + { + storage.CreateStream(namePtr, (STGM)flags, 0, 0, out IStream stm); + return new Stream(stm, this); + } + } + + internal StatStgEnumerator CreateStatStgEnumerator() => new(storage); + + public IEnumerable EnumerateEntries() + { + ObjectDisposedException.ThrowIf(disposed, this); + + using StatStgEnumerator enumerator = CreateStatStgEnumerator(); + while (enumerator.MoveNext()) + { + yield return enumerator.Current.pwcsName.ToString(); + } + } + + public void DestroyElement(string name) + { + ObjectDisposedException.ThrowIf(disposed, this); + + storage.DestroyElement(name); + } + + public void DestroyElementIfExists(string name) + { + ObjectDisposedException.ThrowIf(disposed, this); + + if (ContainsElement(name)) + storage.DestroyElement(name); + } + + public bool ContainsElement(string name) + { + ObjectDisposedException.ThrowIf(disposed, this); + + return StatStgCollection.Any(s => s.pwcsName.AsSpan().SequenceEqual(name)); + } + + public bool ContainsStream(string name) + { + ObjectDisposedException.ThrowIf(disposed, this); + + return StatStgCollection.Any(s => (STGTY)s.type == STGTY.STGTY_STREAM && s.pwcsName.AsSpan().SequenceEqual(name)); + } + + public void Commit(CommitFlags flags = CommitFlags.Default) + { + ObjectDisposedException.ThrowIf(disposed, this); + + storage.Commit((uint)flags); + } + + public void MoveElement(string name, Storage destination) => MoveElement(name, destination, name); + + public void MoveElement(string name, Storage destination, string newName) + { + ObjectDisposedException.ThrowIf(disposed, this); + + storage.MoveElementTo(name, destination.storage, newName, 0); + } + + public unsafe Storage OpenStorage(string name, StorageModes flags = StorageModes.AccessReadWrite | StorageModes.ShareExclusive) + { + ObjectDisposedException.ThrowIf(disposed, this); + + fixed (char* namePtr = name) + { + storage.OpenStorage(namePtr, null, (STGM)flags, null, 0, out IStorage childStorage); + return new Storage(childStorage, this); + } + } + + public unsafe Stream OpenStream(string name, StorageModes flags = StorageModes.AccessReadWrite | StorageModes.ShareExclusive) + { + ObjectDisposedException.ThrowIf(disposed, this); + + fixed (char* namePtr = name) + { + storage.OpenStream(namePtr, null, (STGM)flags, 0, out IStream iStream); + return new Stream(iStream, this); + } + } + + public Stream OpenOrCreateStream(string name, StorageModes flags = StorageModes.AccessReadWrite | StorageModes.ShareExclusive) + => ContainsStream(name) ? OpenStream(name, flags) : CreateStream(name, flags); + + public void Revert() + { + ObjectDisposedException.ThrowIf(disposed, this); + + storage.Revert(); + } + + public unsafe void SwitchToFile(string fileName) + { + ObjectDisposedException.ThrowIf(disposed, this); + + fixed (char* fileNamePtr = fileName) + { + if (storage is not IRootStorage rootStorage) + throw new InvalidOperationException("Not file storage"); + rootStorage.SwitchToFile(fileNamePtr); + } + } + + // Properties + public Guid Id + { + get + { + ObjectDisposedException.ThrowIf(disposed, this); + + HRESULT hr = PInvoke.ReadClassStg(storage, out Guid guid); + hr.ThrowOnFailure(); + return guid; + } + + set + { + ObjectDisposedException.ThrowIf(disposed, this); + + HRESULT hr = PInvoke.WriteClassStg(storage, value); + hr.ThrowOnFailure(); + } + } +} diff --git a/StructuredStorage/Stream.cs b/StructuredStorage/Stream.cs new file mode 100644 index 00000000..4b1df3f4 --- /dev/null +++ b/StructuredStorage/Stream.cs @@ -0,0 +1,185 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Com; + +namespace StructuredStorage; + +/// +/// Implements Stream on an COM IStream. +/// +public sealed class Stream : System.IO.Stream +{ + public Storage Parent { get; } + + readonly IStream stream; + bool disposed; + + internal Stream(IStream stream, Storage parent) + { + this.stream = stream; + Parent = parent; + + STGM mode = Stat.grfMode; + CanRead = mode.HasFlag(STGM.STGM_READWRITE) || !mode.HasFlag(STGM.STGM_WRITE); + CanWrite = mode.HasFlag(STGM.STGM_READWRITE) || mode.HasFlag(STGM.STGM_WRITE); + } + + protected override void Dispose(bool disposing) + { + if (disposed) + return; + + if (disposing) + { + Flush(); + + int count = Marshal.ReleaseComObject(stream); + Debug.Assert(count == 0); + } + + disposed = true; + + base.Dispose(disposing); + } + + public override void Flush() => Flush(CommitFlags.Default); + + public void Flush(CommitFlags flags) + { + ObjectDisposedException.ThrowIf(disposed, this); + + stream.Commit((STGC)flags); + } + + public override int Read(byte[] buffer, int offset, int count) + { + ObjectDisposedException.ThrowIf(disposed, this); + + Span slice = buffer.AsSpan(offset, count); + return Read(slice); + } + + public override unsafe int Read(Span buffer) + { + ObjectDisposedException.ThrowIf(disposed, this); + + fixed (byte* ptr = buffer) + { + uint read; + HRESULT hr = stream.Read(ptr, (uint)buffer.Length, &read); + hr.ThrowOnFailure(); + return (int)read; + } + } + + public void Revert() + { + ObjectDisposedException.ThrowIf(disposed, this); + + stream.Revert(); + } + + public override unsafe long Seek(long offset, SeekOrigin origin) + { + ObjectDisposedException.ThrowIf(disposed, this); + + ulong pos; + stream.Seek(offset, origin, &pos); + return (long)pos; + } + + public override void SetLength(long value) + { + ObjectDisposedException.ThrowIf(disposed, this); + + stream.SetSize((ulong)value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + ObjectDisposedException.ThrowIf(disposed, this); + + ReadOnlySpan slice = buffer.AsSpan(offset, count); + Write(slice); + } + + public override unsafe void Write(ReadOnlySpan buffer) + { + ObjectDisposedException.ThrowIf(disposed, this); + + fixed (byte* ptr = buffer) + { + uint written; + HRESULT result = stream.Write(ptr, (uint)buffer.Length, &written); + result.ThrowOnFailure(); + } + } + + // Properties + public override bool CanRead { get; } + + public override bool CanSeek => true; + + public override bool CanWrite { get; } + + public override long Length + { + get + { + ObjectDisposedException.ThrowIf(disposed, this); + + return (long)Stat.cbSize; + } + } + + public override unsafe long Position + { + get + { + ObjectDisposedException.ThrowIf(disposed, this); + + ulong pos; + stream.Seek(0L, SeekOrigin.Current, &pos); + return (long)pos; + } + + set + { + ObjectDisposedException.ThrowIf(disposed, this); + + stream.Seek(value, SeekOrigin.Begin, null); + } + } + + public Guid Id + { + get + { + ObjectDisposedException.ThrowIf(disposed, this); + + HRESULT hr = PInvoke.ReadClassStm(stream, out Guid guid); + hr.ThrowOnFailure(); + return guid; + } + + set + { + ObjectDisposedException.ThrowIf(disposed, this); + + HRESULT hr = PInvoke.WriteClassStm(stream, value); + hr.ThrowOnFailure(); + } + } + + internal unsafe STATSTG Stat + { + get + { + STATSTG stat; + stream.Stat(&stat, STATFLAG.STATFLAG_NONAME); + return stat; + } + } +} diff --git a/StructuredStorage/StructuredStorage.csproj b/StructuredStorage/StructuredStorage.csproj new file mode 100644 index 00000000..40393c66 --- /dev/null +++ b/StructuredStorage/StructuredStorage.csproj @@ -0,0 +1,18 @@ + + + + net8.0-windows + enable + enable + 12.0 + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + From 1e397634be70432cbab08ed7f1fdc2e61f0eb2f5 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Fri, 8 Nov 2024 13:33:47 +1300 Subject: [PATCH 080/134] Fix leave open flag --- OpenMcdf3/IOContext.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index 3c13816b..a6c61aee 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -14,6 +14,7 @@ enum IOContextFlags internal sealed class IOContext : IDisposable { readonly Stream stream; + readonly IOContextFlags contextFlags; readonly CfbBinaryWriter? writer; readonly TransactedStream? transactedStream; MiniFat? miniFat; @@ -77,6 +78,7 @@ public FatStream MiniStream public IOContext(Stream stream, Version version, IOContextFlags contextFlags = IOContextFlags.None) { this.stream = stream; + this.contextFlags = contextFlags; using CfbBinaryReader reader = new(stream); Header = contextFlags.HasFlag(IOContextFlags.Create) ? new(version) : reader.ReadHeader(); @@ -130,6 +132,9 @@ public void Dispose() Fat.Dispose(); writer?.Dispose(); Reader.Dispose(); + transactedStream?.Dispose(); + if (!contextFlags.HasFlag(IOContextFlags.LeaveOpen)) + stream.Dispose(); IsDisposed = true; } } From 089876e07ec460483b0b4a9de4213e37dcf44532 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Fri, 8 Nov 2024 14:33:43 +1300 Subject: [PATCH 081/134] Fix warnings --- OpenMcdf3/CfbBinaryReader.cs | 7 +- OpenMcdf3/CfbBinaryWriter.cs | 12 +- OpenMcdf3/CfbStream.cs | 2 +- OpenMcdf3/CompilerServices.cs | 9 -- OpenMcdf3/DirectoryEntryEnumerator.cs | 1 - OpenMcdf3/DirectoryTreeEnumerator.cs | 1 - OpenMcdf3/FatChainEnumerator.cs | 8 +- OpenMcdf3/FatStream.cs | 11 +- OpenMcdf3/Header.cs | 10 +- OpenMcdf3/MiniFatStream.cs | 2 +- OpenMcdf3/OpenMcdf3.csproj | 12 +- OpenMcdf3/StreamExtensions.cs | 6 +- OpenMcdf3/System/.editorconfig | 2 + OpenMcdf3/System/.globalconfig | 3 + OpenMcdf3/System/CompilerServices.cs | 5 + OpenMcdf3/System/Index.cs | 167 ++++++++++++++++++++ OpenMcdf3/System/NullableAttributes.cs | 201 +++++++++++++++++++++++++ OpenMcdf3/System/Range.cs | 128 ++++++++++++++++ OpenMcdf3/TransactedStream.cs | 4 +- 19 files changed, 549 insertions(+), 42 deletions(-) delete mode 100644 OpenMcdf3/CompilerServices.cs create mode 100644 OpenMcdf3/System/.editorconfig create mode 100644 OpenMcdf3/System/.globalconfig create mode 100644 OpenMcdf3/System/CompilerServices.cs create mode 100644 OpenMcdf3/System/Index.cs create mode 100644 OpenMcdf3/System/NullableAttributes.cs create mode 100644 OpenMcdf3/System/Range.cs diff --git a/OpenMcdf3/CfbBinaryReader.cs b/OpenMcdf3/CfbBinaryReader.cs index 51cbf2ae..5ffdb220 100644 --- a/OpenMcdf3/CfbBinaryReader.cs +++ b/OpenMcdf3/CfbBinaryReader.cs @@ -1,5 +1,8 @@ -using System.Buffers; -using System.Text; +using System.Text; + +#if NETSTANDARD2_0 +using System.Buffers; +#endif namespace OpenMcdf3; diff --git a/OpenMcdf3/CfbBinaryWriter.cs b/OpenMcdf3/CfbBinaryWriter.cs index 5ff8c4a6..6b4f9ba4 100644 --- a/OpenMcdf3/CfbBinaryWriter.cs +++ b/OpenMcdf3/CfbBinaryWriter.cs @@ -7,8 +7,6 @@ namespace OpenMcdf3; /// internal sealed class CfbBinaryWriter : BinaryWriter { - readonly byte[] buffer = new byte[DirectoryEntry.NameFieldLength]; - public CfbBinaryWriter(Stream input) : base(input, Encoding.Unicode, true) { @@ -20,7 +18,7 @@ public long Position set => BaseStream.Position = value; } -#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) public override void Write(ReadOnlySpan buffer) => BaseStream.Write(buffer); @@ -28,13 +26,13 @@ public long Position public void Write(in Guid value) { -#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER +#if NETSTANDARD2_0 || NETFRAMEWORK + byte[] bytes = value.ToByteArray(); + Write(bytes); +#else Span localBuffer = stackalloc byte[16]; value.TryWriteBytes(localBuffer); Write(localBuffer); -#else - byte[] bytes = value.ToByteArray(); - Write(bytes); #endif } diff --git a/OpenMcdf3/CfbStream.cs b/OpenMcdf3/CfbStream.cs index 933ef167..f030356b 100644 --- a/OpenMcdf3/CfbStream.cs +++ b/OpenMcdf3/CfbStream.cs @@ -94,7 +94,7 @@ public override void Write(byte[] buffer, int offset, int count) stream.Write(buffer, offset, count); } -#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) public override int Read(Span buffer) => stream.Read(buffer); diff --git a/OpenMcdf3/CompilerServices.cs b/OpenMcdf3/CompilerServices.cs deleted file mode 100644 index 81332ada..00000000 --- a/OpenMcdf3/CompilerServices.cs +++ /dev/null @@ -1,9 +0,0 @@ -#if NETSTANDARD2_0 || NETSTANDARD2_1 - -namespace System.Runtime.CompilerServices; - -internal class IsExternalInit -{ -} - -#endif \ No newline at end of file diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index 05880a6e..fe979d06 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -1,5 +1,4 @@ using System.Collections; -using System.Diagnostics; namespace OpenMcdf3; diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index 91b93b9d..e11a4898 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -1,5 +1,4 @@ using System.Collections; -using System.Diagnostics; namespace OpenMcdf3; diff --git a/OpenMcdf3/FatChainEnumerator.cs b/OpenMcdf3/FatChainEnumerator.cs index f97128a3..beb09245 100644 --- a/OpenMcdf3/FatChainEnumerator.cs +++ b/OpenMcdf3/FatChainEnumerator.cs @@ -29,8 +29,6 @@ public void Dispose() fatEnumerator.Dispose(); } - public uint StartId => startId; - public Sector CurrentSector => new(Current.Value, ioContext.SectorSize); /// @@ -141,7 +139,7 @@ public uint Extend() /// /// Returns the ID of the first sector in the chain. /// - public void Extend(uint requiredChainLength) + public uint Extend(uint requiredChainLength) { uint chainLength = (uint)GetLength(); if (chainLength >= requiredChainLength) @@ -168,6 +166,7 @@ public void Extend(uint requiredChainLength) } length = requiredChainLength; + return startId; } public uint ExtendFrom(uint hintId) @@ -189,7 +188,7 @@ public uint ExtendFrom(uint hintId) return id; } - public void Shrink(uint requiredChainLength) + public uint Shrink(uint requiredChainLength) { uint chainLength = (uint)GetLength(); if (chainLength <= requiredChainLength) @@ -225,6 +224,7 @@ public void Shrink(uint requiredChainLength) #endif length = requiredChainLength; + return startId; } /// diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index 05fe2591..652b76b4 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -1,6 +1,4 @@ -using System.IO; - -namespace OpenMcdf3; +namespace OpenMcdf3; /// /// Provides a for a stream object in a compound file./> @@ -152,11 +150,10 @@ public override void SetLength(long value) uint requiredChainLength = (uint)((value + ioContext.SectorSize - 1) / ioContext.SectorSize); if (value > ChainCapacity) - chain.Extend(requiredChainLength); + DirectoryEntry.StartSectorId = chain.Extend(requiredChainLength); else if (value <= ChainCapacity - ioContext.SectorSize) - chain.Shrink(requiredChainLength); + DirectoryEntry.StartSectorId = chain.Shrink(requiredChainLength); - DirectoryEntry.StartSectorId = chain.StartId; DirectoryEntry.StreamLength = value; isDirty = true; } @@ -206,7 +203,7 @@ public override void Write(byte[] buffer, int offset, int count) throw new InvalidOperationException($"End of FAT chain was reached"); } -#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) public override int ReadByte() => this.ReadByteCore(); diff --git a/OpenMcdf3/Header.cs b/OpenMcdf3/Header.cs index 7225028f..3c99d722 100644 --- a/OpenMcdf3/Header.cs +++ b/OpenMcdf3/Header.cs @@ -1,5 +1,4 @@ - -namespace OpenMcdf3; +namespace OpenMcdf3; /// /// The structure at the beginning of a compound file. @@ -160,5 +159,12 @@ public bool Equals(Header? other) && Difat.SequenceEqual(other.Difat); } + public override int GetHashCode() + { + return HashCode.Combine( + HashCode.Combine(CLSID, MinorVersion, MajorVersion, SectorShift, DirectorySectorCount, FatSectorCount, FirstDirectorySectorId, TransactionSignature), + HashCode.Combine(FirstMiniFatSectorId, MiniFatSectorCount, FirstDifatSectorId, DifatSectorCount, Difat)); + } + public override string ToString() => $"MajorVersion: {MajorVersion}, MinorVersion: {MinorVersion}, FirstDirectorySectorId: {FirstDirectorySectorId}, FirstMiniFatSectorId: {FirstMiniFatSectorId}"; } diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index cea8cdcf..b90ee021 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -195,7 +195,7 @@ public override void Write(byte[] buffer, int offset, int count) throw new InvalidOperationException($"End of mini FAT chain was reached."); } -#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) public override int ReadByte() => this.ReadByteCore(); diff --git a/OpenMcdf3/OpenMcdf3.csproj b/OpenMcdf3/OpenMcdf3.csproj index 48b852fb..884c3d20 100644 --- a/OpenMcdf3/OpenMcdf3.csproj +++ b/OpenMcdf3/OpenMcdf3.csproj @@ -2,14 +2,20 @@ netstandard2.0;net8.0 - 11.0 + 12.0 enable enable true - - + + + + + + + + diff --git a/OpenMcdf3/StreamExtensions.cs b/OpenMcdf3/StreamExtensions.cs index ec600f63..5d904c00 100644 --- a/OpenMcdf3/StreamExtensions.cs +++ b/OpenMcdf3/StreamExtensions.cs @@ -1,4 +1,6 @@ -using System.Buffers; +#if !NET7_0_OR_GREATER +using System.Buffers; +#endif namespace OpenMcdf3; @@ -38,7 +40,7 @@ public static void ReadExactly(this Stream stream, byte[] buffer, int offset, in #endif -#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) public static int ReadByteCore(this Stream stream) { diff --git a/OpenMcdf3/System/.editorconfig b/OpenMcdf3/System/.editorconfig new file mode 100644 index 00000000..e4aabe9b --- /dev/null +++ b/OpenMcdf3/System/.editorconfig @@ -0,0 +1,2 @@ +[*.cs] +dotnet_analyzer_diagnostic.severity = none \ No newline at end of file diff --git a/OpenMcdf3/System/.globalconfig b/OpenMcdf3/System/.globalconfig new file mode 100644 index 00000000..54c4c5b9 --- /dev/null +++ b/OpenMcdf3/System/.globalconfig @@ -0,0 +1,3 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories + +dotnet_analyzer_diagnostic.severity = none \ No newline at end of file diff --git a/OpenMcdf3/System/CompilerServices.cs b/OpenMcdf3/System/CompilerServices.cs new file mode 100644 index 00000000..dfcd73bb --- /dev/null +++ b/OpenMcdf3/System/CompilerServices.cs @@ -0,0 +1,5 @@ +namespace System.Runtime.CompilerServices; + +internal class IsExternalInit +{ +} diff --git a/OpenMcdf3/System/Index.cs b/OpenMcdf3/System/Index.cs new file mode 100644 index 00000000..52a556be --- /dev/null +++ b/OpenMcdf3/System/Index.cs @@ -0,0 +1,167 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace System +{ + /// Represent a type can be used to index a collection either from the start or the end. + /// + /// Index is used by the C# compiler to support the new index syntax + /// + /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; + /// int lastElement = someArray[^1]; // lastElement = 5 + /// + /// +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + readonly struct Index : IEquatable + { + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Index(int value, bool fromEnd = false) + { + if (value < 0) + { + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + if (fromEnd) + _value = ~value; + else + _value = value; + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + _value = value; + } + + /// Create an Index pointing at first element. + public static Index Start => new Index(0); + + /// Create an Index pointing at beyond last element. + public static Index End => new Index(~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromStart(int value) + { + if (value < 0) + { + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + return new Index(value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromEnd(int value) + { + if (value < 0) + { + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + return new Index(~value); + } + + /// Returns the index value. + public int Value + { + get + { + if (_value < 0) + return ~_value; + else + return _value; + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int length) + { + int offset = _value; + if (IsFromEnd) + { + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; + } + return offset; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals([NotNullWhen(true)] object? value) => value is Index && _value == ((Index)value)._value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(Index other) => _value == other._value; + + /// Returns the hash code for this instance. + public override int GetHashCode() => _value; + + /// Converts integer number to an Index. + public static implicit operator Index(int value) => FromStart(value); + + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString() + { + if (IsFromEnd) + return ToStringFromEnd(); + + return ((uint)Value).ToString(); + } + + private static void ThrowValueArgumentOutOfRange_NeedNonNegNumException() + { +#if SYSTEM_PRIVATE_CORELIB + throw new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_NeedNonNegNum); +#else + throw new ArgumentOutOfRangeException("value", "value must be non-negative"); +#endif + } + + private string ToStringFromEnd() + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + Span span = stackalloc char[11]; // 1 for ^ and 10 for longest possible uint value + bool formatted = ((uint)Value).TryFormat(span.Slice(1), out int charsWritten); + Debug.Assert(formatted); + span[0] = '^'; + return new string(span.Slice(0, charsWritten + 1)); +#else + return '^' + Value.ToString(); +#endif + } + } +} \ No newline at end of file diff --git a/OpenMcdf3/System/NullableAttributes.cs b/OpenMcdf3/System/NullableAttributes.cs new file mode 100644 index 00000000..08417f13 --- /dev/null +++ b/OpenMcdf3/System/NullableAttributes.cs @@ -0,0 +1,201 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ +#if !NETSTANDARD2_1 + /// Specifies that null is allowed as an input even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class AllowNullAttribute : Attribute + { } + + /// Specifies that null is disallowed as an input even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class DisallowNullAttribute : Attribute + { } + + /// Specifies that an output may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class MaybeNullAttribute : Attribute + { } + + /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class NotNullAttribute : Attribute + { } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class MaybeNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class NotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that the output will be non-null if the named parameter is non-null. + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class NotNullIfNotNullAttribute : Attribute + { + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + + /// Gets the associated parameter name. + public string ParameterName { get; } + } + + /// Applied to a method that will never return under any circumstance. + [AttributeUsage(AttributeTargets.Method, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class DoesNotReturnAttribute : Attribute + { } + + /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class DoesNotReturnIfAttribute : Attribute + { + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + + /// Gets the condition parameter value. + public bool ParameterValue { get; } + } +#endif + + /// Specifies that the method or property will ensure that the listed field and property members have not-null values. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class MemberNotNullAttribute : Attribute + { + /// Initializes the attribute with a field or property member. + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullAttribute(string member) => Members = [member]; + + /// Initializes the attribute with the list of field and property members. + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullAttribute(params string[] members) => Members = members; + + /// Gets field or property member names. + public string[] Members { get; } + } + + /// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class MemberNotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// The return value condition. If the method returns this value, the associated field or property member will not be null. + /// + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = [member]; + } + + /// Initializes the attribute with the specified return value condition and list of field and property members. + /// + /// The return value condition. If the method returns this value, the associated field and property members will not be null. + /// + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + + /// Gets the return value condition. + public bool ReturnValue { get; } + + /// Gets field or property member names. + public string[] Members { get; } + } +} \ No newline at end of file diff --git a/OpenMcdf3/System/Range.cs b/OpenMcdf3/System/Range.cs new file mode 100644 index 00000000..8967a17a --- /dev/null +++ b/OpenMcdf3/System/Range.cs @@ -0,0 +1,128 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +#if NETSTANDARD2_0 || NETFRAMEWORK +#endif + +namespace System +{ + /// Represent a range has start and end indexes. + /// + /// Range is used by the C# compiler to support the range syntax. + /// + /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; + /// int[] subArray1 = someArray[0..2]; // { 1, 2 } + /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } + /// + /// +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + readonly struct Range : IEquatable + { + /// Represent the inclusive start index of the Range. + public Index Start { get; } + + /// Represent the exclusive end index of the Range. + public Index End { get; } + + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range(Index start, Index end) + { + Start = start; + End = end; + } + + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals([NotNullWhen(true)] object? value) => + value is Range r && + r.Start.Equals(Start) && + r.End.Equals(End); + + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); + + /// Returns the hash code for this instance. + public override int GetHashCode() + { + return HashCode.Combine(Start.GetHashCode(), End.GetHashCode()); + } + + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString() + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + Span span = stackalloc char[2 + (2 * 11)]; // 2 for "..", then for each index 1 for '^' and 10 for longest possible uint + int pos = 0; + + if (Start.IsFromEnd) + { + span[0] = '^'; + pos = 1; + } + bool formatted = ((uint)Start.Value).TryFormat(span.Slice(pos), out int charsWritten); + Debug.Assert(formatted); + pos += charsWritten; + + span[pos++] = '.'; + span[pos++] = '.'; + + if (End.IsFromEnd) + { + span[pos++] = '^'; + } + formatted = ((uint)End.Value).TryFormat(span.Slice(pos), out charsWritten); + Debug.Assert(formatted); + pos += charsWritten; + + return new string(span.Slice(0, pos)); +#else + return Start.ToString() + ".." + End.ToString(); +#endif + } + + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt(Index start) => new Range(start, Index.End); + + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt(Index end) => new Range(Index.Start, end); + + /// Create a Range object starting from first element to the end. + public static Range All => new Range(Index.Start, Index.End); + + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public (int Offset, int Length) GetOffsetAndLength(int length) + { + int start = Start.GetOffset(length); + int end = End.GetOffset(length); + + if ((uint)end > (uint)length || (uint)start > (uint)end) + { + ThrowArgumentOutOfRangeException(); + } + + return (start, end - start); + } + + private static void ThrowArgumentOutOfRangeException() + { + throw new ArgumentOutOfRangeException("length"); + } + } +} \ No newline at end of file diff --git a/OpenMcdf3/TransactedStream.cs b/OpenMcdf3/TransactedStream.cs index 2c055837..b5bdfbc6 100644 --- a/OpenMcdf3/TransactedStream.cs +++ b/OpenMcdf3/TransactedStream.cs @@ -130,7 +130,7 @@ public void Revert() dirtySectorPositions.Clear(); } -#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_0_OR_GREATER +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) public override int ReadByte() => this.ReadByteCore(); @@ -141,7 +141,7 @@ public override int Read(Span buffer) int localCount = Math.Min(buffer.Length, remainingFromSector); Debug.Assert(localCount == buffer.Length); - Span slice = buffer.Slice(0, localCount); + Span slice = buffer[..localCount]; int read; if (dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) { From 760bbc46482a972e7dfd62fd9e0aea032e8c5b71 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Fri, 8 Nov 2024 15:15:49 +1300 Subject: [PATCH 082/134] Improve benchmarks --- OpenMcdf3.Benchmarks/InMemory.cs | 86 ------------------- OpenMcdf3.Benchmarks/MemoryStreamIO.cs | 60 +++++++++++++ .../OpenMcdf3.Benchmarks.csproj | 3 +- OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs | 25 ++++++ .../StructuredStorageBenchmarks.cs | 26 ++++++ StructuredStorage/Storage.cs | 2 + 6 files changed, 115 insertions(+), 87 deletions(-) delete mode 100644 OpenMcdf3.Benchmarks/InMemory.cs create mode 100644 OpenMcdf3.Benchmarks/MemoryStreamIO.cs create mode 100644 OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs create mode 100644 OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs diff --git a/OpenMcdf3.Benchmarks/InMemory.cs b/OpenMcdf3.Benchmarks/InMemory.cs deleted file mode 100644 index 209088ab..00000000 --- a/OpenMcdf3.Benchmarks/InMemory.cs +++ /dev/null @@ -1,86 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Order; - -namespace OpenMcdf3.Benchmark; - -[ShortRunJob] -[CsvExporter] -[HtmlExporter] -[MarkdownExporter] -//[DryCoreJob] // I always forget this attribute, so please leave it commented out -[MemoryDiagnoser] -[Orderer(SummaryOrderPolicy.FastestToSlowest)] -public class InMemory : IDisposable -{ - bool inMemory = true; - private const int Kb = 1024; - private const int Mb = Kb * Kb; - private const string storageName = "MyStorage"; - private const string streamName = "MyStream"; - - private Stream? readStream; - private Stream? writeStream; - - private byte[] buffer = Array.Empty(); - - [Params(512, Mb /*Kb, 4 * Kb, 128 * Kb, 256 * Kb, 512 * Kb,*/)] - public int BufferSize { get; set; } - - [Params(Mb /*, 8 * Mb, 64 * Mb, 128 * Mb*/)] - public int TotalStreamSize { get; set; } - - public void Dispose() - { - readStream?.Dispose(); - writeStream?.Dispose(); - } - - [GlobalSetup] - public void GlobalSetup() - { - buffer = new byte[BufferSize]; - readStream = inMemory ? new MemoryStream(2 * TotalStreamSize) : File.Create(Path.GetTempFileName()); - writeStream = inMemory ? new MemoryStream(2 * TotalStreamSize) : File.Create(Path.GetTempFileName()); - - using var storage = RootStorage.Create(readStream, Version.V3, StorageModeFlags.LeaveOpen); - using CfbStream stream = storage.CreateStream(streamName); - - int iterationCount = TotalStreamSize / BufferSize; - for (int iteration = 0; iteration < iterationCount; ++iteration) - stream.Write(buffer); - } - - [Benchmark] - public void Read() - { - using var compoundFile = RootStorage.Open(readStream); - using CfbStream cfStream = compoundFile.OpenStream(streamName); - long streamSize = cfStream.Length; - long position = 0L; - while (position < streamSize) - { - int read = cfStream.Read(buffer, 0, buffer.Length); - if (read <= 0) - throw new EndOfStreamException(); - position += read; - } - } - - [Benchmark] - public void Write() => WriteCore(StorageModeFlags.None); - - [Benchmark] - public void WriteTransacted() => WriteCore(StorageModeFlags.Transacted); - - void WriteCore(StorageModeFlags flags) - { - using var storage = RootStorage.Create(writeStream, Version.V3, flags); - Storage subStorage = storage.CreateStorage(storageName); - CfbStream stream = subStorage.CreateStream(streamName + 0); - - while (stream.Length < TotalStreamSize) - { - stream.Write(buffer, 0, buffer.Length); - } - } -} diff --git a/OpenMcdf3.Benchmarks/MemoryStreamIO.cs b/OpenMcdf3.Benchmarks/MemoryStreamIO.cs new file mode 100644 index 00000000..a7a5d7cc --- /dev/null +++ b/OpenMcdf3.Benchmarks/MemoryStreamIO.cs @@ -0,0 +1,60 @@ +using BenchmarkDotNet.Attributes; +using OpenMcdf3.Benchmarks; + +namespace OpenMcdf3.Benchmark; + +[ShortRunJob] +[MemoryDiagnoser] +public class MemoryStreamIO : IDisposable +{ + const Version version = Version.V3; + + private MemoryStream? readStream; + private MemoryStream? writeStream; + + private byte[] buffer = Array.Empty(); + + [Params(512, 1024 * 1024)] + public int BufferSize { get; set; } + + [Params(1024 * 1024)] + public int StreamLength { get; set; } + + public void Dispose() + { + readStream?.Dispose(); + writeStream?.Dispose(); + } + + [GlobalSetup] + public void GlobalSetup() + { + buffer = new byte[BufferSize]; + readStream = new MemoryStream(2 * StreamLength); + writeStream = new MemoryStream(2 * StreamLength); + + using var storage = RootStorage.Create(readStream, version, StorageModeFlags.LeaveOpen); + using CfbStream stream = storage.CreateStream("Test"); + + int iterationCount = StreamLength / BufferSize; + for (int iteration = 0; iteration < iterationCount; ++iteration) + stream.Write(buffer); + } + + [Benchmark] + public void Read() => OpenMcdfBenchmarks.ReadStream(readStream!, buffer); + + [Benchmark] + public void Write() => OpenMcdfBenchmarks.WriteStream(writeStream!, version, StorageModeFlags.LeaveOpen, buffer, StreamLength); + + [Benchmark] + public void WriteTransacted() => OpenMcdfBenchmarks.WriteStream(writeStream!, version, StorageModeFlags.LeaveOpen | StorageModeFlags.Transacted, buffer, StreamLength); + +#if WINDOWS + [Benchmark] + public void ReadStructuredStorage() => StructuredStorageBenchmarks.ReadStream(readStream!, buffer); + + [Benchmark] + public void WriteStructuredStorage() => StructuredStorageBenchmarks.WriteInMemory(version, StorageModeFlags.LeaveOpen, buffer, StreamLength); +#endif +} diff --git a/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj b/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj index 209959c3..b8f832e4 100644 --- a/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj +++ b/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj @@ -1,7 +1,7 @@  - net8.0 + net8.0-windows 11.0 Exe enable @@ -14,6 +14,7 @@ + diff --git a/OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs b/OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs new file mode 100644 index 00000000..da4bd78c --- /dev/null +++ b/OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs @@ -0,0 +1,25 @@ +namespace OpenMcdf3.Benchmarks; + +internal static class OpenMcdfBenchmarks +{ + public static void ReadStream(Stream stream, byte[] buffer) + { + using var storage = RootStorage.Open(stream, StorageModeFlags.LeaveOpen); + using CfbStream cfbStream = storage.OpenStream("Test"); + while (cfbStream.Position < cfbStream.Length) + { + int read = cfbStream.Read(buffer, 0, buffer.Length); + if (read <= 0) + throw new EndOfStreamException(); + } + } + + public static void WriteStream(Stream stream, Version version, StorageModeFlags flags, byte[] buffer, long streamLength) + { + using var storage = RootStorage.Create(stream, version, flags); + using CfbStream cfbStream = storage.CreateStream("Test"); + + while (stream.Length < streamLength) + stream.Write(buffer, 0, buffer.Length); + } +} diff --git a/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs b/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs new file mode 100644 index 00000000..0ad07e0c --- /dev/null +++ b/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs @@ -0,0 +1,26 @@ +namespace OpenMcdf3.Benchmarks; + +internal class StructuredStorageBenchmarks +{ + public static void ReadStream(MemoryStream stream, byte[] buffer) + { + using var storage = StructuredStorage.Storage.Open(stream); + using StructuredStorage.Stream storageStream = storage.OpenStream("Test"); + + while (storageStream.Position < storageStream.Length) + { + int read = stream.Read(buffer, 0, buffer.Length); + if (read <= 0) + throw new EndOfStreamException(); + } + } + + public static void WriteInMemory(Version version, StorageModeFlags flags, byte[] buffer, long streamLength) + { + using var storage = StructuredStorage.Storage.CreateInMemory((int)streamLength * 2); + using StructuredStorage.Stream stream = storage.CreateStream("Test"); + + while (stream.Length < streamLength) + stream.Write(buffer, 0, buffer.Length); + } +} diff --git a/StructuredStorage/Storage.cs b/StructuredStorage/Storage.cs index 369df70b..ee9ccfa7 100644 --- a/StructuredStorage/Storage.cs +++ b/StructuredStorage/Storage.cs @@ -185,6 +185,8 @@ public static Storage CreateInMemory(int capacity) public static unsafe Storage Open(MemoryStream stream, StorageModes modes = StorageModes.ShareExclusive | StorageModes.AccessReadWrite) { + stream.Position = 0; + LockBytes lockBytes = new(stream); HRESULT hr = PInvoke.StgOpenStorageOnILockBytes(lockBytes.ILockBytes, null, (STGM)modes, null, out IStorage storage); hr.ThrowOnFailure(); From a1e5bdcfa3f7896dc0b8c6ad9bb70cf7b83436a9 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Fri, 8 Nov 2024 16:27:46 +1300 Subject: [PATCH 083/134] Fix structured storage empty writes --- StructuredStorage/Stream.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/StructuredStorage/Stream.cs b/StructuredStorage/Stream.cs index 4b1df3f4..7495ebd1 100644 --- a/StructuredStorage/Stream.cs +++ b/StructuredStorage/Stream.cs @@ -109,6 +109,9 @@ public override unsafe void Write(ReadOnlySpan buffer) { ObjectDisposedException.ThrowIf(disposed, this); + if (buffer.Length == 0) + return; + fixed (byte* ptr = buffer) { uint written; From 47d7659410efa150358f60dd2c17d1d2715916e1 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Fri, 8 Nov 2024 16:28:09 +1300 Subject: [PATCH 084/134] Add reference write read test --- OpenMcdf3.Tests/StreamTests.cs | 78 +++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/OpenMcdf3.Tests/StreamTests.cs b/OpenMcdf3.Tests/StreamTests.cs index 7a0f030f..d9237544 100644 --- a/OpenMcdf3.Tests/StreamTests.cs +++ b/OpenMcdf3.Tests/StreamTests.cs @@ -187,17 +187,91 @@ public void WriteThenRead(Version version, int length) stream.Write(expectedBuffer, 0, expectedBuffer.Length); } - using (var rootStorage = RootStorage.Open(memoryStream)) + byte[] actualBuffer = new byte[length]; + using (var rootStorage = RootStorage.Open(memoryStream, StorageModeFlags.LeaveOpen)) + { + using CfbStream stream = rootStorage.OpenStream("TestStream"); + rootStorage.Validate(); + Assert.AreEqual(length, stream.Length); + + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } + +#if WINDOWS + using (var rootStorage = StructuredStorage.Storage.Open(memoryStream)) + { + IEnumerable entries = rootStorage.EnumerateEntries(); + using StructuredStorage.Stream stream = rootStorage.OpenStream("TestStream"); + Assert.AreEqual(length, stream.Length); + + stream.ReadExactly(actualBuffer); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + } +#endif + } + +#if WINDOWS + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 63)] + [DataRow(Version.V3, 64)] // Mini-stream sector size + [DataRow(Version.V3, 65)] + [DataRow(Version.V3, 511)] + [DataRow(Version.V3, 512)] // Multiple stream sectors + [DataRow(Version.V3, 513)] + [DataRow(Version.V3, 4095)] + [DataRow(Version.V3, 4096)] + [DataRow(Version.V3, 4097)] + [DataRow(Version.V3, 128 * 512)] // Multiple FAT sectors + [DataRow(Version.V3, 1024 * 4096)] // Multiple FAT sectors + [DataRow(Version.V3, 7087616)] // First DIFAT chain + [DataRow(Version.V3, 2 * 7087616)] // Long DIFAT chain + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 63)] + [DataRow(Version.V4, 64)] // Mini-stream sector size + [DataRow(Version.V4, 65)] + [DataRow(Version.V4, 511)] + [DataRow(Version.V4, 512)] + [DataRow(Version.V4, 513)] + [DataRow(Version.V4, 4095)] + [DataRow(Version.V4, 4096)] // Multiple stream sectors + [DataRow(Version.V4, 4097)] + [DataRow(Version.V4, 1024 * 4096)] // Multiple FAT sectors (1024 * 4096) + [DataRow(Version.V4, 7087616 * 4)] // First DIFAT chain + [DataRow(Version.V4, 2 * 7087616 * 4)] // Long DIFAT chain + public void StructuredStorageWriteThenRead(Version version, int length) + { + // Fill with bytes equal to their position modulo 256 + byte[] expectedBuffer = new byte[length]; + for (int i = 0; i < length; i++) + expectedBuffer[i] = (byte)i; + + using MemoryStream memoryStream = new(); + string fileName = Path.GetTempFileName(); + File.Delete(fileName); + + using (var rootStorage = StructuredStorage.Storage.Create(fileName, StructuredStorage.StorageModes.AccessReadWrite | StructuredStorage.StorageModes.ShareExclusive, version == Version.V4)) + { + IEnumerable entries = rootStorage.EnumerateEntries(); + using StructuredStorage.Stream stream = rootStorage.CreateStream("TestStream"); + + stream.Write(expectedBuffer, 0, expectedBuffer.Length); + Assert.AreEqual(length, stream.Length); + } + + byte[] actualBuffer = new byte[length]; + using (var rootStorage = RootStorage.OpenRead(fileName)) { using CfbStream stream = rootStorage.OpenStream("TestStream"); rootStorage.Validate(); Assert.AreEqual(length, stream.Length); - byte[] actualBuffer = new byte[length]; stream.ReadExactly(actualBuffer); CollectionAssert.AreEqual(expectedBuffer, actualBuffer); } } +#endif [TestMethod] [DataRow(Version.V3, 0)] From 7f3e51bd99a6ea624575f91bb70888857032712d Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Fri, 8 Nov 2024 22:12:38 +1300 Subject: [PATCH 085/134] Fix reading reference streams --- OpenMcdf3/Fat.cs | 9 ++------- OpenMcdf3/FatSectorEnumerator.cs | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs index adb5d6e5..97f4a85f 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf3/Fat.cs @@ -11,9 +11,7 @@ internal sealed class Fat : IEnumerable, IDisposable { private readonly IOContext ioContext; private readonly FatSectorEnumerator fatSectorEnumerator; - private readonly int DifatArrayElementCount; private readonly int FatElementsPerSector; - private readonly int DifatElementsPerSector; private readonly byte[] cachedSectorBuffer; Sector cachedSector = Sector.EndOfChain; private bool isDirty; @@ -22,8 +20,6 @@ public Fat(IOContext ioContext) { this.ioContext = ioContext; FatElementsPerSector = ioContext.SectorSize / sizeof(uint); - DifatElementsPerSector = FatElementsPerSector - 1; - DifatArrayElementCount = Header.DifatArrayLength * FatElementsPerSector; fatSectorEnumerator = new(ioContext); cachedSectorBuffer = new byte[ioContext.SectorSize]; } @@ -53,9 +49,8 @@ public uint this[uint key] uint GetSectorIndexAndElementOffset(uint key, out long elementIndex) { - if (key < DifatArrayElementCount) - return (uint)Math.DivRem(key, FatElementsPerSector, out elementIndex); - return Header.DifatArrayLength + (uint)Math.DivRem(key - DifatArrayElementCount, DifatElementsPerSector, out elementIndex); + uint index = (uint)Math.DivRem(key, FatElementsPerSector, out elementIndex); + return index; } void CacheCurrentSector() diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index 16b7c8ef..e22994ea 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -9,6 +9,7 @@ namespace OpenMcdf3; internal sealed class FatSectorEnumerator : IEnumerator { private readonly IOContext ioContext; + private readonly uint DifatElementsPerSector; private bool start = true; private uint index = uint.MaxValue; private uint difatSectorId; @@ -17,6 +18,7 @@ internal sealed class FatSectorEnumerator : IEnumerator public FatSectorEnumerator(IOContext ioContext) { this.ioContext = ioContext; + DifatElementsPerSector = (uint)((ioContext.SectorSize / sizeof(uint)) - 1); difatSectorId = ioContext.Header.FirstDifatSectorId; } @@ -66,11 +68,18 @@ public bool MoveNext() } Sector difatSector = new(difatSectorId, ioContext.SectorSize); + uint elementIndex = (nextIndex - Header.DifatArrayLength) % DifatElementsPerSector; + ioContext.Reader.Position = difatSector.Position + elementIndex * sizeof(uint); + uint id2 = ioContext.Reader.ReadUInt32(); + index = nextIndex; - current = difatSector; + current = new Sector(id2, ioContext.SectorSize); - ioContext.Reader.Position = difatSector.EndPosition - sizeof(uint); - difatSectorId = ioContext.Reader.ReadUInt32(); + if (elementIndex == DifatElementsPerSector - 1) + { + ioContext.Reader.Position = difatSector.EndPosition - sizeof(uint); + difatSectorId = ioContext.Reader.ReadUInt32(); + } return true; } From ea58529d8623fc22dd0625db784e67d1b69dabc7 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Sun, 10 Nov 2024 22:06:52 +1300 Subject: [PATCH 086/134] Add difat sector enumerator --- OpenMcdf3/DifatSectorEnumerator.cs | 131 +++++++++++++++++++++++++++++ OpenMcdf3/Fat.cs | 5 +- OpenMcdf3/FatSectorEnumerator.cs | 128 ++++++++++------------------ 3 files changed, 180 insertions(+), 84 deletions(-) create mode 100644 OpenMcdf3/DifatSectorEnumerator.cs diff --git a/OpenMcdf3/DifatSectorEnumerator.cs b/OpenMcdf3/DifatSectorEnumerator.cs new file mode 100644 index 00000000..4e306901 --- /dev/null +++ b/OpenMcdf3/DifatSectorEnumerator.cs @@ -0,0 +1,131 @@ +using System.Collections; +using System.Diagnostics; + +namespace OpenMcdf3; + +internal class DifatSectorEnumerator : IEnumerator +{ + private readonly IOContext ioContext; + public readonly uint DifatElementsPerSector; + bool start = true; + uint index = uint.MaxValue; + Sector current = Sector.EndOfChain; + private uint difatSectorId = SectorType.EndOfChain; + + public DifatSectorEnumerator(IOContext ioContext) + { + this.ioContext = ioContext; + DifatElementsPerSector = (uint)((ioContext.SectorSize / sizeof(uint)) - 1); + } + + /// + public void Dispose() + { + // IOContext is owned by parent + } + + /// + public Sector Current + { + get + { + if (current.Id == SectorType.EndOfChain) + throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); + return current; + } + } + + /// + object IEnumerator.Current => Current; + + /// + public bool MoveNext() + { + if (start) + { + start = false; + index = uint.MaxValue; + difatSectorId = ioContext.Header.FirstDifatSectorId; + } + + uint nextIndex = index + 1; + if (difatSectorId == SectorType.EndOfChain) + { + index = uint.MaxValue; + current = Sector.EndOfChain; + difatSectorId = SectorType.EndOfChain; + return false; + } + + current = new(difatSectorId, ioContext.SectorSize); + index = nextIndex; + ioContext.Reader.Position = current.EndPosition - sizeof(uint); + difatSectorId = ioContext.Reader.ReadUInt32(); + return true; + } + + public bool MoveTo(uint index) + { + if (index >= ioContext.Header.DifatSectorCount) + return false; + + if (start && !MoveNext()) + return false; + + if (index < this.index) + Reset(); + + while (start || this.index < index) + { + if (!MoveNext()) + return false; + } + + return true; + } + + /// + public void Reset() + { + start = true; + index = uint.MaxValue; + current = Sector.EndOfChain; + difatSectorId = SectorType.EndOfChain; + } + + public void Add() + { + Sector newDifatSector = new(ioContext.SectorCount, ioContext.SectorSize); + + Header header = ioContext.Header; + CfbBinaryWriter writer = ioContext.Writer; + if (header.FirstDifatSectorId == SectorType.EndOfChain) + { + header.FirstDifatSectorId = newDifatSector.Id; + } + else + { + bool ok = MoveTo(header.DifatSectorCount - 1); + if (!ok) + throw new InvalidOperationException("Failed to move to last DIFAT sector."); + + writer.Position = current.EndPosition - sizeof(uint); + writer.Write(newDifatSector.Id); + } + + writer.Position = newDifatSector.Position; + writer.Write(SectorDataCache.GetFatEntryData(newDifatSector.Length)); + writer.Position = newDifatSector.EndPosition - sizeof(uint); + writer.Write(SectorType.EndOfChain); + + ioContext.ExtendStreamLength(newDifatSector.EndPosition); + header.DifatSectorCount++; + + ioContext.Fat[newDifatSector.Id] = SectorType.Difat; + + start = false; + index = header.DifatSectorCount - 1; + current = newDifatSector; + difatSectorId = SectorType.EndOfChain; + } +} diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs index 97f4a85f..aed25162 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf3/Fat.cs @@ -135,7 +135,10 @@ public uint Add(FatEnumerator fatEnumerator, uint startIndex) uint newSectorId = fatSectorEnumerator.Add(); // Next id must be free - bool ok = fatEnumerator.MoveTo(newSectorId + 1); + bool ok = fatEnumerator.MoveTo(newSectorId); + Debug.Assert(ok); + + ok = fatEnumerator.MoveNextFreeEntry(); Debug.Assert(ok); CacheCurrentSector(); diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf3/FatSectorEnumerator.cs index e22994ea..d4464514 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf3/FatSectorEnumerator.cs @@ -9,23 +9,22 @@ namespace OpenMcdf3; internal sealed class FatSectorEnumerator : IEnumerator { private readonly IOContext ioContext; - private readonly uint DifatElementsPerSector; + private readonly DifatSectorEnumerator difatSectorEnumerator; private bool start = true; private uint index = uint.MaxValue; - private uint difatSectorId; private Sector current = Sector.EndOfChain; public FatSectorEnumerator(IOContext ioContext) { this.ioContext = ioContext; - DifatElementsPerSector = (uint)((ioContext.SectorSize / sizeof(uint)) - 1); - difatSectorId = ioContext.Header.FirstDifatSectorId; + difatSectorEnumerator = new(ioContext); } /// public void Dispose() { // IOContext is owned by a parent + difatSectorEnumerator.Dispose(); } /// @@ -52,36 +51,7 @@ public bool MoveNext() } uint nextIndex = index + 1; - if (nextIndex < ioContext.Header.FatSectorCount && nextIndex < Header.DifatArrayLength) // Include the free entries - { - uint id = ioContext.Header.Difat[nextIndex]; - index = nextIndex; - current = new Sector(id, ioContext.SectorSize); - return true; - } - - if (difatSectorId == SectorType.EndOfChain) - { - index = uint.MaxValue; - current = Sector.EndOfChain; - return false; - } - - Sector difatSector = new(difatSectorId, ioContext.SectorSize); - uint elementIndex = (nextIndex - Header.DifatArrayLength) % DifatElementsPerSector; - ioContext.Reader.Position = difatSector.Position + elementIndex * sizeof(uint); - uint id2 = ioContext.Reader.ReadUInt32(); - - index = nextIndex; - current = new Sector(id2, ioContext.SectorSize); - - if (elementIndex == DifatElementsPerSector - 1) - { - ioContext.Reader.Position = difatSector.EndPosition - sizeof(uint); - difatSectorId = ioContext.Reader.ReadUInt32(); - } - - return true; + return MoveTo(nextIndex); } public bool IsAt(uint index) => !start && index == this.index; @@ -98,28 +68,39 @@ public bool MoveTo(uint index) if (index == this.index) return true; - if (index >= ioContext.Header.FatSectorCount + ioContext.Header.DifatSectorCount) + if (index >= ioContext.Header.FatSectorCount) { this.index = uint.MaxValue; current = Sector.EndOfChain; return false; } - if (this.index < Header.DifatArrayLength || index < this.index) + if (index < Header.DifatArrayLength) { - // Jump as close as possible - this.index = Math.Min(index, Header.DifatArrayLength - 1); - uint id = ioContext.Header.Difat[this.index]; - current = new(id, ioContext.SectorSize); - difatSectorId = ioContext.Header.FirstDifatSectorId; + this.index = index; + uint difatId = ioContext.Header.Difat[this.index]; + current = new(difatId, ioContext.SectorSize); + return true; + } + + if (index < this.index) + { + this.index = Header.DifatArrayLength; } - while (this.index < index) + uint difatSectorIndex = (uint)Math.DivRem(index - Header.DifatArrayLength, difatSectorEnumerator.DifatElementsPerSector, out long difatElementIndex); + if (!difatSectorEnumerator.MoveTo(difatSectorIndex)) { - if (!MoveNext()) - return false; + this.index = uint.MaxValue; + current = Sector.EndOfChain; + return false; } + Sector difatSector = difatSectorEnumerator.Current; + ioContext.Reader.Position = difatSector.Position + (difatElementIndex * sizeof(uint)); + uint id = ioContext.Reader.ReadUInt32(); + this.index = index; + current = new Sector(id, ioContext.SectorSize); return true; } @@ -128,8 +109,8 @@ public void Reset() { start = true; index = uint.MaxValue; - difatSectorId = ioContext.Header.FirstDifatSectorId; current = Sector.EndOfChain; + difatSectorEnumerator.Reset(); } (uint lastIndex, Sector lastSector) MoveToEnd() @@ -155,54 +136,35 @@ public uint Add() { // No FAT sectors are free, so add a new one Header header = ioContext.Header; - uint nextIndex = ioContext.Header.FatSectorCount + ioContext.Header.DifatSectorCount; - uint lastIndex = nextIndex - 1; - Sector newSector = new(ioContext.SectorCount, ioContext.SectorSize); + uint nextIndex = ioContext.Header.FatSectorCount; + Sector newFatSector = new(ioContext.SectorCount, ioContext.SectorSize); CfbBinaryWriter writer = ioContext.Writer; - writer.Position = newSector.Position; - writer.Write(SectorDataCache.GetFatEntryData(newSector.Length)); + writer.Position = newFatSector.Position; + writer.Write(SectorDataCache.GetFatEntryData(newFatSector.Length)); + ioContext.ExtendStreamLength(newFatSector.EndPosition); - uint sectorType; - if (nextIndex < Header.DifatArrayLength) - { - index = nextIndex; - current = newSector; - sectorType = SectorType.Fat; + header.FatSectorCount++; - header.Difat[nextIndex] = newSector.Id; - header.FatSectorCount++; + index = nextIndex; + current = newFatSector; - writer.Position = newSector.Position; - writer.Write(SectorDataCache.GetFatEntryData(newSector.Length)); + if (nextIndex < Header.DifatArrayLength) + { + header.Difat[nextIndex] = newFatSector.Id; } else { - bool ok = MoveTo(lastIndex); - Debug.Assert(ok); - - Sector lastSector = current; - writer.Position = lastSector.EndPosition - sizeof(uint); - writer.Write(newSector.Id); - - index = nextIndex; - current = newSector; - difatSectorId = newSector.Id; - sectorType = SectorType.Difat; - - writer.Position = newSector.Position; - writer.Write(SectorDataCache.GetFatEntryData(newSector.Length)); - - writer.Position = newSector.EndPosition - sizeof(uint); - writer.Write(SectorType.EndOfChain); + uint difatSectorIndex = (uint)Math.DivRem(nextIndex - Header.DifatArrayLength, difatSectorEnumerator.DifatElementsPerSector, out long difatElementIndex); + if (!difatSectorEnumerator.MoveTo(difatSectorIndex)) + difatSectorEnumerator.Add(); - // Chain the sector - if (header.FirstDifatSectorId == SectorType.EndOfChain) - header.FirstDifatSectorId = newSector.Id; - header.DifatSectorCount++; + Sector difatSector = difatSectorEnumerator.Current; + writer.Position = difatSector.Position + difatElementIndex * sizeof(uint); + writer.Write(newFatSector.Id); } - ioContext.Fat[newSector.Id] = sectorType; - return newSector.Id; + ioContext.Fat[newFatSector.Id] = SectorType.Fat; + return newFatSector.Id; } } From 2ac65882d9638cb8b7c1819c9c741d160cbe31f5 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 11 Nov 2024 00:01:09 +1300 Subject: [PATCH 087/134] Improve benchmarks --- OpenMcdf3.Benchmarks/FileStreamRead.cs | 55 ++++++++++++++++++ .../FileStreamTransactedWrite.cs | 49 ++++++++++++++++ OpenMcdf3.Benchmarks/FileStreamWrite.cs | 48 +++++++++++++++ ...{MemoryStreamIO.cs => MemoryStreamRead.cs} | 22 +++---- .../MemoryStreamTransactedWrite.cs | 42 ++++++++++++++ OpenMcdf3.Benchmarks/MemoryStreamWrite.cs | 47 +++++++++++++++ OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs | 12 ++++ OpenMcdf3.Benchmarks/Program.cs | 4 +- .../StructuredStorageBenchmarks.cs | 58 ++++++++++++++++--- 9 files changed, 314 insertions(+), 23 deletions(-) create mode 100644 OpenMcdf3.Benchmarks/FileStreamRead.cs create mode 100644 OpenMcdf3.Benchmarks/FileStreamTransactedWrite.cs create mode 100644 OpenMcdf3.Benchmarks/FileStreamWrite.cs rename OpenMcdf3.Benchmarks/{MemoryStreamIO.cs => MemoryStreamRead.cs} (63%) create mode 100644 OpenMcdf3.Benchmarks/MemoryStreamTransactedWrite.cs create mode 100644 OpenMcdf3.Benchmarks/MemoryStreamWrite.cs diff --git a/OpenMcdf3.Benchmarks/FileStreamRead.cs b/OpenMcdf3.Benchmarks/FileStreamRead.cs new file mode 100644 index 00000000..8acb0a6e --- /dev/null +++ b/OpenMcdf3.Benchmarks/FileStreamRead.cs @@ -0,0 +1,55 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using OpenMcdf3.Benchmarks; + +namespace OpenMcdf3.Benchmark; + +[MediumRunJob] +[MemoryDiagnoser] +[HideColumns(Column.AllocRatio)] +[MarkdownExporter] +public class FileStreamRead : IDisposable +{ + const Version version = Version.V3; + + private string readFileName = ""; + + private byte[] buffer = Array.Empty(); + + [Params(512, 1024 * 1024)] + public int BufferSize { get; set; } + + [Params(1024 * 1024)] + public int StreamLength { get; set; } + + public void Dispose() + { + File.Delete(readFileName); + } + + [GlobalSetup] + public void GlobalSetup() + { + readFileName = Path.GetTempFileName(); + + buffer = new byte[BufferSize]; + + using var storage = RootStorage.Create(readFileName, version, StorageModeFlags.LeaveOpen); + using CfbStream stream = storage.CreateStream("Test"); + + int iterationCount = StreamLength / BufferSize; + for (int iteration = 0; iteration < iterationCount; ++iteration) + stream.Write(buffer); + } + + [GlobalCleanup] + public void GlobalCleanup() => Dispose(); + + [Benchmark] + public void Read() => OpenMcdfBenchmarks.ReadStream(readFileName!, buffer); + +#if WINDOWS + [Benchmark(Baseline = true)] + public void ReadStructuredStorage() => StructuredStorageBenchmarks.ReadStream(readFileName!, buffer); +#endif +} diff --git a/OpenMcdf3.Benchmarks/FileStreamTransactedWrite.cs b/OpenMcdf3.Benchmarks/FileStreamTransactedWrite.cs new file mode 100644 index 00000000..e26e1305 --- /dev/null +++ b/OpenMcdf3.Benchmarks/FileStreamTransactedWrite.cs @@ -0,0 +1,49 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using OpenMcdf3.Benchmarks; + +namespace OpenMcdf3.Benchmark; + +[MediumRunJob] +[MemoryDiagnoser] +[HideColumns(Column.AllocRatio)] +[MarkdownExporter] +public class FileStreamTransactedWrite : IDisposable +{ + const Version version = Version.V3; + + private string writeFileName = ""; + + private byte[] buffer = Array.Empty(); + + [Params(512, 1024 * 1024)] + public int BufferSize { get; set; } + + [Params(1024 * 1024)] + public int StreamLength { get; set; } + + public void Dispose() + { + File.Delete(writeFileName); + } + + [GlobalSetup] + public void GlobalSetup() + { + writeFileName = Path.GetTempFileName(); + + buffer = new byte[BufferSize]; + } + + [GlobalCleanup] + public void GlobalCleanup() => Dispose(); + + [Benchmark] + public void WriteTransacted() => OpenMcdfBenchmarks.WriteStream(writeFileName!, version, StorageModeFlags.None | StorageModeFlags.Transacted, buffer, StreamLength); + +#if WINDOWS + + [Benchmark(Baseline = true)] + public void WriteStructuredStorageTransacted() => StructuredStorageBenchmarks.WriteStream(writeFileName, version, StorageModeFlags.Transacted, buffer, StreamLength); +#endif +} diff --git a/OpenMcdf3.Benchmarks/FileStreamWrite.cs b/OpenMcdf3.Benchmarks/FileStreamWrite.cs new file mode 100644 index 00000000..d308a3c7 --- /dev/null +++ b/OpenMcdf3.Benchmarks/FileStreamWrite.cs @@ -0,0 +1,48 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using OpenMcdf3.Benchmarks; + +namespace OpenMcdf3.Benchmark; + +[MediumRunJob] +[MemoryDiagnoser] +[HideColumns(Column.AllocRatio)] +[MarkdownExporter] +public class FileStreamWrite : IDisposable +{ + const Version version = Version.V3; + + private string writeFileName = ""; + + private byte[] buffer = Array.Empty(); + + [Params(512, 1024 * 1024)] + public int BufferSize { get; set; } + + [Params(1024 * 1024)] + public int StreamLength { get; set; } + + public void Dispose() + { + File.Delete(writeFileName); + } + + [GlobalSetup] + public void GlobalSetup() + { + writeFileName = Path.GetTempFileName(); + + buffer = new byte[BufferSize]; + } + + [GlobalCleanup] + public void GlobalCleanup() => Dispose(); + + [Benchmark] + public void Write() => OpenMcdfBenchmarks.WriteStream(writeFileName!, version, StorageModeFlags.None, buffer, StreamLength); + +#if WINDOWS + [Benchmark(Baseline = true)] + public void WriteStructuredStorage() => StructuredStorageBenchmarks.WriteStream(writeFileName, version, StorageModeFlags.None, buffer, StreamLength); +#endif +} diff --git a/OpenMcdf3.Benchmarks/MemoryStreamIO.cs b/OpenMcdf3.Benchmarks/MemoryStreamRead.cs similarity index 63% rename from OpenMcdf3.Benchmarks/MemoryStreamIO.cs rename to OpenMcdf3.Benchmarks/MemoryStreamRead.cs index a7a5d7cc..a050aabd 100644 --- a/OpenMcdf3.Benchmarks/MemoryStreamIO.cs +++ b/OpenMcdf3.Benchmarks/MemoryStreamRead.cs @@ -1,16 +1,18 @@ using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; using OpenMcdf3.Benchmarks; namespace OpenMcdf3.Benchmark; [ShortRunJob] [MemoryDiagnoser] -public class MemoryStreamIO : IDisposable +[HideColumns(Column.AllocRatio)] +[MarkdownExporter] +public class MemoryStreamRead : IDisposable { const Version version = Version.V3; private MemoryStream? readStream; - private MemoryStream? writeStream; private byte[] buffer = Array.Empty(); @@ -23,7 +25,6 @@ public class MemoryStreamIO : IDisposable public void Dispose() { readStream?.Dispose(); - writeStream?.Dispose(); } [GlobalSetup] @@ -31,7 +32,6 @@ public void GlobalSetup() { buffer = new byte[BufferSize]; readStream = new MemoryStream(2 * StreamLength); - writeStream = new MemoryStream(2 * StreamLength); using var storage = RootStorage.Create(readStream, version, StorageModeFlags.LeaveOpen); using CfbStream stream = storage.CreateStream("Test"); @@ -41,20 +41,14 @@ public void GlobalSetup() stream.Write(buffer); } - [Benchmark] - public void Read() => OpenMcdfBenchmarks.ReadStream(readStream!, buffer); - - [Benchmark] - public void Write() => OpenMcdfBenchmarks.WriteStream(writeStream!, version, StorageModeFlags.LeaveOpen, buffer, StreamLength); + [GlobalCleanup] + public void GlobalCleanup() => Dispose(); [Benchmark] - public void WriteTransacted() => OpenMcdfBenchmarks.WriteStream(writeStream!, version, StorageModeFlags.LeaveOpen | StorageModeFlags.Transacted, buffer, StreamLength); + public void Read() => OpenMcdfBenchmarks.ReadStream(readStream!, buffer); #if WINDOWS - [Benchmark] + [Benchmark(Baseline = true)] public void ReadStructuredStorage() => StructuredStorageBenchmarks.ReadStream(readStream!, buffer); - - [Benchmark] - public void WriteStructuredStorage() => StructuredStorageBenchmarks.WriteInMemory(version, StorageModeFlags.LeaveOpen, buffer, StreamLength); #endif } diff --git a/OpenMcdf3.Benchmarks/MemoryStreamTransactedWrite.cs b/OpenMcdf3.Benchmarks/MemoryStreamTransactedWrite.cs new file mode 100644 index 00000000..2b0afaec --- /dev/null +++ b/OpenMcdf3.Benchmarks/MemoryStreamTransactedWrite.cs @@ -0,0 +1,42 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using OpenMcdf3.Benchmarks; + +namespace OpenMcdf3.Benchmark; + +[ShortRunJob] +[MemoryDiagnoser] +[HideColumns(Column.AllocRatio)] +[MarkdownExporter] +public class MemoryStreamTransactedWrite : IDisposable +{ + const Version version = Version.V3; + + private MemoryStream? writeStream; + + private byte[] buffer = Array.Empty(); + + [Params(512, 1024 * 1024)] + public int BufferSize { get; set; } + + [Params(1024 * 1024)] + public int StreamLength { get; set; } + + public void Dispose() + { + writeStream?.Dispose(); + } + + [GlobalSetup] + public void GlobalSetup() + { + buffer = new byte[BufferSize]; + writeStream = new MemoryStream(2 * StreamLength); + } + + [GlobalCleanup] + public void GlobalCleanup() => Dispose(); + + [Benchmark] + public void WriteTransacted() => OpenMcdfBenchmarks.WriteStream(writeStream!, version, StorageModeFlags.LeaveOpen | StorageModeFlags.Transacted, buffer, StreamLength); +} diff --git a/OpenMcdf3.Benchmarks/MemoryStreamWrite.cs b/OpenMcdf3.Benchmarks/MemoryStreamWrite.cs new file mode 100644 index 00000000..87d848d3 --- /dev/null +++ b/OpenMcdf3.Benchmarks/MemoryStreamWrite.cs @@ -0,0 +1,47 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using OpenMcdf3.Benchmarks; + +namespace OpenMcdf3.Benchmark; + +[ShortRunJob] +[MemoryDiagnoser] +[HideColumns(Column.AllocRatio)] +[MarkdownExporter] +public class MemoryStreamWrite : IDisposable +{ + const Version version = Version.V3; + + private MemoryStream? writeStream; + + private byte[] buffer = Array.Empty(); + + [Params(512, 1024 * 1024)] + public int BufferSize { get; set; } + + [Params(1024 * 1024)] + public int StreamLength { get; set; } + + public void Dispose() + { + writeStream?.Dispose(); + } + + [GlobalSetup] + public void GlobalSetup() + { + buffer = new byte[BufferSize]; + writeStream = new MemoryStream(2 * StreamLength); + } + + [GlobalCleanup] + public void GlobalCleanup() => Dispose(); + + [Benchmark] + public void Write() => OpenMcdfBenchmarks.WriteStream(writeStream!, version, StorageModeFlags.LeaveOpen, buffer, StreamLength); + +#if WINDOWS + [Benchmark(Baseline = true)] + public void WriteStructuredStorage() => StructuredStorageBenchmarks.WriteInMemory(version, StorageModeFlags.LeaveOpen, buffer, StreamLength); +#endif +} diff --git a/OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs b/OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs index da4bd78c..f4232b5c 100644 --- a/OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs +++ b/OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs @@ -2,6 +2,12 @@ internal static class OpenMcdfBenchmarks { + public static void ReadStream(string fileName, byte[] buffer) + { + using FileStream fileStream = File.OpenRead(fileName); + ReadStream(fileStream, buffer); + } + public static void ReadStream(Stream stream, byte[] buffer) { using var storage = RootStorage.Open(stream, StorageModeFlags.LeaveOpen); @@ -14,6 +20,12 @@ public static void ReadStream(Stream stream, byte[] buffer) } } + public static void WriteStream(string fileName, Version version, StorageModeFlags flags, byte[] buffer, long streamLength) + { + using FileStream fileStream = File.Create(fileName); + WriteStream(fileStream, version, flags, buffer, streamLength); + } + public static void WriteStream(Stream stream, Version version, StorageModeFlags flags, byte[] buffer, long streamLength) { using var storage = RootStorage.Create(stream, version, flags); diff --git a/OpenMcdf3.Benchmarks/Program.cs b/OpenMcdf3.Benchmarks/Program.cs index 2707ca37..05756ebc 100644 --- a/OpenMcdf3.Benchmarks/Program.cs +++ b/OpenMcdf3.Benchmarks/Program.cs @@ -2,10 +2,10 @@ namespace OpenMcdf3.Benchmarks; -internal static class Program +internal sealed class Program { private static void Main(string[] args) { - BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); + BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(); } } diff --git a/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs b/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs index 0ad07e0c..28b8b672 100644 --- a/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs +++ b/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs @@ -1,26 +1,70 @@ -namespace OpenMcdf3.Benchmarks; +using StructuredStorage; + +namespace OpenMcdf3.Benchmarks; internal class StructuredStorageBenchmarks { + public static void ReadStream(string fileName, byte[] buffer) + { + using var storage = StructuredStorage.Storage.Open(fileName); + using StructuredStorage.Stream storageStream = storage.OpenStream("Test"); + + long length = storageStream.Length; // Length lookup is expensive + long totalRead = 0; + while (totalRead < length) + { + int read = storageStream.Read(buffer, 0, buffer.Length); + if (read <= 0) + throw new EndOfStreamException($"Read past end of stream at {storageStream.Position}/{storageStream.Length}"); + totalRead += read; + } + } + public static void ReadStream(MemoryStream stream, byte[] buffer) { using var storage = StructuredStorage.Storage.Open(stream); using StructuredStorage.Stream storageStream = storage.OpenStream("Test"); - while (storageStream.Position < storageStream.Length) + long length = storageStream.Length; // Length lookup is expensive + long totalRead = 0; + while (totalRead < length) { - int read = stream.Read(buffer, 0, buffer.Length); + int read = storageStream.Read(buffer, 0, buffer.Length); if (read <= 0) - throw new EndOfStreamException(); + throw new EndOfStreamException($"Read past end of stream at {storageStream.Position}/{storageStream.Length}"); + totalRead += read; + } + } + + public static void WriteStream(string fileName, Version version, StorageModeFlags flags, byte[] buffer, long streamLength) + { + File.Delete(fileName); + + StorageModes modes = StorageModes.AccessReadWrite | StorageModes.ShareExclusive; + if (flags.HasFlag(StorageModeFlags.Transacted)) + modes |= StorageModes.Transacted; + + using var storage = StructuredStorage.Storage.Create(fileName, modes, version == Version.V4); + using StructuredStorage.Stream storageStream = storage.CreateStream("Test"); + + long totalWritten = 0; + while (totalWritten < streamLength) + { + storageStream.Write(buffer, 0, buffer.Length); + totalWritten += buffer.Length; } } public static void WriteInMemory(Version version, StorageModeFlags flags, byte[] buffer, long streamLength) { using var storage = StructuredStorage.Storage.CreateInMemory((int)streamLength * 2); - using StructuredStorage.Stream stream = storage.CreateStream("Test"); + using StructuredStorage.Stream storageStream = storage.CreateStream("Test"); - while (stream.Length < streamLength) - stream.Write(buffer, 0, buffer.Length); + long totalWritten = 0; + while (totalWritten < streamLength) + { + storageStream.Write(buffer, 0, buffer.Length); + totalWritten += buffer.Length; + } } } From cc2d502cce9af0425a4fab50a616f83f9b018c64 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 11 Nov 2024 13:39:33 +1300 Subject: [PATCH 088/134] Delete transaction scratch file on dispose --- OpenMcdf3/IOContext.cs | 3 +++ OpenMcdf3/TransactedStream.cs | 51 ++++++++++++++++++----------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index a6c61aee..d47df376 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -132,7 +132,10 @@ public void Dispose() Fat.Dispose(); writer?.Dispose(); Reader.Dispose(); + string? overlayFileName = (transactedStream?.OverlayStream as FileStream)?.Name; transactedStream?.Dispose(); + if (overlayFileName is not null) + File.Delete(overlayFileName); if (!contextFlags.HasFlag(IOContextFlags.LeaveOpen)) stream.Dispose(); IsDisposed = true; diff --git a/OpenMcdf3/TransactedStream.cs b/OpenMcdf3/TransactedStream.cs index b5bdfbc6..42273a2a 100644 --- a/OpenMcdf3/TransactedStream.cs +++ b/OpenMcdf3/TransactedStream.cs @@ -6,7 +6,6 @@ internal class TransactedStream : Stream { readonly IOContext ioContext; readonly Stream originalStream; - readonly Stream overlayStream; readonly Dictionary dirtySectorPositions = new(); readonly byte[] buffer; @@ -14,29 +13,31 @@ public TransactedStream(IOContext ioContext, Stream originalStream, Stream overl { this.ioContext = ioContext; this.originalStream = originalStream; - this.overlayStream = overlayStream; + OverlayStream = overlayStream; buffer = new byte[ioContext.SectorSize]; } protected override void Dispose(bool disposing) { // Original stream might be owned by the caller - overlayStream.Dispose(); + OverlayStream.Dispose(); base.Dispose(disposing); } + public Stream OverlayStream { get; } + public override bool CanRead => true; public override bool CanSeek => true; public override bool CanWrite => true; - public override long Length => overlayStream.Length; + public override long Length => OverlayStream.Length; public override long Position { get => originalStream.Position; set => originalStream.Position = value; } - public override void Flush() => overlayStream.Flush(); + public override void Flush() => OverlayStream.Flush(); public override int Read(byte[] buffer, int offset, int count) { @@ -52,8 +53,8 @@ public override int Read(byte[] buffer, int offset, int count) if (dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) { - overlayStream.Position = overlayPosition + sectorOffset; - read = overlayStream.Read(buffer, offset + totalRead, localCount); + OverlayStream.Position = overlayPosition + sectorOffset; + read = OverlayStream.Read(buffer, offset + totalRead, localCount); originalStream.Seek(read, SeekOrigin.Current); } else @@ -87,7 +88,7 @@ public override void Write(byte[] buffer, int offset, int count) bool added = false; if (!dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) { - overlayPosition = overlayStream.Length; + overlayPosition = OverlayStream.Length; dirtySectorPositions.Add(sectorId, overlayPosition); added = true; } @@ -99,14 +100,14 @@ public override void Write(byte[] buffer, int offset, int count) originalStream.Position = originalPosition - sectorOffset; originalStream.ReadExactly(this.buffer); - overlayStream.Position = overlayPosition; - overlayStream.Write(this.buffer, 0, this.buffer.Length); + OverlayStream.Position = overlayPosition; + OverlayStream.Write(this.buffer, 0, this.buffer.Length); } - overlayStream.Position = overlayPosition + sectorOffset; - overlayStream.Write(buffer, offset, localCount); - if (overlayStream.Length < overlayPosition + ioContext.SectorSize) - overlayStream.SetLength(overlayPosition + ioContext.SectorSize); + OverlayStream.Position = overlayPosition + sectorOffset; + OverlayStream.Write(buffer, offset, localCount); + if (OverlayStream.Length < overlayPosition + ioContext.SectorSize) + OverlayStream.SetLength(overlayPosition + ioContext.SectorSize); originalStream.Position = originalPosition + localCount; } @@ -114,8 +115,8 @@ public void Commit() { foreach (KeyValuePair entry in dirtySectorPositions) { - overlayStream.Position = entry.Value; - overlayStream.ReadExactly(buffer); + OverlayStream.Position = entry.Value; + OverlayStream.ReadExactly(buffer); originalStream.Position = entry.Key * ioContext.SectorSize; originalStream.Write(buffer, 0, buffer.Length); @@ -145,8 +146,8 @@ public override int Read(Span buffer) int read; if (dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) { - overlayStream.Position = overlayPosition + sectorOffset; - read = overlayStream.Read(slice); + OverlayStream.Position = overlayPosition + sectorOffset; + read = OverlayStream.Read(slice); originalStream.Seek(read, SeekOrigin.Current); } else @@ -170,7 +171,7 @@ public override void Write(ReadOnlySpan buffer) bool added = false; if (!dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) { - overlayPosition = overlayStream.Length; + overlayPosition = OverlayStream.Length; dirtySectorPositions.Add(sectorId, overlayPosition); added = true; } @@ -182,14 +183,14 @@ public override void Write(ReadOnlySpan buffer) originalStream.Position = originalPosition - sectorOffset; originalStream.ReadExactly(this.buffer); - overlayStream.Position = overlayPosition; - overlayStream.Write(this.buffer, 0, this.buffer.Length); + OverlayStream.Position = overlayPosition; + OverlayStream.Write(this.buffer, 0, this.buffer.Length); } - overlayStream.Position = overlayPosition + sectorOffset; - overlayStream.Write(buffer); - if (overlayStream.Length < overlayPosition + ioContext.SectorSize) - overlayStream.SetLength(overlayPosition + ioContext.SectorSize); + OverlayStream.Position = overlayPosition + sectorOffset; + OverlayStream.Write(buffer); + if (OverlayStream.Length < overlayPosition + ioContext.SectorSize) + OverlayStream.SetLength(overlayPosition + ioContext.SectorSize); originalStream.Position = originalPosition + localCount; } From f1391ff8f8341c2f95ef01bc98fe8decc759d957 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 11 Nov 2024 16:02:02 +1300 Subject: [PATCH 089/134] Add flush with optional consolidation --- OpenMcdf3.Tests/StorageTests.cs | 26 ++++++++++++++- OpenMcdf3/Fat.cs | 31 +++++++++--------- OpenMcdf3/FatEnumerator.cs | 10 ------ OpenMcdf3/IOContext.cs | 30 +++++++++++------ OpenMcdf3/RootStorage.cs | 58 +++++++++++++++++++++++++++++++-- OpenMcdf3/Storage.cs | 19 +++++++++++ 6 files changed, 134 insertions(+), 40 deletions(-) diff --git a/OpenMcdf3.Tests/StorageTests.cs b/OpenMcdf3.Tests/StorageTests.cs index 2390ee51..3c6897d7 100644 --- a/OpenMcdf3.Tests/StorageTests.cs +++ b/OpenMcdf3.Tests/StorageTests.cs @@ -217,10 +217,34 @@ public void DeleteStream(Version version) Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); } - using (var rootStorage = RootStorage.Create(memoryStream, version)) + using (var rootStorage = RootStorage.Open(memoryStream)) { rootStorage.Delete("Test"); Assert.AreEqual(0, rootStorage.EnumerateEntries().Count()); } } + + [TestMethod] + [DataRow(Version.V3)] + [DataRow(Version.V4)] + public void Consolidate(Version version) + { + byte[] buffer = new byte[4096]; + + using MemoryStream memoryStream = new(); + using var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen); + CfbStream stream = rootStorage.CreateStream("Test"); + Assert.AreEqual(1, rootStorage.EnumerateEntries().Count()); + + stream.Write(buffer, 0, buffer.Length); + rootStorage.Flush(); + + int originalMemoryStreamLength = (int)memoryStream.Length; + + rootStorage.Delete("Test"); + + rootStorage.Flush(true); + + Assert.IsTrue(originalMemoryStreamLength > memoryStream.Length); + } } diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs index aed25162..f7da94a1 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf3/Fat.cs @@ -11,7 +11,7 @@ internal sealed class Fat : IEnumerable, IDisposable { private readonly IOContext ioContext; private readonly FatSectorEnumerator fatSectorEnumerator; - private readonly int FatElementsPerSector; + internal readonly int FatElementsPerSector; private readonly byte[] cachedSectorBuffer; Sector cachedSector = Sector.EndOfChain; private bool isDirty; @@ -145,7 +145,8 @@ public uint Add(FatEnumerator fatEnumerator, uint startIndex) } FatEntry entry = fatEnumerator.Current; - ioContext.ExtendStreamLength(fatEnumerator.CurrentSector.EndPosition); + Sector sector = new(entry.Index, ioContext.SectorSize); + ioContext.ExtendStreamLength(sector.EndPosition); this[entry.Index] = SectorType.EndOfChain; return entry.Index; } @@ -156,27 +157,25 @@ public uint Add(FatEnumerator fatEnumerator, uint startIndex) internal void WriteTrace(TextWriter writer) { - using FatEnumerator fatEnumerator = new(ioContext); - byte[] data = new byte[ioContext.SectorSize]; Stream baseStream = ioContext.Reader.BaseStream; writer.WriteLine("Start of FAT ================="); - while (fatEnumerator.MoveNext()) + foreach (FatEntry entry in this) { - FatEntry current = fatEnumerator.Current; - if (current.IsFree) + Sector sector = new(entry.Index, ioContext.SectorSize); + if (entry.IsFree) { - writer.WriteLine($"{current}"); + writer.WriteLine($"{entry}"); } else { - baseStream.Position = fatEnumerator.CurrentSector.Position; + baseStream.Position = sector.Position; baseStream.ReadExactly(data, 0, data.Length); string hex = BitConverter.ToString(data); - writer.WriteLine($"{current}: {hex}"); + writer.WriteLine($"{entry}: {hex}"); } } @@ -185,15 +184,15 @@ internal void WriteTrace(TextWriter writer) internal void Validate() { - using FatEnumerator fatEnumerator = new(ioContext); - - while (fatEnumerator.MoveNext()) + foreach (FatEntry entry in this) { - FatEntry current = fatEnumerator.Current; - if (current.Value <= SectorType.Maximum && fatEnumerator.CurrentSector.EndPosition > ioContext.Length) + Sector sector = new(entry.Index, ioContext.SectorSize); + if (entry.Value <= SectorType.Maximum && sector.EndPosition > ioContext.Length) { - throw new FormatException($"FAT entry {current} is beyond the end of the stream."); + throw new FormatException($"FAT entry {entry} is beyond the end of the stream."); } } } + + internal long GetFreeSectorCount() => this.Count(entry => entry.IsFree); } diff --git a/OpenMcdf3/FatEnumerator.cs b/OpenMcdf3/FatEnumerator.cs index d64aebad..ee59bbb3 100644 --- a/OpenMcdf3/FatEnumerator.cs +++ b/OpenMcdf3/FatEnumerator.cs @@ -22,16 +22,6 @@ public void Dispose() { } - public Sector CurrentSector - { - get - { - if (index == uint.MaxValue) - throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); - return new(index, ioContext.SectorSize); - } - } - /// public FatEntry Current { diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index d47df376..992a9fbc 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -13,7 +13,6 @@ enum IOContextFlags /// internal sealed class IOContext : IDisposable { - readonly Stream stream; readonly IOContextFlags contextFlags; readonly CfbBinaryWriter? writer; readonly TransactedStream? transactedStream; @@ -22,6 +21,8 @@ internal sealed class IOContext : IDisposable public Header Header { get; } + public Stream BaseStream { get; } + public CfbBinaryReader Reader { get; } public CfbBinaryWriter Writer @@ -75,9 +76,11 @@ public FatStream MiniStream public uint SectorCount => (uint)Math.Max(0, (Length - SectorSize) / SectorSize); // TODO: Check + bool isDirty; + public IOContext(Stream stream, Version version, IOContextFlags contextFlags = IOContextFlags.None) { - this.stream = stream; + BaseStream = stream; this.contextFlags = contextFlags; using CfbBinaryReader reader = new(stream); @@ -119,12 +122,7 @@ public void Dispose() { if (!IsDisposed) { - if (writer is not null && transactedStream is null) - { - // Ensure the stream is as long as expected - stream.SetLength(Length); - WriteHeader(); - } + Flush(); miniStream?.Dispose(); miniFat?.Dispose(); @@ -137,15 +135,27 @@ public void Dispose() if (overlayFileName is not null) File.Delete(overlayFileName); if (!contextFlags.HasFlag(IOContextFlags.LeaveOpen)) - stream.Dispose(); + BaseStream.Dispose(); IsDisposed = true; } } + public void Flush() + { + if (isDirty && writer is not null && transactedStream is null) + { + // Ensure the stream is as long as expected + BaseStream.SetLength(Length); + WriteHeader(); + isDirty = false; + } + } + public void ExtendStreamLength(long length) { if (Length < length) Length = length; + isDirty = true; } public void WriteHeader() @@ -171,7 +181,7 @@ public void Commit() public void Revert() { if (writer is null || transactedStream is null) - throw new InvalidOperationException("Cannot commit non-transacted storage."); + throw new InvalidOperationException("Cannot revert non-transacted storage."); transactedStream.Revert(); } diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index 3d452d30..a84eca03 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -20,6 +20,8 @@ public enum StorageModeFlags /// public sealed class RootStorage : Storage, IDisposable { + readonly StorageModeFlags storageModeFlags; + public static RootStorage Create(string fileName, Version version = Version.V3, StorageModeFlags flags = StorageModeFlags.None) { FileStream stream = File.Create(fileName); @@ -39,7 +41,7 @@ public static RootStorage Create(Stream stream, Version version = Version.V3, St contextFlags |= IOContextFlags.Transacted; IOContext ioContext = new(stream, version, contextFlags); - return new RootStorage(ioContext); + return new RootStorage(ioContext, flags); } public static RootStorage Open(string fileName, FileMode mode, StorageModeFlags flags = StorageModeFlags.None) @@ -66,23 +68,73 @@ public static RootStorage Open(Stream stream, StorageModeFlags flags = StorageMo contextFlags |= IOContextFlags.Transacted; IOContext ioContext = new(stream, Version.Unknown, contextFlags); - return new RootStorage(ioContext); + return new RootStorage(ioContext, flags); } - RootStorage(IOContext ioContext) + RootStorage(IOContext ioContext, StorageModeFlags storageModeFlags) : base(ioContext, ioContext.RootEntry) { + this.storageModeFlags = storageModeFlags; } public void Dispose() => ioContext?.Dispose(); + public void Flush(bool consolidate = false) + { + this.ThrowIfDisposed(ioContext.IsDisposed); + + ioContext.Flush(); + + if (consolidate) + Consolidate(); + } + + void Consolidate() + { + // TODO: Consolidate by defragmentation instead of copy + + Stream? destinationStream = null; + + try + { + if (ioContext.BaseStream is MemoryStream) + destinationStream = new MemoryStream((int)ioContext.BaseStream.Length); + else if (ioContext.BaseStream is FileStream) + destinationStream = File.Create(Path.GetTempFileName()); + else + throw new NotSupportedException("Unsupported stream type for consolidation."); + + using (RootStorage destinationStorage = Create(destinationStream, ioContext.Version, storageModeFlags)) + CopyTo(destinationStorage); + + ioContext.BaseStream.Position = 0; + destinationStream.Position = 0; + + destinationStream.CopyTo(ioContext.BaseStream); + ioContext.BaseStream.SetLength(destinationStream.Length); + } + catch + { + if (destinationStream is FileStream fs) + { + string fileName = fs.Name; + fs.Dispose(); + File.Delete(fileName); + } + } + } + public void Commit() { + this.ThrowIfDisposed(ioContext.IsDisposed); + ioContext.Commit(); } public void Revert() { + this.ThrowIfDisposed(ioContext.IsDisposed); + ioContext.Revert(); } diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index 33cf3b76..b9c581b9 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -103,6 +103,25 @@ public CfbStream OpenStream(string name) return new CfbStream(ioContext, entry); } + public void CopyTo(Storage destination) + { + foreach (DirectoryEntry entry in EnumerateDirectoryEntries()) + { + if (entry.Type is StorageType.Storage) + { + Storage subSource = new(ioContext, entry); + Storage subDestination = destination.CreateStorage(entry.NameString); + subSource.CopyTo(subDestination); + } + else if (entry.Type is StorageType.Stream) + { + CfbStream stream = new(ioContext, entry); + CfbStream destinationStream = destination.CreateStream(entry.NameString); + stream.CopyTo(destinationStream); + } + } + } + public void Delete(string name) { ThrowHelper.ThrowIfNameIsInvalid(name); From 5b12f1b2c9eb5e34aaacc733f927cfeb772d05e4 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 11 Nov 2024 20:35:16 +1300 Subject: [PATCH 090/134] Add v4 to benchmarks --- OpenMcdf3.Benchmarks/FileStreamRead.cs | 8 ++++---- OpenMcdf3.Benchmarks/FileStreamTransactedWrite.cs | 10 +++++----- OpenMcdf3.Benchmarks/FileStreamWrite.cs | 10 +++++----- OpenMcdf3.Benchmarks/MemoryStreamRead.cs | 8 ++++---- OpenMcdf3.Benchmarks/MemoryStreamTransactedWrite.cs | 8 ++++---- OpenMcdf3.Benchmarks/MemoryStreamWrite.cs | 10 +++++----- OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs | 3 +++ 7 files changed, 30 insertions(+), 27 deletions(-) diff --git a/OpenMcdf3.Benchmarks/FileStreamRead.cs b/OpenMcdf3.Benchmarks/FileStreamRead.cs index 8acb0a6e..ed3b3e1d 100644 --- a/OpenMcdf3.Benchmarks/FileStreamRead.cs +++ b/OpenMcdf3.Benchmarks/FileStreamRead.cs @@ -10,12 +10,12 @@ namespace OpenMcdf3.Benchmark; [MarkdownExporter] public class FileStreamRead : IDisposable { - const Version version = Version.V3; - private string readFileName = ""; - private byte[] buffer = Array.Empty(); + [Params(Version.V3, Version.V4)] + public Version Version { get; set; } + [Params(512, 1024 * 1024)] public int BufferSize { get; set; } @@ -34,7 +34,7 @@ public void GlobalSetup() buffer = new byte[BufferSize]; - using var storage = RootStorage.Create(readFileName, version, StorageModeFlags.LeaveOpen); + using var storage = RootStorage.Create(readFileName, Version, StorageModeFlags.LeaveOpen); using CfbStream stream = storage.CreateStream("Test"); int iterationCount = StreamLength / BufferSize; diff --git a/OpenMcdf3.Benchmarks/FileStreamTransactedWrite.cs b/OpenMcdf3.Benchmarks/FileStreamTransactedWrite.cs index e26e1305..3f7b1a3d 100644 --- a/OpenMcdf3.Benchmarks/FileStreamTransactedWrite.cs +++ b/OpenMcdf3.Benchmarks/FileStreamTransactedWrite.cs @@ -10,12 +10,12 @@ namespace OpenMcdf3.Benchmark; [MarkdownExporter] public class FileStreamTransactedWrite : IDisposable { - const Version version = Version.V3; - private string writeFileName = ""; - private byte[] buffer = Array.Empty(); + [Params(Version.V3, Version.V4)] + public Version Version { get; set; } + [Params(512, 1024 * 1024)] public int BufferSize { get; set; } @@ -39,11 +39,11 @@ public void GlobalSetup() public void GlobalCleanup() => Dispose(); [Benchmark] - public void WriteTransacted() => OpenMcdfBenchmarks.WriteStream(writeFileName!, version, StorageModeFlags.None | StorageModeFlags.Transacted, buffer, StreamLength); + public void WriteTransacted() => OpenMcdfBenchmarks.WriteStream(writeFileName!, Version, StorageModeFlags.None | StorageModeFlags.Transacted, buffer, StreamLength); #if WINDOWS [Benchmark(Baseline = true)] - public void WriteStructuredStorageTransacted() => StructuredStorageBenchmarks.WriteStream(writeFileName, version, StorageModeFlags.Transacted, buffer, StreamLength); + public void WriteStructuredStorageTransacted() => StructuredStorageBenchmarks.WriteStream(writeFileName, Version, StorageModeFlags.Transacted, buffer, StreamLength); #endif } diff --git a/OpenMcdf3.Benchmarks/FileStreamWrite.cs b/OpenMcdf3.Benchmarks/FileStreamWrite.cs index d308a3c7..d5b0d8ce 100644 --- a/OpenMcdf3.Benchmarks/FileStreamWrite.cs +++ b/OpenMcdf3.Benchmarks/FileStreamWrite.cs @@ -10,12 +10,12 @@ namespace OpenMcdf3.Benchmark; [MarkdownExporter] public class FileStreamWrite : IDisposable { - const Version version = Version.V3; - private string writeFileName = ""; - private byte[] buffer = Array.Empty(); + [Params(Version.V3, Version.V4)] + public Version Version { get; set; } + [Params(512, 1024 * 1024)] public int BufferSize { get; set; } @@ -39,10 +39,10 @@ public void GlobalSetup() public void GlobalCleanup() => Dispose(); [Benchmark] - public void Write() => OpenMcdfBenchmarks.WriteStream(writeFileName!, version, StorageModeFlags.None, buffer, StreamLength); + public void Write() => OpenMcdfBenchmarks.WriteStream(writeFileName!, Version, StorageModeFlags.None, buffer, StreamLength); #if WINDOWS [Benchmark(Baseline = true)] - public void WriteStructuredStorage() => StructuredStorageBenchmarks.WriteStream(writeFileName, version, StorageModeFlags.None, buffer, StreamLength); + public void WriteStructuredStorage() => StructuredStorageBenchmarks.WriteStream(writeFileName, Version, StorageModeFlags.None, buffer, StreamLength); #endif } diff --git a/OpenMcdf3.Benchmarks/MemoryStreamRead.cs b/OpenMcdf3.Benchmarks/MemoryStreamRead.cs index a050aabd..56a40285 100644 --- a/OpenMcdf3.Benchmarks/MemoryStreamRead.cs +++ b/OpenMcdf3.Benchmarks/MemoryStreamRead.cs @@ -10,12 +10,12 @@ namespace OpenMcdf3.Benchmark; [MarkdownExporter] public class MemoryStreamRead : IDisposable { - const Version version = Version.V3; - private MemoryStream? readStream; - private byte[] buffer = Array.Empty(); + [Params(Version.V3, Version.V4)] + public Version Version { get; set; } + [Params(512, 1024 * 1024)] public int BufferSize { get; set; } @@ -33,7 +33,7 @@ public void GlobalSetup() buffer = new byte[BufferSize]; readStream = new MemoryStream(2 * StreamLength); - using var storage = RootStorage.Create(readStream, version, StorageModeFlags.LeaveOpen); + using var storage = RootStorage.Create(readStream, Version, StorageModeFlags.LeaveOpen); using CfbStream stream = storage.CreateStream("Test"); int iterationCount = StreamLength / BufferSize; diff --git a/OpenMcdf3.Benchmarks/MemoryStreamTransactedWrite.cs b/OpenMcdf3.Benchmarks/MemoryStreamTransactedWrite.cs index 2b0afaec..73c6ef87 100644 --- a/OpenMcdf3.Benchmarks/MemoryStreamTransactedWrite.cs +++ b/OpenMcdf3.Benchmarks/MemoryStreamTransactedWrite.cs @@ -10,12 +10,12 @@ namespace OpenMcdf3.Benchmark; [MarkdownExporter] public class MemoryStreamTransactedWrite : IDisposable { - const Version version = Version.V3; - private MemoryStream? writeStream; - private byte[] buffer = Array.Empty(); + [Params(Version.V3, Version.V4)] + public Version Version { get; set; } + [Params(512, 1024 * 1024)] public int BufferSize { get; set; } @@ -38,5 +38,5 @@ public void GlobalSetup() public void GlobalCleanup() => Dispose(); [Benchmark] - public void WriteTransacted() => OpenMcdfBenchmarks.WriteStream(writeStream!, version, StorageModeFlags.LeaveOpen | StorageModeFlags.Transacted, buffer, StreamLength); + public void WriteTransacted() => OpenMcdfBenchmarks.WriteStream(writeStream!, Version, StorageModeFlags.LeaveOpen | StorageModeFlags.Transacted, buffer, StreamLength); } diff --git a/OpenMcdf3.Benchmarks/MemoryStreamWrite.cs b/OpenMcdf3.Benchmarks/MemoryStreamWrite.cs index 87d848d3..db669337 100644 --- a/OpenMcdf3.Benchmarks/MemoryStreamWrite.cs +++ b/OpenMcdf3.Benchmarks/MemoryStreamWrite.cs @@ -10,12 +10,12 @@ namespace OpenMcdf3.Benchmark; [MarkdownExporter] public class MemoryStreamWrite : IDisposable { - const Version version = Version.V3; - private MemoryStream? writeStream; - private byte[] buffer = Array.Empty(); + [Params(Version.V3, Version.V4)] + public Version Version { get; set; } + [Params(512, 1024 * 1024)] public int BufferSize { get; set; } @@ -38,10 +38,10 @@ public void GlobalSetup() public void GlobalCleanup() => Dispose(); [Benchmark] - public void Write() => OpenMcdfBenchmarks.WriteStream(writeStream!, version, StorageModeFlags.LeaveOpen, buffer, StreamLength); + public void Write() => OpenMcdfBenchmarks.WriteStream(writeStream!, Version, StorageModeFlags.LeaveOpen, buffer, StreamLength); #if WINDOWS [Benchmark(Baseline = true)] - public void WriteStructuredStorage() => StructuredStorageBenchmarks.WriteInMemory(version, StorageModeFlags.LeaveOpen, buffer, StreamLength); + public void WriteStructuredStorage() => StructuredStorageBenchmarks.WriteInMemory(Version, StorageModeFlags.LeaveOpen, buffer, StreamLength); #endif } diff --git a/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs b/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs index 28b8b672..c83cde10 100644 --- a/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs +++ b/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs @@ -57,6 +57,9 @@ public static void WriteStream(string fileName, Version version, StorageModeFlag public static void WriteInMemory(Version version, StorageModeFlags flags, byte[] buffer, long streamLength) { + if (version == Version.V4) + throw new NotSupportedException(); + using var storage = StructuredStorage.Storage.CreateInMemory((int)streamLength * 2); using StructuredStorage.Stream storageStream = storage.CreateStream("Test"); From 45432d3aaa691d6268737c16ff40f56155310738 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 11 Nov 2024 20:42:10 +1300 Subject: [PATCH 091/134] Add validation for FAT/DIFAT sector count --- OpenMcdf3/Fat.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs index f7da94a1..839d9c51 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf3/Fat.cs @@ -184,14 +184,23 @@ internal void WriteTrace(TextWriter writer) internal void Validate() { + long fatSectorCount = 0; + long difatSectorCount = 0; foreach (FatEntry entry in this) { Sector sector = new(entry.Index, ioContext.SectorSize); if (entry.Value <= SectorType.Maximum && sector.EndPosition > ioContext.Length) - { throw new FormatException($"FAT entry {entry} is beyond the end of the stream."); - } + if (entry.Value == SectorType.Fat) + fatSectorCount++; + if (entry.Value == SectorType.Difat) + difatSectorCount++; } + + if (ioContext.Header.FatSectorCount != fatSectorCount) + throw new FormatException($"FAT sector count mismatch. Expected: {ioContext.Header.FatSectorCount} Actual: {fatSectorCount}."); + if (ioContext.Header.DifatSectorCount != difatSectorCount) + throw new FormatException($"DIFAT sector count mismatch: Expected: {ioContext.Header.DifatSectorCount} Actual: {difatSectorCount}."); } internal long GetFreeSectorCount() => this.Count(entry => entry.IsFree); From d91bf30f4d4380358cffd01b479116fe2463ffd8 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 11 Nov 2024 20:56:04 +1300 Subject: [PATCH 092/134] Trace free/used sectors --- OpenMcdf3/Fat.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf3/Fat.cs index 839d9c51..9e3a2b0a 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf3/Fat.cs @@ -163,15 +163,20 @@ internal void WriteTrace(TextWriter writer) writer.WriteLine("Start of FAT ================="); + long freeCount = 0; + long usedCount = 0; + foreach (FatEntry entry in this) { Sector sector = new(entry.Index, ioContext.SectorSize); if (entry.IsFree) { + freeCount++; writer.WriteLine($"{entry}"); } else { + usedCount++; baseStream.Position = sector.Position; baseStream.ReadExactly(data, 0, data.Length); string hex = BitConverter.ToString(data); @@ -180,6 +185,9 @@ internal void WriteTrace(TextWriter writer) } writer.WriteLine("End of FAT ==================="); + writer.WriteLine(); + writer.WriteLine($"Free sectors: {freeCount}"); + writer.WriteLine($"Used sectors: {usedCount}"); } internal void Validate() From 4d36a9ddd9f29afa7e5d71f2961ca1970a967c79 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 11 Nov 2024 21:25:51 +1300 Subject: [PATCH 093/134] Improve root storage flag handling --- OpenMcdf3.Tests/StorageTests.cs | 2 +- OpenMcdf3/RootStorage.cs | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/OpenMcdf3.Tests/StorageTests.cs b/OpenMcdf3.Tests/StorageTests.cs index 3c6897d7..1160186b 100644 --- a/OpenMcdf3.Tests/StorageTests.cs +++ b/OpenMcdf3.Tests/StorageTests.cs @@ -10,7 +10,7 @@ public sealed class StorageTests [DataRow("MultipleStorage4.cfs", 1)] public void Read(string fileName, long storageCount) { - using (var rootStorage = RootStorage.OpenRead(fileName, StorageModeFlags.LeaveOpen)) + using (var rootStorage = RootStorage.OpenRead(fileName)) { IEnumerable storageEntries = rootStorage.EnumerateEntries(StorageType.Storage); Assert.AreEqual(storageCount, storageEntries.Count()); diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf3/RootStorage.cs index a84eca03..b50fb065 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf3/RootStorage.cs @@ -22,10 +22,18 @@ public sealed class RootStorage : Storage, IDisposable { readonly StorageModeFlags storageModeFlags; + private static void ThrowIfLeaveOpen(StorageModeFlags flags) + { + if (flags.HasFlag(StorageModeFlags.LeaveOpen)) + throw new ArgumentException($"{StorageModeFlags.LeaveOpen} is not valid for files"); + } + public static RootStorage Create(string fileName, Version version = Version.V3, StorageModeFlags flags = StorageModeFlags.None) { + ThrowIfLeaveOpen(flags); + FileStream stream = File.Create(fileName); - return Create(stream, version); + return Create(stream, version, flags); } public static RootStorage Create(Stream stream, Version version = Version.V3, StorageModeFlags flags = StorageModeFlags.None) @@ -46,14 +54,18 @@ public static RootStorage Create(Stream stream, Version version = Version.V3, St public static RootStorage Open(string fileName, FileMode mode, StorageModeFlags flags = StorageModeFlags.None) { + ThrowIfLeaveOpen(flags); + FileStream stream = File.Open(fileName, mode); - return Open(stream); + return Open(stream, flags); } public static RootStorage OpenRead(string fileName, StorageModeFlags flags = StorageModeFlags.None) { + ThrowIfLeaveOpen(flags); + FileStream stream = File.OpenRead(fileName); - return Open(stream); + return Open(stream, flags); } public static RootStorage Open(Stream stream, StorageModeFlags flags = StorageModeFlags.None) From 0c596c094cb7c82607288bfbb2665d63f6778298 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 11 Nov 2024 21:54:06 +1300 Subject: [PATCH 094/134] Rename Directories to DirectoryEntries --- OpenMcdf3/{Directories.cs => DirectoryEntries.cs} | 4 ++-- OpenMcdf3/DirectoryEntryEnumerator.cs | 4 ++-- OpenMcdf3/DirectoryTree.cs | 4 ++-- OpenMcdf3/DirectoryTreeEnumerator.cs | 4 ++-- OpenMcdf3/FatStream.cs | 2 +- OpenMcdf3/IOContext.cs | 12 ++++++------ OpenMcdf3/MiniFatStream.cs | 2 +- OpenMcdf3/Storage.cs | 6 +++--- 8 files changed, 19 insertions(+), 19 deletions(-) rename OpenMcdf3/{Directories.cs => DirectoryEntries.cs} (97%) diff --git a/OpenMcdf3/Directories.cs b/OpenMcdf3/DirectoryEntries.cs similarity index 97% rename from OpenMcdf3/Directories.cs rename to OpenMcdf3/DirectoryEntries.cs index f96513d9..e217737b 100644 --- a/OpenMcdf3/Directories.cs +++ b/OpenMcdf3/DirectoryEntries.cs @@ -1,13 +1,13 @@ namespace OpenMcdf3; -internal sealed class Directories : IDisposable +internal sealed class DirectoryEntries : IDisposable { private readonly IOContext ioContext; private readonly FatChainEnumerator fatChainEnumerator; private readonly DirectoryEntryEnumerator directoryEntryEnumerator; private readonly int entriesPerSector; - public Directories(IOContext ioContext) + public DirectoryEntries(IOContext ioContext) { this.ioContext = ioContext; fatChainEnumerator = new FatChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf3/DirectoryEntryEnumerator.cs index fe979d06..1e73a062 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf3/DirectoryEntryEnumerator.cs @@ -7,12 +7,12 @@ namespace OpenMcdf3; /// internal sealed class DirectoryEntryEnumerator : IEnumerator { - private readonly Directories directories; + private readonly DirectoryEntries directories; private bool start = true; private uint index = uint.MaxValue; private DirectoryEntry? current; - public DirectoryEntryEnumerator(Directories directories) + public DirectoryEntryEnumerator(DirectoryEntries directories) { this.directories = directories; } diff --git a/OpenMcdf3/DirectoryTree.cs b/OpenMcdf3/DirectoryTree.cs index c8077b9a..f3cfccec 100644 --- a/OpenMcdf3/DirectoryTree.cs +++ b/OpenMcdf3/DirectoryTree.cs @@ -11,10 +11,10 @@ internal enum RelationType Directory, } - private readonly Directories directories; + private readonly DirectoryEntries directories; private readonly DirectoryEntry root; - public DirectoryTree(Directories directories, DirectoryEntry root) + public DirectoryTree(DirectoryEntries directories, DirectoryEntry root) { this.directories = directories; this.root = root; diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf3/DirectoryTreeEnumerator.cs index e11a4898..9b1eae0c 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf3/DirectoryTreeEnumerator.cs @@ -7,12 +7,12 @@ namespace OpenMcdf3; /// internal sealed class DirectoryTreeEnumerator : IEnumerator { - private readonly Directories directories; + private readonly DirectoryEntries directories; private readonly DirectoryEntry root; private readonly Stack stack = new(); DirectoryEntry? current; - internal DirectoryTreeEnumerator(Directories directories, DirectoryEntry root) + internal DirectoryTreeEnumerator(DirectoryEntries directories, DirectoryEntry root) { this.directories = directories; this.root = root; diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf3/FatStream.cs index 652b76b4..bc0dd9a4 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf3/FatStream.cs @@ -63,7 +63,7 @@ public override void Flush() if (isDirty) { - ioContext.Directories.Write(DirectoryEntry); + ioContext.DirectoryEntries.Write(DirectoryEntry); isDirty = false; } diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf3/IOContext.cs index 992a9fbc..7966c611 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf3/IOContext.cs @@ -37,7 +37,7 @@ public CfbBinaryWriter Writer public Fat Fat { get; } - public Directories Directories { get; } + public DirectoryEntries DirectoryEntries { get; } public DirectoryEntry RootEntry { get; } @@ -102,19 +102,19 @@ public IOContext(Stream stream, Version version, IOContextFlags contextFlags = I writer = new(actualStream); Fat = new(this); - Directories = new(this); + DirectoryEntries = new(this); if (contextFlags.HasFlag(IOContextFlags.Create)) { - RootEntry = Directories.CreateOrRecycleDirectoryEntry(); + RootEntry = DirectoryEntries.CreateOrRecycleDirectoryEntry(); RootEntry.RecycleRoot(); WriteHeader(); - Directories.Write(RootEntry); + DirectoryEntries.Write(RootEntry); } else { - RootEntry = Directories.GetDictionaryEntry(0); + RootEntry = DirectoryEntries.GetDictionaryEntry(0); } } @@ -126,7 +126,7 @@ public void Dispose() miniStream?.Dispose(); miniFat?.Dispose(); - Directories.Dispose(); + DirectoryEntries.Dispose(); Fat.Dispose(); writer?.Dispose(); Reader.Dispose(); diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf3/MiniFatStream.cs index b90ee021..b3a1beea 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf3/MiniFatStream.cs @@ -55,7 +55,7 @@ public override void Flush() if (isDirty) { - ioContext.Directories.Write(DirectoryEntry); + ioContext.DirectoryEntries.Write(DirectoryEntry); isDirty = false; } diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index b9c581b9..e7a77572 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -16,7 +16,7 @@ internal Storage(IOContext ioContext, DirectoryEntry directoryEntry) throw new ArgumentException("DirectoryEntry must be a Storage or Root.", nameof(directoryEntry)); this.ioContext = ioContext; - directoryTree = new(ioContext.Directories, directoryEntry); + directoryTree = new(ioContext.DirectoryEntries, directoryEntry); DirectoryEntry = directoryEntry; } @@ -38,7 +38,7 @@ public IEnumerable EnumerateEntries(StorageType type) IEnumerable EnumerateDirectoryEntries() { - using DirectoryTreeEnumerator treeEnumerator = new(ioContext.Directories, DirectoryEntry); + using DirectoryTreeEnumerator treeEnumerator = new(ioContext.DirectoryEntries, DirectoryEntry); while (treeEnumerator.MoveNext()) { yield return treeEnumerator.Current; @@ -50,7 +50,7 @@ IEnumerable EnumerateDirectoryEntries(StorageType type) => Enume DirectoryEntry AddDirectoryEntry(StorageType storageType, string name) { - DirectoryEntry entry = ioContext.Directories.CreateOrRecycleDirectoryEntry(); + DirectoryEntry entry = ioContext.DirectoryEntries.CreateOrRecycleDirectoryEntry(); entry.Recycle(storageType, name); directoryTree.Add(entry); return entry; From 5e873e8df5d8a289d028476c50e6eb24418a8b71 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 11 Nov 2024 22:43:27 +1300 Subject: [PATCH 095/134] Port OpenMcdf Extensions (OLE) --- OpenMcdf.Ole/CodePages.cs | 6 + OpenMcdf.Ole/Common.cs | 8 + OpenMcdf.Ole/DefaultPropertyFactory.cs | 7 + OpenMcdf.Ole/DictionaryEntry.cs | 61 ++ OpenMcdf.Ole/DictionaryProperty.cs | 132 ++++ .../DocumentSummaryInfoPropertyFactory.cs | 16 + OpenMcdf.Ole/IBinarySerializable.cs | 7 + OpenMcdf.Ole/IDictionaryProperty.cs | 6 + OpenMcdf.Ole/IProperty.cs | 21 + OpenMcdf.Ole/ITypedPropertyValue.cs | 21 + OpenMcdf.Ole/Identifiers.cs | 27 + OpenMcdf.Ole/OlePropertiesContainer.cs | 316 +++++++++ OpenMcdf.Ole/OleProperty.cs | 64 ++ OpenMcdf.Ole/OpenMcdf.Ole.csproj | 16 + OpenMcdf.Ole/PropertyContext.cs | 14 + OpenMcdf.Ole/PropertyFactory.cs | 665 ++++++++++++++++++ OpenMcdf.Ole/PropertyIdentifierAndOffset.cs | 19 + OpenMcdf.Ole/PropertySet.cs | 32 + OpenMcdf.Ole/PropertySetStream.cs | 235 +++++++ OpenMcdf.Ole/ProperyIdentifiers.cs | 48 ++ OpenMcdf.Ole/TypedPropertyValue.cs | 155 ++++ OpenMcdf.Ole/VTPropertyType.cs | 44 ++ OpenMcdf.Ole/WellKnownFormatIdentifiers.cs | 11 + .../StructuredStorageBenchmarks.cs | 2 +- OpenMcdf3.sln | 6 + 25 files changed, 1938 insertions(+), 1 deletion(-) create mode 100644 OpenMcdf.Ole/CodePages.cs create mode 100644 OpenMcdf.Ole/Common.cs create mode 100644 OpenMcdf.Ole/DefaultPropertyFactory.cs create mode 100644 OpenMcdf.Ole/DictionaryEntry.cs create mode 100644 OpenMcdf.Ole/DictionaryProperty.cs create mode 100644 OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs create mode 100644 OpenMcdf.Ole/IBinarySerializable.cs create mode 100644 OpenMcdf.Ole/IDictionaryProperty.cs create mode 100644 OpenMcdf.Ole/IProperty.cs create mode 100644 OpenMcdf.Ole/ITypedPropertyValue.cs create mode 100644 OpenMcdf.Ole/Identifiers.cs create mode 100644 OpenMcdf.Ole/OlePropertiesContainer.cs create mode 100644 OpenMcdf.Ole/OleProperty.cs create mode 100644 OpenMcdf.Ole/OpenMcdf.Ole.csproj create mode 100644 OpenMcdf.Ole/PropertyContext.cs create mode 100644 OpenMcdf.Ole/PropertyFactory.cs create mode 100644 OpenMcdf.Ole/PropertyIdentifierAndOffset.cs create mode 100644 OpenMcdf.Ole/PropertySet.cs create mode 100644 OpenMcdf.Ole/PropertySetStream.cs create mode 100644 OpenMcdf.Ole/ProperyIdentifiers.cs create mode 100644 OpenMcdf.Ole/TypedPropertyValue.cs create mode 100644 OpenMcdf.Ole/VTPropertyType.cs create mode 100644 OpenMcdf.Ole/WellKnownFormatIdentifiers.cs diff --git a/OpenMcdf.Ole/CodePages.cs b/OpenMcdf.Ole/CodePages.cs new file mode 100644 index 00000000..1f5a22c7 --- /dev/null +++ b/OpenMcdf.Ole/CodePages.cs @@ -0,0 +1,6 @@ +namespace OpenMcdf.Ole; + +internal static class CodePages +{ + public const int WinUnicode = 0x04B0; +} diff --git a/OpenMcdf.Ole/Common.cs b/OpenMcdf.Ole/Common.cs new file mode 100644 index 00000000..3ee12e48 --- /dev/null +++ b/OpenMcdf.Ole/Common.cs @@ -0,0 +1,8 @@ +namespace OpenMcdf.Ole; + +public enum PropertyDimensions +{ + IsScalar, + IsVector, + IsArray +} diff --git a/OpenMcdf.Ole/DefaultPropertyFactory.cs b/OpenMcdf.Ole/DefaultPropertyFactory.cs new file mode 100644 index 00000000..4b432a7e --- /dev/null +++ b/OpenMcdf.Ole/DefaultPropertyFactory.cs @@ -0,0 +1,7 @@ +namespace OpenMcdf.Ole; + +// The default property factory. +internal sealed class DefaultPropertyFactory : PropertyFactory +{ + public static PropertyFactory Instance { get; } = new DefaultPropertyFactory(); +} diff --git a/OpenMcdf.Ole/DictionaryEntry.cs b/OpenMcdf.Ole/DictionaryEntry.cs new file mode 100644 index 00000000..68c04b6e --- /dev/null +++ b/OpenMcdf.Ole/DictionaryEntry.cs @@ -0,0 +1,61 @@ +using System.Text; + +namespace OpenMcdf.Ole; + +public class DictionaryEntry +{ + readonly int codePage; + + public DictionaryEntry(int codePage) + { + this.codePage = codePage; + } + + public uint PropertyIdentifier { get; set; } + public int Length { get; set; } + public string Name => GetName(); + + private byte[] nameBytes; + + public void Read(BinaryReader br) + { + PropertyIdentifier = br.ReadUInt32(); + Length = br.ReadInt32(); + + if (codePage != CodePages.WinUnicode) + { + nameBytes = br.ReadBytes(Length); + } + else + { + nameBytes = br.ReadBytes(Length << 1); + + int m = Length * 2 % 4; + if (m > 0) + br.ReadBytes(m); + } + } + + public void Write(BinaryWriter bw) + { + bw.Write(PropertyIdentifier); + bw.Write(Length); + bw.Write(nameBytes); + + //if (codePage == CP_WINUNICODE) + // int m = Length % 4; + + //if (m > 0) + // for (int i = 0; i < m; i++) + // bw.Write((byte)m); + } + + private string GetName() + { + string result = Encoding.GetEncoding(codePage).GetString(nameBytes); + + result = result.Trim('\0'); + + return result; + } +} diff --git a/OpenMcdf.Ole/DictionaryProperty.cs b/OpenMcdf.Ole/DictionaryProperty.cs new file mode 100644 index 00000000..1089307c --- /dev/null +++ b/OpenMcdf.Ole/DictionaryProperty.cs @@ -0,0 +1,132 @@ +using System.Text; + +namespace OpenMcdf.Ole; + +public class DictionaryProperty : IDictionaryProperty +{ + private readonly int codePage; + + public DictionaryProperty(int codePage) + { + this.codePage = codePage; + entries = new Dictionary(); + } + + public PropertyType PropertyType => PropertyType.DictionaryProperty; + + private Dictionary entries; + + public object Value + { + get => entries; + set => entries = (Dictionary)value; + } + + public void Read(BinaryReader br) + { + long curPos = br.BaseStream.Position; + + uint numEntries = br.ReadUInt32(); + + for (uint i = 0; i < numEntries; i++) + { + DictionaryEntry de = new DictionaryEntry(codePage); + + de.Read(br); + entries.Add(de.PropertyIdentifier, de.Name); + } + + int m = (int)(br.BaseStream.Position - curPos) % 4; + + if (m > 0) + { + for (int i = 0; i < m; i++) + { + br.ReadByte(); + } + } + } + + /// + /// Write the dictionary and all its values into the specified . + /// + /// + /// Based on the Microsoft specifications at https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-oleps/99127b7f-c440-4697-91a4-c853086d6b33 + /// + /// A writer to write the dictionary into. + public void Write(BinaryWriter bw) + { + long curPos = bw.BaseStream.Position; + + bw.Write(entries.Count); + + foreach (KeyValuePair kv in entries) + { + WriteEntry(bw, kv.Key, kv.Value); + } + + int size = (int)(bw.BaseStream.Position - curPos); + WritePaddingIfNeeded(bw, size); + } + + // Write a single entry to the dictionary, and handle and required null termination and padding. + private void WriteEntry(BinaryWriter bw, uint propertyIdentifier, string name) + { + // Write the PropertyIdentifier + bw.Write(propertyIdentifier); + + // Encode string data with the current codepage + byte[] nameBytes = Encoding.GetEncoding(codePage).GetBytes(name); + uint byteLength = (uint)nameBytes.Length; + + // If the code page is WINUNICODE, write the length as the number of characters and pad the length to a multiple of 4 bytes + // Otherwise, write the length as the number of bytes and don't pad. + // In either case, the string must be null terminated + if (codePage == CodePages.WinUnicode) + { + bool addNullTerminator = + byteLength == 0 || nameBytes[byteLength - 1] != '\0' || nameBytes[byteLength - 2] != '\0'; + + if (addNullTerminator) + byteLength += 2; + + bw.Write(byteLength / 2); + bw.Write(nameBytes); + + if (addNullTerminator) + { + bw.Write((byte)0); + bw.Write((byte)0); + } + + WritePaddingIfNeeded(bw, (int)byteLength); + } + else + { + bool addNullTerminator = + byteLength == 0 || nameBytes[byteLength - 1] != '\0'; + + if (addNullTerminator) + byteLength += 1; + + bw.Write(byteLength); + bw.Write(nameBytes); + + if (addNullTerminator) + bw.Write((byte)0); + } + } + + // Write as much padding as needed to pad fieldLength to a multiple of 4 bytes + private void WritePaddingIfNeeded(BinaryWriter bw, int fieldLength) + { + int m = fieldLength % 4; + + if (m > 0) + { + for (int i = 0; i < 4 - m; i++) // padding + bw.Write((byte)0); + } + } +} + diff --git a/OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs b/OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs new file mode 100644 index 00000000..18602bd8 --- /dev/null +++ b/OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs @@ -0,0 +1,16 @@ +namespace OpenMcdf.Ole; + +// A separate factory for DocumentSummaryInformation properties, to handle special cases with unaligned strings. +internal sealed class DocumentSummaryInfoPropertyFactory : PropertyFactory +{ + public static PropertyFactory Instance { get; } = new DocumentSummaryInfoPropertyFactory(); + + protected override ITypedPropertyValue CreateLpstrProperty(VTPropertyType vType, int codePage, uint propertyIdentifier, bool isVariant) + { + // PIDDSI_HEADINGPAIR and PIDDSI_DOCPARTS use unaligned (unpadded) strings - the others are padded + if (propertyIdentifier == 0x0000000C || propertyIdentifier == 0x0000000D) + return new VT_Unaligned_LPSTR_Property(vType, codePage, isVariant); + + return base.CreateLpstrProperty(vType, codePage, propertyIdentifier, isVariant); + } +} diff --git a/OpenMcdf.Ole/IBinarySerializable.cs b/OpenMcdf.Ole/IBinarySerializable.cs new file mode 100644 index 00000000..b04e8a7c --- /dev/null +++ b/OpenMcdf.Ole/IBinarySerializable.cs @@ -0,0 +1,7 @@ +namespace OpenMcdf.Ole; + +public interface IBinarySerializable +{ + void Write(BinaryWriter bw); + void Read(BinaryReader br); +} diff --git a/OpenMcdf.Ole/IDictionaryProperty.cs b/OpenMcdf.Ole/IDictionaryProperty.cs new file mode 100644 index 00000000..26246505 --- /dev/null +++ b/OpenMcdf.Ole/IDictionaryProperty.cs @@ -0,0 +1,6 @@ +namespace OpenMcdf.Ole +{ + public interface IDictionaryProperty : IProperty + { + } +} \ No newline at end of file diff --git a/OpenMcdf.Ole/IProperty.cs b/OpenMcdf.Ole/IProperty.cs new file mode 100644 index 00000000..9d44eed4 --- /dev/null +++ b/OpenMcdf.Ole/IProperty.cs @@ -0,0 +1,21 @@ +namespace OpenMcdf.Ole; + +public enum PropertyType +{ + TypedPropertyValue = 0, + DictionaryProperty = 1 +} + +public interface IProperty : IBinarySerializable +{ + object Value + { + get; + set; + } + + PropertyType PropertyType + { + get; + } +} diff --git a/OpenMcdf.Ole/ITypedPropertyValue.cs b/OpenMcdf.Ole/ITypedPropertyValue.cs new file mode 100644 index 00000000..9c316265 --- /dev/null +++ b/OpenMcdf.Ole/ITypedPropertyValue.cs @@ -0,0 +1,21 @@ +namespace OpenMcdf.Ole +{ + public interface ITypedPropertyValue : IProperty + { + VTPropertyType VTType + { + get; + //set; + } + + PropertyDimensions PropertyDimensions + { + get; + } + + bool IsVariant + { + get; + } + } +} diff --git a/OpenMcdf.Ole/Identifiers.cs b/OpenMcdf.Ole/Identifiers.cs new file mode 100644 index 00000000..860297d0 --- /dev/null +++ b/OpenMcdf.Ole/Identifiers.cs @@ -0,0 +1,27 @@ +namespace OpenMcdf.Ole; + +public static class Identifiers +{ + public static string GetDescription(uint identifier, ContainerType map, IDictionary customDict = null) + { + IDictionary nameDictionary = customDict; + + if (nameDictionary is null) + { + switch (map) + { + case ContainerType.SummaryInfo: + nameDictionary = PropertyIdentifiers.SummaryInfo; + break; + case ContainerType.DocumentSummaryInfo: + nameDictionary = PropertyIdentifiers.DocumentSummaryInfo; + break; + } + } + + if (nameDictionary?.TryGetValue(identifier, out string? value) == true) + return value; + + return $"0x{identifier:x8}"; + } +} diff --git a/OpenMcdf.Ole/OlePropertiesContainer.cs b/OpenMcdf.Ole/OlePropertiesContainer.cs new file mode 100644 index 00000000..073afbdf --- /dev/null +++ b/OpenMcdf.Ole/OlePropertiesContainer.cs @@ -0,0 +1,316 @@ +namespace OpenMcdf.Ole; + +public enum ContainerType +{ + AppSpecific = 0, + SummaryInfo = 1, + DocumentSummaryInfo = 2, + UserDefinedProperties = 3, + GlobalInfo = 4, + ImageContents = 5, + ImageInfo = 6 +} + +public class OlePropertiesContainer +{ + public Dictionary PropertyNames; + + public OlePropertiesContainer UserDefinedProperties { get; private set; } + + public bool HasUserDefinedProperties { get; private set; } + + public ContainerType ContainerType { get; } + private Guid? FmtID0 { get; } + + public PropertyContext Context { get; } + + private readonly List properties = new(); + internal Stream cfStream; + + /* + Property name Property ID PID Type + Codepage PID_CODEPAGE 1 VT_I2 + Title PID_TITLE 2 VT_LPSTR + Subject PID_SUBJECT 3 VT_LPSTR + Author PID_AUTHOR 4 VT_LPSTR + Keywords PID_KEYWORDS 5 VT_LPSTR + Comments PID_COMMENTS 6 VT_LPSTR + Template PID_TEMPLATE 7 VT_LPSTR + Last Saved By PID_LASTAUTHOR 8 VT_LPSTR + Revision Number PID_REVNUMBER 9 VT_LPSTR + Last Printed PID_LASTPRINTED 11 VT_FILETIME + Create Time/Date PID_CREATE_DTM 12 VT_FILETIME + Last Save Time/Date PID_LASTSAVE_DTM 13 VT_FILETIME + Page Count PID_PAGECOUNT 14 VT_I4 + Word Count PID_WORDCOUNT 15 VT_I4 + Character Count PID_CHARCOUNT 16 VT_I4 + Creating Application PID_APPNAME 18 VT_LPSTR + Security PID_SECURITY 19 VT_I4 + */ + public class SummaryInfoProperties + { + public short CodePage { get; set; } + public string Title { get; set; } + public string Subject { get; set; } + public string Author { get; set; } + public string KeyWords { get; set; } + public string Comments { get; set; } + public string Template { get; set; } + public string LastSavedBy { get; set; } + public string RevisionNumber { get; set; } + public DateTime LastPrinted { get; set; } + public DateTime CreateTime { get; set; } + public DateTime LastSavedTime { get; set; } + public int PageCount { get; set; } + public int WordCount { get; set; } + public int CharacterCount { get; set; } + public string CreatingApplication { get; set; } + public int Security { get; set; } + } + + public static OlePropertiesContainer CreateNewSummaryInfo(SummaryInfoProperties sumInfoProps) + { + return null; + } + + public OlePropertiesContainer(int codePage, ContainerType containerType) + { + Context = new PropertyContext + { + CodePage = codePage, + Behavior = Behavior.CaseInsensitive + }; + + ContainerType = containerType; + } + + internal OlePropertiesContainer(Stream cfStream) + { + PropertySetStream pStream = new(); + + this.cfStream = cfStream; + + using BinaryReader reader = new(cfStream); + pStream.Read(reader); + + if (pStream.FMTID0 == WellKnownFormatIdentifiers.SummaryInformation) + ContainerType = ContainerType.SummaryInfo; + else if (pStream.FMTID0 == WellKnownFormatIdentifiers.DocSummaryInformation) + ContainerType = ContainerType.DocumentSummaryInfo; + else + ContainerType = ContainerType.AppSpecific; + FmtID0 = pStream.FMTID0; + + PropertyNames = (Dictionary?)pStream.PropertySet0.Properties + .FirstOrDefault(p => p.PropertyType == PropertyType.DictionaryProperty)?.Value; + + Context = new PropertyContext() + { + CodePage = pStream.PropertySet0.PropertyContext.CodePage + }; + + for (int i = 0; i < pStream.PropertySet0.Properties.Count; i++) + { + if (pStream.PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier == 0) continue; + //if (pStream.PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier == 1) continue; + //if (pStream.PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier == 0x80000000) continue; + + var p = (ITypedPropertyValue)pStream.PropertySet0.Properties[i]; + PropertyIdentifierAndOffset poi = pStream.PropertySet0.PropertyIdentifierAndOffsets[i]; + + var op = new OleProperty(this) + { + VTType = p.VTType, + PropertyIdentifier = pStream.PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier, + Value = p.Value + }; + + properties.Add(op); + } + + if (pStream.NumPropertySets == 2) + { + UserDefinedProperties = new OlePropertiesContainer(pStream.PropertySet1.PropertyContext.CodePage, ContainerType.UserDefinedProperties); + HasUserDefinedProperties = true; + + for (int i = 0; i < pStream.PropertySet1.Properties.Count; i++) + { + if (pStream.PropertySet1.PropertyIdentifierAndOffsets[i].PropertyIdentifier is 0 or 0x80000000) + continue; + + var p = (ITypedPropertyValue)pStream.PropertySet1.Properties[i]; + PropertyIdentifierAndOffset poi = pStream.PropertySet1.PropertyIdentifierAndOffsets[i]; + + OleProperty op = new(UserDefinedProperties) + { + VTType = p.VTType, + PropertyIdentifier = pStream.PropertySet1.PropertyIdentifierAndOffsets[i].PropertyIdentifier, + Value = p.Value + }; + + UserDefinedProperties.properties.Add(op); + } + + var existingPropertyNames = (Dictionary?)pStream.PropertySet1.Properties + .FirstOrDefault(p => p.PropertyType == PropertyType.DictionaryProperty)?.Value; + + UserDefinedProperties.PropertyNames = existingPropertyNames ?? new Dictionary(); + } + } + + public IList Properties => properties; + + public OleProperty NewProperty(VTPropertyType vtPropertyType, uint propertyIdentifier, string? propertyName = null) + { + //throw new NotImplementedException("API Unstable - Work in progress - Milestone 2.3.0.0"); + var op = new OleProperty(this) + { + VTType = vtPropertyType, + PropertyIdentifier = propertyIdentifier + }; + + return op; + } + + public void AddProperty(OleProperty property) + { + //throw new NotImplementedException("API Unstable - Work in progress - Milestone 2.3.0.0"); + properties.Add(property); + } + + public void RemoveProperty(uint propertyIdentifier) + { + //throw new NotImplementedException("API Unstable - Work in progress - Milestone 2.3.0.0"); + OleProperty? toRemove = properties.FirstOrDefault(o => o.PropertyIdentifier == propertyIdentifier); + + if (toRemove != null) + properties.Remove(toRemove); + } + + /// + /// Create a new UserDefinedProperties container within this container. + /// + /// + /// Only containers of type DocumentSummaryInfo can contain user defined properties. + /// + /// The code page to use for the user defined properties. + /// The UserDefinedProperties container. + /// If this container is a type that doesn't suppose user defined properties. + public OlePropertiesContainer CreateUserDefinedProperties(int codePage) + { + // Only the DocumentSummaryInfo stream can contain a UserDefinedProperties + if (ContainerType != ContainerType.DocumentSummaryInfo) + throw new InvalidOperationException($"Only a DocumentSummaryInfo can contain user defined properties. Current container type is {ContainerType}"); + + // Create the container, and add the codepage to the initial set of properties + UserDefinedProperties = new OlePropertiesContainer(codePage, ContainerType.UserDefinedProperties) + { + PropertyNames = new Dictionary() + }; + + var op = new OleProperty(UserDefinedProperties) + { + VTType = VTPropertyType.VT_I2, + PropertyIdentifier = 1, + Value = (short)codePage + }; + + UserDefinedProperties.properties.Add(op); + HasUserDefinedProperties = true; + + return UserDefinedProperties; + } + + public void Save(Stream cfStream) + { + //throw new NotImplementedException("API Unstable - Work in progress - Milestone 2.3.0.0"); + //properties.Sort((a, b) => a.PropertyIdentifier.CompareTo(b.PropertyIdentifier)); + + using BinaryWriter bw = new(cfStream); + + Guid fmtId0 = FmtID0 ?? (ContainerType == ContainerType.SummaryInfo ? WellKnownFormatIdentifiers.SummaryInformation : WellKnownFormatIdentifiers.DocSummaryInformation); + + PropertySetStream ps = new() + { + ByteOrder = 0xFFFE, + Version = 0, + SystemIdentifier = 0x00020006, + CLSID = Guid.Empty, + + NumPropertySets = 1, + + FMTID0 = fmtId0, + Offset0 = 0, + + FMTID1 = Guid.Empty, + Offset1 = 0, + + PropertySet0 = new PropertySet + { + NumProperties = (uint)Properties.Count, + PropertyIdentifierAndOffsets = new List(), + Properties = new List(), + PropertyContext = Context + } + }; + + // If we're writing an AppSpecific property set and have property names, then add a dictionary property + if (ContainerType == ContainerType.AppSpecific && PropertyNames != null && PropertyNames.Count > 0) + { + AddDictionaryPropertyToPropertySet(PropertyNames, ps.PropertySet0); + ps.PropertySet0.NumProperties += 1; + } + + PropertyFactory factory = + ContainerType == ContainerType.DocumentSummaryInfo ? DocumentSummaryInfoPropertyFactory.Instance : DefaultPropertyFactory.Instance; + + foreach (OleProperty op in Properties) + { + ITypedPropertyValue p = factory.NewProperty(op.VTType, Context.CodePage, op.PropertyIdentifier); + p.Value = op.Value; + ps.PropertySet0.Properties.Add(p); + ps.PropertySet0.PropertyIdentifierAndOffsets.Add(new PropertyIdentifierAndOffset() { PropertyIdentifier = op.PropertyIdentifier, Offset = 0 }); + } + + if (HasUserDefinedProperties) + { + ps.NumPropertySets = 2; + + ps.PropertySet1 = new PropertySet + { + // Number of user defined properties, plus 1 for the name dictionary + NumProperties = (uint)UserDefinedProperties.Properties.Count + 1, + PropertyIdentifierAndOffsets = new List(), + Properties = new List(), + PropertyContext = UserDefinedProperties.Context + }; + + ps.FMTID1 = WellKnownFormatIdentifiers.UserDefinedProperties; + ps.Offset1 = 0; + + // Add the dictionary containing the property names + AddDictionaryPropertyToPropertySet(UserDefinedProperties.PropertyNames, ps.PropertySet1); + + // Add the properties themselves + foreach (OleProperty op in UserDefinedProperties.Properties) + { + ITypedPropertyValue p = DefaultPropertyFactory.Instance.NewProperty(op.VTType, ps.PropertySet1.PropertyContext.CodePage, op.PropertyIdentifier); + p.Value = op.Value; + ps.PropertySet1.Properties.Add(p); + ps.PropertySet1.PropertyIdentifierAndOffsets.Add(new PropertyIdentifierAndOffset() { PropertyIdentifier = op.PropertyIdentifier, Offset = 0 }); + } + } + + ps.Write(bw); + } + + private void AddDictionaryPropertyToPropertySet(Dictionary propertyNames, PropertySet propertySet) + { + IDictionaryProperty dictionaryProperty = new DictionaryProperty(propertySet.PropertyContext.CodePage) + { + Value = propertyNames + }; + propertySet.Properties.Add(dictionaryProperty); + propertySet.PropertyIdentifierAndOffsets.Add(new PropertyIdentifierAndOffset() { PropertyIdentifier = 0, Offset = 0 }); + } +} diff --git a/OpenMcdf.Ole/OleProperty.cs b/OpenMcdf.Ole/OleProperty.cs new file mode 100644 index 00000000..6d8cefbf --- /dev/null +++ b/OpenMcdf.Ole/OleProperty.cs @@ -0,0 +1,64 @@ +namespace OpenMcdf.Ole; + +public class OleProperty +{ + private readonly OlePropertiesContainer container; + + internal OleProperty(OlePropertiesContainer container) + { + this.container = container; + } + + public string PropertyName => DecodePropertyIdentifier(); + + private string DecodePropertyIdentifier() + { + return Identifiers.GetDescription(PropertyIdentifier, container.ContainerType, container.PropertyNames); + } + + //public string Description { get { return description; } + public uint PropertyIdentifier { get; internal set; } + + public VTPropertyType VTType + { + get; + internal set; + } + + object value; + + public object Value + { + get + { + switch (VTType) + { + case VTPropertyType.VT_LPSTR: + case VTPropertyType.VT_LPWSTR: + if (value is string str && !string.IsNullOrEmpty(str)) + return str.Trim('\0'); + break; + default: + return value; + } + + return value; + } + set => this.value = value; + } + + public override bool Equals(object obj) + { + if (obj is not OleProperty other) + return false; + + return other.PropertyIdentifier == PropertyIdentifier; + } + + public override int GetHashCode() + { + return (int)PropertyIdentifier; + } + + public override string ToString() => $"{PropertyName} - {VTType} - {Value}"; +} diff --git a/OpenMcdf.Ole/OpenMcdf.Ole.csproj b/OpenMcdf.Ole/OpenMcdf.Ole.csproj new file mode 100644 index 00000000..101f0831 --- /dev/null +++ b/OpenMcdf.Ole/OpenMcdf.Ole.csproj @@ -0,0 +1,16 @@ + + + netstandard2.0;net8.0 + 12.0 + enable + enable + + + + + + + + + + \ No newline at end of file diff --git a/OpenMcdf.Ole/PropertyContext.cs b/OpenMcdf.Ole/PropertyContext.cs new file mode 100644 index 00000000..abbe0a2d --- /dev/null +++ b/OpenMcdf.Ole/PropertyContext.cs @@ -0,0 +1,14 @@ +namespace OpenMcdf.Ole; + +public enum Behavior +{ + CaseSensitive, + CaseInsensitive +} + +public class PropertyContext +{ + public int CodePage { get; set; } + public Behavior Behavior { get; set; } + public uint Locale { get; set; } +} diff --git a/OpenMcdf.Ole/PropertyFactory.cs b/OpenMcdf.Ole/PropertyFactory.cs new file mode 100644 index 00000000..c2c201ac --- /dev/null +++ b/OpenMcdf.Ole/PropertyFactory.cs @@ -0,0 +1,665 @@ +using System.Text; + +namespace OpenMcdf.Ole; + +internal abstract class PropertyFactory +{ + static PropertyFactory() + { +#if NETSTANDARD2_0_OR_GREATER + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); +#endif + } + + public ITypedPropertyValue NewProperty(VTPropertyType vType, int codePage, uint propertyIdentifier, bool isVariant = false) + { + ITypedPropertyValue pr = (VTPropertyType)((ushort)vType & 0x00FF) switch + { + VTPropertyType.VT_I1 => new VT_I1_Property(vType, isVariant), + VTPropertyType.VT_I2 => new VT_I2_Property(vType, isVariant), + VTPropertyType.VT_I4 => new VT_I4_Property(vType, isVariant), + VTPropertyType.VT_R4 => new VT_R4_Property(vType, isVariant), + VTPropertyType.VT_R8 => new VT_R8_Property(vType, isVariant), + VTPropertyType.VT_CY => new VT_CY_Property(vType, isVariant), + VTPropertyType.VT_DATE => new VT_DATE_Property(vType, isVariant), + VTPropertyType.VT_INT => new VT_INT_Property(vType, isVariant), + VTPropertyType.VT_UINT => new VT_UINT_Property(vType, isVariant), + VTPropertyType.VT_UI1 => new VT_UI1_Property(vType, isVariant), + VTPropertyType.VT_UI2 => new VT_UI2_Property(vType, isVariant), + VTPropertyType.VT_UI4 => new VT_UI4_Property(vType, isVariant), + VTPropertyType.VT_UI8 => new VT_UI8_Property(vType, isVariant), + VTPropertyType.VT_BSTR => new VT_LPSTR_Property(vType, codePage, isVariant), + VTPropertyType.VT_LPSTR => CreateLpstrProperty(vType, codePage, propertyIdentifier, isVariant), + VTPropertyType.VT_LPWSTR => new VT_LPWSTR_Property(vType, codePage, isVariant), + VTPropertyType.VT_FILETIME => new VT_FILETIME_Property(vType, isVariant), + VTPropertyType.VT_DECIMAL => new VT_DECIMAL_Property(vType, isVariant), + VTPropertyType.VT_BOOL => new VT_BOOL_Property(vType, isVariant), + VTPropertyType.VT_EMPTY => new VT_EMPTY_Property(vType, isVariant), + VTPropertyType.VT_VARIANT_VECTOR => new VT_VariantVector(vType, codePage, isVariant, this, propertyIdentifier), + VTPropertyType.VT_CF => new VT_CF_Property(vType, isVariant), + VTPropertyType.VT_BLOB_OBJECT or VTPropertyType.VT_BLOB => new VT_BLOB_Property(vType, isVariant), + VTPropertyType.VT_CLSID => new VT_CLSID_Property(vType, isVariant), + _ => throw new Exception("Unrecognized property type"), + }; + return pr; + } + + protected virtual ITypedPropertyValue CreateLpstrProperty(VTPropertyType vType, int codePage, uint propertyIdentifier, bool isVariant) + { + return new VT_LPSTR_Property(vType, codePage, isVariant); + } + + #region Property implementations + + private class VT_EMPTY_Property : TypedPropertyValue + { + public VT_EMPTY_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override object ReadScalarValue(BinaryReader br) + { + return null; + } + + public override void WriteScalarValue(BinaryWriter bw, object pValue) + { + } + } + + private class VT_I1_Property : TypedPropertyValue + { + public VT_I1_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override sbyte ReadScalarValue(BinaryReader br) + { + sbyte r = br.ReadSByte(); + return r; + } + + public override void WriteScalarValue(BinaryWriter bw, sbyte pValue) + { + bw.Write(pValue); + } + } + + private class VT_UI1_Property : TypedPropertyValue + { + public VT_UI1_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override byte ReadScalarValue(BinaryReader br) + { + byte r = br.ReadByte(); + return r; + } + + public override void WriteScalarValue(BinaryWriter bw, byte pValue) + { + bw.Write(pValue); + } + } + + private class VT_UI4_Property : TypedPropertyValue + { + public VT_UI4_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override uint ReadScalarValue(BinaryReader br) + { + uint r = br.ReadUInt32(); + return r; + } + + public override void WriteScalarValue(BinaryWriter bw, uint pValue) + { + bw.Write(pValue); + } + } + + private class VT_UI8_Property : TypedPropertyValue + { + public VT_UI8_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override ulong ReadScalarValue(BinaryReader br) + { + ulong r = br.ReadUInt64(); + return r; + } + + public override void WriteScalarValue(BinaryWriter bw, ulong pValue) + { + bw.Write(pValue); + } + } + + private class VT_I2_Property : TypedPropertyValue + { + public VT_I2_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override short ReadScalarValue(BinaryReader br) + { + short r = br.ReadInt16(); + return r; + } + + public override void WriteScalarValue(BinaryWriter bw, short pValue) + { + bw.Write(pValue); + } + } + + private class VT_UI2_Property : TypedPropertyValue + { + public VT_UI2_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override ushort ReadScalarValue(BinaryReader br) + { + ushort r = br.ReadUInt16(); + return r; + } + + public override void WriteScalarValue(BinaryWriter bw, ushort pValue) + { + bw.Write(pValue); + } + } + + private class VT_I4_Property : TypedPropertyValue + { + public VT_I4_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override int ReadScalarValue(BinaryReader br) + { + int r = br.ReadInt32(); + return r; + } + + public override void WriteScalarValue(BinaryWriter bw, int pValue) + { + bw.Write(pValue); + } + } + + private class VT_I8_Property : TypedPropertyValue + { + public VT_I8_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override long ReadScalarValue(BinaryReader br) + { + long r = br.ReadInt64(); + return r; + } + + public override void WriteScalarValue(BinaryWriter bw, long pValue) + { + bw.Write(pValue); + } + } + + private class VT_INT_Property : TypedPropertyValue + { + public VT_INT_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override int ReadScalarValue(BinaryReader br) + { + int r = br.ReadInt32(); + return r; + } + + public override void WriteScalarValue(BinaryWriter bw, int pValue) + { + bw.Write(pValue); + } + } + + private class VT_UINT_Property : TypedPropertyValue + { + public VT_UINT_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override uint ReadScalarValue(BinaryReader br) + { + uint r = br.ReadUInt32(); + return r; + } + + public override void WriteScalarValue(BinaryWriter bw, uint pValue) + { + bw.Write(pValue); + } + } + + private class VT_R4_Property : TypedPropertyValue + { + public VT_R4_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override float ReadScalarValue(BinaryReader br) + { + float r = br.ReadSingle(); + return r; + } + + public override void WriteScalarValue(BinaryWriter bw, float pValue) + { + bw.Write(pValue); + } + } + + private class VT_R8_Property : TypedPropertyValue + { + public VT_R8_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override double ReadScalarValue(BinaryReader br) + { + double r = br.ReadDouble(); + return r; + } + + public override void WriteScalarValue(BinaryWriter bw, double pValue) + { + bw.Write(pValue); + } + } + + private class VT_CY_Property : TypedPropertyValue + { + public VT_CY_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override long ReadScalarValue(BinaryReader br) + { + long temp = br.ReadInt64(); + + long tmp = temp /= 10000; + + return tmp; + } + + public override void WriteScalarValue(BinaryWriter bw, long pValue) + { + bw.Write(pValue * 10000); + } + } + + private class VT_DATE_Property : TypedPropertyValue + { + public VT_DATE_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override DateTime ReadScalarValue(BinaryReader br) + { + double temp = br.ReadDouble(); + + return DateTime.FromOADate(temp); + } + + public override void WriteScalarValue(BinaryWriter bw, DateTime pValue) + { + bw.Write(pValue.ToOADate()); + } + } + + protected class VT_LPSTR_Property : TypedPropertyValue + { + private byte[] data; + private readonly int codePage; + + public VT_LPSTR_Property(VTPropertyType vType, int codePage, bool isVariant) : base(vType, isVariant) + { + this.codePage = codePage; + } + + public override string ReadScalarValue(BinaryReader br) + { + uint size = br.ReadUInt32(); + data = br.ReadBytes((int)size); + + string result = Encoding.GetEncoding(codePage).GetString(data); + //result = result.Trim(new char[] { '\0' }); + + //if (this.codePage == CodePages.CP_WINUNICODE) + //{ + // result = result.Substring(0, result.Length - 2); + //} + //else + //{ + // result = result.Substring(0, result.Length - 1); + //} + + return result; + } + + public override void WriteScalarValue(BinaryWriter bw, string pValue) + { + //bool addNullTerminator = true; + + if (string.IsNullOrEmpty(pValue)) //|| String.IsNullOrEmpty(pValue.Trim(new char[] { '\0' }))) + { + bw.Write((uint)0); + } + else if (codePage == CodePages.WinUnicode) + { + data = Encoding.GetEncoding(codePage).GetBytes(pValue); + + //if (data.Length >= 2 && data[data.Length - 2] == '\0' && data[data.Length - 1] == '\0') + // addNullTerminator = false; + + uint dataLength = (uint)data.Length; + + //if (addNullTerminator) + dataLength += 2; // null terminator \u+0000 + + // var mod = dataLength % 4; // pad to multiple of 4 bytes + + bw.Write(dataLength); // datalength of string + null char (unicode) + bw.Write(data); // string + + //if (addNullTerminator) + //{ + bw.Write('\0'); // first byte of null unicode char + bw.Write('\0'); // second byte of null unicode char + //} + + //for (int i = 0; i < (4 - mod); i++) // padding + // bw.Write('\0'); + } + else + { + data = Encoding.GetEncoding(codePage).GetBytes(pValue); + + //if (data.Length >= 1 && data[data.Length - 1] == '\0') + // addNullTerminator = false; + + uint dataLength = (uint)data.Length; + + //if (addNullTerminator) + dataLength += 1; // null terminator \u+0000 + + uint mod = dataLength % 4; // pad to multiple of 4 bytes + + bw.Write(dataLength); // datalength of string + null char (unicode) + bw.Write(data); // string + + //if (addNullTerminator) + //{ + bw.Write('\0'); // null terminator'\0' + //} + + //for (int i = 0; i < (4 - mod); i++) // padding + // bw.Write('\0'); + } + } + } + + protected class VT_Unaligned_LPSTR_Property : VT_LPSTR_Property + { + public VT_Unaligned_LPSTR_Property(VTPropertyType vType, int codePage, bool isVariant) : base(vType, codePage, isVariant) + { + NeedsPadding = false; + } + } + + private class VT_LPWSTR_Property : TypedPropertyValue + { + private byte[] data; + private readonly int codePage; + + public VT_LPWSTR_Property(VTPropertyType vType, int codePage, bool isVariant) : base(vType, isVariant) + { + this.codePage = codePage; + } + + public override string ReadScalarValue(BinaryReader br) + { + uint nChars = br.ReadUInt32(); + data = br.ReadBytes((int)((nChars - 1) * 2)); //WChar- null terminator + br.ReadBytes(2); // Skip null terminator + string result = Encoding.Unicode.GetString(data); + //result = result.Trim(new char[] { '\0' }); + + return result; + } + + public override void WriteScalarValue(BinaryWriter bw, string pValue) + { + data = Encoding.Unicode.GetBytes(pValue); + + // The written data length field is the number of characters (not bytes) and must include a null terminator + // add a null terminator if there isn't one already + int byteLength = data.Length; + + //bool addNullTerminator = + // byteLength == 0 || data[byteLength - 1] != '\0' || data[byteLength - 2] != '\0'; + + //if (addNullTerminator) + byteLength += 2; + + bw.Write((uint)byteLength / 2); + bw.Write(data); + + //if (addNullTerminator) + //{ + bw.Write((byte)0); + bw.Write((byte)0); + //} + + //var mod = byteLength % 4; // pad to multiple of 4 bytes + //for (int i = 0; i < (4 - mod); i++) // padding + // bw.Write('\0'); + } + } + + private class VT_FILETIME_Property : TypedPropertyValue + { + public VT_FILETIME_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override DateTime ReadScalarValue(BinaryReader br) + { + long tmp = br.ReadInt64(); + + return DateTime.FromFileTimeUtc(tmp); + } + + public override void WriteScalarValue(BinaryWriter bw, DateTime pValue) + { + bw.Write(pValue.ToFileTimeUtc()); + } + } + + private class VT_DECIMAL_Property : TypedPropertyValue + { + public VT_DECIMAL_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override decimal ReadScalarValue(BinaryReader br) + { + decimal d; + + br.ReadInt16(); // wReserved + byte scale = br.ReadByte(); + byte sign = br.ReadByte(); + + uint u = br.ReadUInt32(); + d = Convert.ToDecimal(Math.Pow(2, 64)) * u; + d += br.ReadUInt64(); + + if (sign != 0) + d = -d; + d /= 10 << scale; + + propertyValue = d; + return d; + } + + public override void WriteScalarValue(BinaryWriter bw, decimal pValue) + { + int[] parts = decimal.GetBits(pValue); + + bool sign = (parts[3] & 0x80000000) != 0; + byte scale = (byte)((parts[3] >> 16) & 0x7F); + + bw.Write((short)0); + bw.Write(scale); + bw.Write(sign ? (byte)0 : (byte)1); + + bw.Write(parts[2]); + bw.Write(parts[1]); + bw.Write(parts[0]); + } + } + + private class VT_BOOL_Property : TypedPropertyValue + { + public VT_BOOL_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override bool ReadScalarValue(BinaryReader br) + { + propertyValue = br.ReadUInt16() == 0xFFFF ? true : false; + return (bool)propertyValue; + //br.ReadUInt16();//padding + } + + public override void WriteScalarValue(BinaryWriter bw, bool pValue) + { + bw.Write(pValue ? (ushort)0xFFFF : (ushort)0); + } + } + + private class VT_CF_Property : TypedPropertyValue + { + public VT_CF_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override object ReadScalarValue(BinaryReader br) + { + uint size = br.ReadUInt32(); + byte[] data = br.ReadBytes((int)size); + return data; + //br.ReadUInt16();//padding + } + + public override void WriteScalarValue(BinaryWriter bw, object pValue) + { + if (pValue is not byte[] r) + { + bw.Write(0u); + } + else + { + bw.Write((uint)r.Length); + bw.Write(r); + } + } + } + + private class VT_BLOB_Property : TypedPropertyValue + { + public VT_BLOB_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override object ReadScalarValue(BinaryReader br) + { + uint size = br.ReadUInt32(); + byte[] data = br.ReadBytes((int)size); + return data; + } + + public override void WriteScalarValue(BinaryWriter bw, object pValue) + { + if (pValue is not byte[] r) + { + bw.Write(0u); + } + else + { + bw.Write((uint)r.Length); + bw.Write(r); + } + } + } + + private class VT_CLSID_Property : TypedPropertyValue + { + public VT_CLSID_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) + { + } + + public override object ReadScalarValue(BinaryReader br) + { + byte[] data = br.ReadBytes(16); + return new Guid(data); + } + + public override void WriteScalarValue(BinaryWriter bw, object pValue) + { + if (pValue is byte[] r) + bw.Write(r); + } + } + + private class VT_VariantVector : TypedPropertyValue + { + private readonly int codePage; + private readonly PropertyFactory factory; + private readonly uint propertyIdentifier; + + public VT_VariantVector(VTPropertyType vType, int codePage, bool isVariant, PropertyFactory factory, uint propertyIdentifier) : base(vType, isVariant) + { + this.codePage = codePage; + this.factory = factory; + this.propertyIdentifier = propertyIdentifier; + NeedsPadding = false; + } + + public override object ReadScalarValue(BinaryReader br) + { + VTPropertyType vType = (VTPropertyType)br.ReadUInt16(); + br.ReadUInt16(); // Ushort Padding + + ITypedPropertyValue p = factory.NewProperty(vType, codePage, propertyIdentifier, true); + p.Read(br); + return p; + } + + public override void WriteScalarValue(BinaryWriter bw, object pValue) + { + ITypedPropertyValue p = (ITypedPropertyValue)pValue; + + p.Write(bw); + } + } + + #endregion + +} diff --git a/OpenMcdf.Ole/PropertyIdentifierAndOffset.cs b/OpenMcdf.Ole/PropertyIdentifierAndOffset.cs new file mode 100644 index 00000000..0e2e0c88 --- /dev/null +++ b/OpenMcdf.Ole/PropertyIdentifierAndOffset.cs @@ -0,0 +1,19 @@ +namespace OpenMcdf.Ole; + +public class PropertyIdentifierAndOffset : IBinarySerializable +{ + public uint PropertyIdentifier { get; set; } + public uint Offset { get; set; } + + public void Read(BinaryReader br) + { + PropertyIdentifier = br.ReadUInt32(); + Offset = br.ReadUInt32(); + } + + public void Write(BinaryWriter bw) + { + bw.Write(PropertyIdentifier); + bw.Write(Offset); + } +} diff --git a/OpenMcdf.Ole/PropertySet.cs b/OpenMcdf.Ole/PropertySet.cs new file mode 100644 index 00000000..dcc8f990 --- /dev/null +++ b/OpenMcdf.Ole/PropertySet.cs @@ -0,0 +1,32 @@ +namespace OpenMcdf.Ole; + +internal sealed class PropertySet +{ + public PropertyContext PropertyContext + { + get; set; + } + + public uint Size { get; set; } + + public uint NumProperties { get; set; } + + public List PropertyIdentifierAndOffsets { get; set; } = new List(); + + public List Properties { get; set; } = new List(); + + public void LoadContext(int propertySetOffset, BinaryReader br) + { + long currPos = br.BaseStream.Position; + + PropertyContext = new PropertyContext(); + int codePageOffset = (int)(propertySetOffset + PropertyIdentifierAndOffsets.Where(pio => pio.PropertyIdentifier == 1).First().Offset); + br.BaseStream.Seek(codePageOffset, SeekOrigin.Begin); + + VTPropertyType vType = (VTPropertyType)br.ReadUInt16(); + br.ReadUInt16(); // Ushort Padding + PropertyContext.CodePage = (ushort)br.ReadInt16(); + + br.BaseStream.Position = currPos; + } +} diff --git a/OpenMcdf.Ole/PropertySetStream.cs b/OpenMcdf.Ole/PropertySetStream.cs new file mode 100644 index 00000000..3c3acb97 --- /dev/null +++ b/OpenMcdf.Ole/PropertySetStream.cs @@ -0,0 +1,235 @@ +namespace OpenMcdf.Ole; + +internal sealed class PropertySetStream +{ + public ushort ByteOrder { get; set; } + public ushort Version { get; set; } + public uint SystemIdentifier { get; set; } + public Guid CLSID { get; set; } + public uint NumPropertySets { get; set; } + public Guid FMTID0 { get; set; } + public uint Offset0 { get; set; } + public Guid FMTID1 { get; set; } + public uint Offset1 { get; set; } + public PropertySet PropertySet0 { get; set; } + public PropertySet PropertySet1 { get; set; } + + //private SummaryInfoMap map; + + public PropertySetStream() + { + } + + public void Read(BinaryReader br) + { + ByteOrder = br.ReadUInt16(); + Version = br.ReadUInt16(); + SystemIdentifier = br.ReadUInt32(); + CLSID = new Guid(br.ReadBytes(16)); + NumPropertySets = br.ReadUInt32(); + FMTID0 = new Guid(br.ReadBytes(16)); + Offset0 = br.ReadUInt32(); + + if (NumPropertySets == 2) + { + FMTID1 = new Guid(br.ReadBytes(16)); + Offset1 = br.ReadUInt32(); + } + + PropertySet0 = new PropertySet + { + Size = br.ReadUInt32(), + NumProperties = br.ReadUInt32() + }; + + // Create appropriate property factory based on the stream type + PropertyFactory factory = FMTID0 == WellKnownFormatIdentifiers.DocSummaryInformation ? DocumentSummaryInfoPropertyFactory.Instance : DefaultPropertyFactory.Instance; + + // Read property offsets (P0) + for (int i = 0; i < PropertySet0.NumProperties; i++) + { + PropertyIdentifierAndOffset pio = new() + { + PropertyIdentifier = br.ReadUInt32(), + Offset = br.ReadUInt32() + }; + PropertySet0.PropertyIdentifierAndOffsets.Add(pio); + } + + PropertySet0.LoadContext((int)Offset0, br); //Read CodePage, Locale + + // Read properties (P0) + for (int i = 0; i < PropertySet0.NumProperties; i++) + { + br.BaseStream.Seek(Offset0 + PropertySet0.PropertyIdentifierAndOffsets[i].Offset, SeekOrigin.Begin); + PropertySet0.Properties.Add(ReadProperty(PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier, PropertySet0.PropertyContext.CodePage, br, factory)); + } + + if (NumPropertySets == 2) + { + br.BaseStream.Seek(Offset1, SeekOrigin.Begin); + PropertySet1 = new PropertySet + { + Size = br.ReadUInt32(), + NumProperties = br.ReadUInt32() + }; + + // Read property offsets + for (int i = 0; i < PropertySet1.NumProperties; i++) + { + PropertyIdentifierAndOffset pio = new() + { + PropertyIdentifier = br.ReadUInt32(), + Offset = br.ReadUInt32() + }; + PropertySet1.PropertyIdentifierAndOffsets.Add(pio); + } + + PropertySet1.LoadContext((int)Offset1, br); + + // Read properties + for (int i = 0; i < PropertySet1.NumProperties; i++) + { + br.BaseStream.Seek(Offset1 + PropertySet1.PropertyIdentifierAndOffsets[i].Offset, SeekOrigin.Begin); + PropertySet1.Properties.Add(ReadProperty(PropertySet1.PropertyIdentifierAndOffsets[i].PropertyIdentifier, PropertySet1.PropertyContext.CodePage, br, DefaultPropertyFactory.Instance)); + } + } + } + + private class OffsetContainer + { + public int OffsetPS { get; set; } + + public List PropertyIdentifierOffsets { get; set; } + public List PropertyOffsets { get; set; } + + public OffsetContainer() + { + PropertyOffsets = new List(); + PropertyIdentifierOffsets = new List(); + } + } + + public void Write(BinaryWriter bw) + { + var oc0 = new OffsetContainer(); + var oc1 = new OffsetContainer(); + + bw.Write(ByteOrder); + bw.Write(Version); + bw.Write(SystemIdentifier); + bw.Write(CLSID.ToByteArray()); + bw.Write(NumPropertySets); + bw.Write(FMTID0.ToByteArray()); + bw.Write(Offset0); + + if (NumPropertySets == 2) + { + bw.Write(FMTID1.ToByteArray()); + bw.Write(Offset1); + } + + oc0.OffsetPS = (int)bw.BaseStream.Position; + bw.Write(PropertySet0.Size); + bw.Write(PropertySet0.NumProperties); + + // w property offsets + for (int i = 0; i < PropertySet0.NumProperties; i++) + { + oc0.PropertyIdentifierOffsets.Add(bw.BaseStream.Position); //Offset of 4 to Offset value + PropertySet0.PropertyIdentifierAndOffsets[i].Write(bw); + } + + for (int i = 0; i < PropertySet0.NumProperties; i++) + { + oc0.PropertyOffsets.Add(bw.BaseStream.Position); + PropertySet0.Properties[i].Write(bw); + } + + long padding0 = bw.BaseStream.Position % 4; + + if (padding0 > 0) + { + for (int p = 0; p < 4 - padding0; p++) + bw.Write((byte)0); + } + + int size0 = (int)(bw.BaseStream.Position - oc0.OffsetPS); + + if (NumPropertySets == 2) + { + oc1.OffsetPS = (int)bw.BaseStream.Position; + + bw.Write(PropertySet1.Size); + bw.Write(PropertySet1.NumProperties); + + // w property offsets + for (int i = 0; i < PropertySet1.PropertyIdentifierAndOffsets.Count; i++) + { + oc1.PropertyIdentifierOffsets.Add(bw.BaseStream.Position); //Offset of 4 to Offset value + PropertySet1.PropertyIdentifierAndOffsets[i].Write(bw); + } + + for (int i = 0; i < PropertySet1.NumProperties; i++) + { + oc1.PropertyOffsets.Add(bw.BaseStream.Position); + PropertySet1.Properties[i].Write(bw); + } + + int size1 = (int)(bw.BaseStream.Position - oc1.OffsetPS); + + bw.Seek(oc1.OffsetPS, SeekOrigin.Begin); + bw.Write(size1); + } + + bw.Seek(oc0.OffsetPS, SeekOrigin.Begin); + bw.Write(size0); + + int shiftO1 = 2 + 2 + 4 + 16 + 4 + 16; //OFFSET0 + bw.Seek(shiftO1, SeekOrigin.Begin); + bw.Write(oc0.OffsetPS); + + if (NumPropertySets == 2) + { + bw.Seek(shiftO1 + 4 + 16, SeekOrigin.Begin); + bw.Write(oc1.OffsetPS); + } + + //----------- + + for (int i = 0; i < PropertySet0.PropertyIdentifierAndOffsets.Count; i++) + { + bw.Seek((int)oc0.PropertyIdentifierOffsets[i] + 4, SeekOrigin.Begin); //Offset of 4 to Offset value + bw.Write((int)(oc0.PropertyOffsets[i] - oc0.OffsetPS)); + } + + if (PropertySet1 != null) + { + for (int i = 0; i < PropertySet1.PropertyIdentifierAndOffsets.Count; i++) + { + bw.Seek((int)oc1.PropertyIdentifierOffsets[i] + 4, SeekOrigin.Begin); //Offset of 4 to Offset value + bw.Write((int)(oc1.PropertyOffsets[i] - oc1.OffsetPS)); + } + } + } + + private static IProperty ReadProperty(uint propertyIdentifier, int codePage, BinaryReader br, PropertyFactory factory) + { + if (propertyIdentifier != 0) + { + var vType = (VTPropertyType)br.ReadUInt16(); + br.ReadUInt16(); // Ushort Padding + + ITypedPropertyValue pr = factory.NewProperty(vType, codePage, propertyIdentifier); + pr.Read(br); + + return pr; + } + else + { + DictionaryProperty dictionaryProperty = new(codePage); + dictionaryProperty.Read(br); + return dictionaryProperty; + } + } +} diff --git a/OpenMcdf.Ole/ProperyIdentifiers.cs b/OpenMcdf.Ole/ProperyIdentifiers.cs new file mode 100644 index 00000000..6dd53bf3 --- /dev/null +++ b/OpenMcdf.Ole/ProperyIdentifiers.cs @@ -0,0 +1,48 @@ +using System.Collections.Immutable; + +namespace OpenMcdf.Ole; + +public static class PropertyIdentifiers +{ + public static ImmutableDictionary SummaryInfo { get; } = new Dictionary() + { + {0x00000001, "CodePageString" }, + {0x00000002, "PIDSI_TITLE" }, + {0x00000003, "PIDSI_SUBJECT" }, + {0x00000004, "PIDSI_AUTHOR" }, + {0x00000005, "PIDSI_KEYWORDS" }, + {0x00000006, "PIDSI_COMMENTS" }, + {0x00000007, "PIDSI_TEMPLATE" }, + {0x00000008, "PIDSI_LASTAUTHOR" }, + {0x00000009, "PIDSI_REVNUMBER" }, + {0x00000012, "PIDSI_APPNAME" }, + {0x0000000A, "PIDSI_EDITTIME" }, + {0x0000000B, "PIDSI_LASTPRINTED" }, + {0x0000000C, "PIDSI_CREATE_DTM" }, + {0x0000000D, "PIDSI_LASTSAVE_DTM" }, + {0x0000000E, "PIDSI_PAGECOUNT" }, + {0x0000000F, "PIDSI_WORDCOUNT" }, + {0x00000010, "PIDSI_CHARCOUNT" }, + {0x00000013, "PIDSI_DOC_SECURITY" } + }.ToImmutableDictionary(); + + public static ImmutableDictionary DocumentSummaryInfo { get; } = new Dictionary() + { + {0x00000001, "CodePageString" }, + {0x00000002, "PIDDSI_CATEGORY" }, + {0x00000003, "PIDDSI_PRESFORMAT" }, + {0x00000004, "PIDDSI_BYTECOUNT" }, + {0x00000005, "PIDDSI_LINECOUNT" }, + {0x00000006, "PIDDSI_PARCOUNT" }, + {0x00000007, "PIDDSI_SLIDECOUNT" }, + {0x00000008, "PIDDSI_NOTECOUNT" }, + {0x00000009, "PIDDSI_HIDDENCOUNT" }, + {0x0000000A, "PIDDSI_MMCLIPCOUNT" }, + {0x0000000B, "PIDDSI_SCALE" }, + {0x0000000C, "PIDDSI_HEADINGPAIR" }, + {0x0000000D, "PIDDSI_DOCPARTS" }, + {0x0000000E, "PIDDSI_MANAGER" }, + {0x0000000F, "PIDDSI_COMPANY" }, + {0x00000010, "PIDDSI_LINKSDIRTY" } + }.ToImmutableDictionary(); +} diff --git a/OpenMcdf.Ole/TypedPropertyValue.cs b/OpenMcdf.Ole/TypedPropertyValue.cs new file mode 100644 index 00000000..80d0e447 --- /dev/null +++ b/OpenMcdf.Ole/TypedPropertyValue.cs @@ -0,0 +1,155 @@ +namespace OpenMcdf.Ole; + +internal abstract class TypedPropertyValue : ITypedPropertyValue +{ + private readonly VTPropertyType _VTType; + + public PropertyType PropertyType => PropertyType.TypedPropertyValue; + + public VTPropertyType VTType => _VTType; + + protected object propertyValue; + + public TypedPropertyValue(VTPropertyType vtType, bool isVariant = false) + { + _VTType = vtType; + PropertyDimensions = CheckPropertyDimensions(vtType); + IsVariant = isVariant; + } + + public PropertyDimensions PropertyDimensions { get; } = PropertyDimensions.IsScalar; + + public bool IsVariant { get; } + + protected virtual bool NeedsPadding { get; set; } = true; + + private PropertyDimensions CheckPropertyDimensions(VTPropertyType vtType) + { + if ((((ushort)vtType) & 0x1000) != 0) + return PropertyDimensions.IsVector; + if ((((ushort)vtType) & 0x2000) != 0) + return PropertyDimensions.IsArray; + return PropertyDimensions.IsScalar; + } + + public virtual object Value + { + get => propertyValue; + + set => propertyValue = value; + } + + public abstract T ReadScalarValue(BinaryReader br); + + public void Read(BinaryReader br) + { + long currentPos = br.BaseStream.Position; + + switch (PropertyDimensions) + { + case PropertyDimensions.IsScalar: + { + propertyValue = ReadScalarValue(br); + int size = (int)(br.BaseStream.Position - currentPos); + + int m = size % 4; + + if (m > 0 && NeedsPadding) + br.ReadBytes(4 - m); // padding + } + + break; + + case PropertyDimensions.IsVector: + { + uint nItems = br.ReadUInt32(); + + List res = new List(); + + for (int i = 0; i < nItems; i++) + { + T s = ReadScalarValue(br); + + res.Add(s); + + // The padding in a vector can be per-item + int itemSize = (int)(br.BaseStream.Position - currentPos); + + int pad = itemSize % 4; + if (pad > 0 && NeedsPadding) + br.ReadBytes(4 - pad); // padding + } + + propertyValue = res; + int size = (int)(br.BaseStream.Position - currentPos); + + int m = size % 4; + if (m > 0 && NeedsPadding) + br.ReadBytes(4 - m); // padding + } + + break; + default: + break; + } + } + + public abstract void WriteScalarValue(BinaryWriter bw, T pValue); + + public void Write(BinaryWriter bw) + { + long currentPos = bw.BaseStream.Position; + int size; + int m; + switch (PropertyDimensions) + { + case PropertyDimensions.IsScalar: + + bw.Write((ushort)_VTType); + bw.Write((ushort)0); + + WriteScalarValue(bw, (T)propertyValue); + size = (int)(bw.BaseStream.Position - currentPos); + m = size % 4; + + if (m > 0 && NeedsPadding) + { + for (int i = 0; i < 4 - m; i++) // padding + bw.Write((byte)0); + } + + break; + + case PropertyDimensions.IsVector: + + bw.Write((ushort)_VTType); + bw.Write((ushort)0); + bw.Write((uint)((List)propertyValue).Count); + + for (int i = 0; i < ((List)propertyValue).Count; i++) + { + WriteScalarValue(bw, ((List)propertyValue)[i]); + + size = (int)(bw.BaseStream.Position - currentPos); + m = size % 4; + + if (m > 0 && NeedsPadding) + { + for (int q = 0; q < 4 - m; q++) // padding + bw.Write((byte)0); + } + } + + size = (int)(bw.BaseStream.Position - currentPos); + m = size % 4; + + if (m > 0 && NeedsPadding) + { + for (int i = 0; i < 4 - m; i++) // padding + bw.Write((byte)0); + } + + break; + } + } +} diff --git a/OpenMcdf.Ole/VTPropertyType.cs b/OpenMcdf.Ole/VTPropertyType.cs new file mode 100644 index 00000000..f6084dd3 --- /dev/null +++ b/OpenMcdf.Ole/VTPropertyType.cs @@ -0,0 +1,44 @@ +namespace OpenMcdf.Ole; + +/// +/// VARENUM +/// +public enum VTPropertyType : ushort +{ + VT_EMPTY = 0x0000, + VT_NULL = 0x0001, + VT_I2 = 0x0002, + VT_I4 = 0x0003, + VT_R4 = 0x0004, + VT_R8 = 0x0005, + VT_CY = 0x0006, + VT_DATE = 0x0007, + VT_BSTR = 0x0008, + VT_ERROR = 0x000A, + VT_BOOL = 0x000B, + VT_DECIMAL = 0x000E, + VT_I1 = 0x0010, + VT_UI1 = 0x0011, + VT_UI2 = 0x0012, + VT_UI4 = 0x0013, + VT_I8 = 0x0014, // MUST be an 8-byte signed integer. + VT_UI8 = 0x0015, // MUST be an 8-byte unsigned integer. + VT_INT = 0x0016, // MUST be a 4-byte signed integer. + VT_UINT = 0x0017, // MUST be a 4-byte unsigned integer. + VT_LPSTR = 0x001E, // MUST be a CodePageString. + VT_LPWSTR = 0x001F, // MUST be a UnicodeString. + VT_FILETIME = 0x0040, // MUST be a FILETIME (Packet Version). + VT_BLOB = 0x0041, // MUST be a BLOB. + VT_STREAM = 0x0042, // MUST be an IndirectPropertyName. The storage representing the (non-simple) property set MUST have a stream element with this name. + VT_STORAGE = 0x0043, // MUST be an IndirectPropertyName. The storage representing the (non-simple) property set MUST have a storage element with this name. + VT_STREAMED_OBJECT = 0x0044, // MUST be an IndirectPropertyName. The storage representing the (non-simple) property set MUST have a stream element with this name. + VT_STORED_OBJECT = 0x0045, // MUST be an IndirectPropertyName. The storage representing the (non-simple) property set MUST have a storage element with this name. + VT_BLOB_OBJECT = 0x0046, // MUST be a BLOB. + VT_CF = 0x0047, // MUST be a ClipboardData. + VT_CLSID = 0x0048, // MUST be a GUID (Packet Version) + VT_VERSIONED_STREAM = 0x0049, // MUST be a versioned Stream, NOT allowed in simple property + VT_VECTOR_HEADER = 0x1000, //--- NOT NORMATIVE + VT_ARRAY_HEADER = 0x2000, //--- NOT NORMATIVE + VT_VARIANT_VECTOR = 0x000C, //--- NOT NORMATIVE + VT_VARIANT_ARRAY = 0x200C, //--- NOT NORMATIVE +} diff --git a/OpenMcdf.Ole/WellKnownFormatIdentifiers.cs b/OpenMcdf.Ole/WellKnownFormatIdentifiers.cs new file mode 100644 index 00000000..9161fdbe --- /dev/null +++ b/OpenMcdf.Ole/WellKnownFormatIdentifiers.cs @@ -0,0 +1,11 @@ +namespace OpenMcdf.Ole; + +public static class WellKnownFormatIdentifiers +{ + public static readonly Guid SummaryInformation = new("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"); + public static readonly Guid DocSummaryInformation = new("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"); + public static readonly Guid UserDefinedProperties = new("{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"); + public static readonly Guid GlobalInfo = new("{56616F00-C154-11CE-8553-00AA00A1F95B}"); + public static readonly Guid ImageContents = new("{56616400-C154-11CE-8553-00AA00A1F95B}"); + public static readonly Guid ImageInfo = new("{56616500-C154-11CE-8553-00AA00A1F95B}"); +} diff --git a/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs b/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs index c83cde10..eda2ffd3 100644 --- a/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs +++ b/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs @@ -2,7 +2,7 @@ namespace OpenMcdf3.Benchmarks; -internal class StructuredStorageBenchmarks +internal static class StructuredStorageBenchmarks { public static void ReadStream(string fileName, byte[] buffer) { diff --git a/OpenMcdf3.sln b/OpenMcdf3.sln index 7f045797..6ddb516e 100644 --- a/OpenMcdf3.sln +++ b/OpenMcdf3.sln @@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3.Perf", "OpenMcdf3 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructuredStorage", "StructuredStorage\StructuredStorage.csproj", "{D7861D73-B42C-403E-9B9E-F921BC70F0D3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Ole", "OpenMcdf.Ole\OpenMcdf.Ole.csproj", "{06FFA945-128E-43FA-B541-38987BC1E0D5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,6 +47,10 @@ Global {D7861D73-B42C-403E-9B9E-F921BC70F0D3}.Debug|Any CPU.Build.0 = Debug|Any CPU {D7861D73-B42C-403E-9B9E-F921BC70F0D3}.Release|Any CPU.ActiveCfg = Release|Any CPU {D7861D73-B42C-403E-9B9E-F921BC70F0D3}.Release|Any CPU.Build.0 = Release|Any CPU + {06FFA945-128E-43FA-B541-38987BC1E0D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06FFA945-128E-43FA-B541-38987BC1E0D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06FFA945-128E-43FA-B541-38987BC1E0D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06FFA945-128E-43FA-B541-38987BC1E0D5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 30bc811480c8350ff39cdfa2346ee4b5b1fd3476 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Mon, 11 Nov 2024 23:33:55 +1300 Subject: [PATCH 096/134] Add StructuredStorageExplorer --- OpenMcdf.Ole/IDictionaryProperty.cs | 7 +- OpenMcdf.Ole/ITypedPropertyValue.cs | 29 +- OpenMcdf.Ole/OlePropertiesContainer.cs | 6 +- .../OpenMcdf3.Benchmarks.csproj | 2 +- OpenMcdf3.Tests/StorageTests.cs | 2 +- OpenMcdf3.sln | 6 + OpenMcdf3/CfbStream.cs | 2 + OpenMcdf3/DirectoryEntry.cs | 11 +- OpenMcdf3/EntryInfo.cs | 14 +- OpenMcdf3/Storage.cs | 10 +- OpenMcdf3/System/Index.cs | 245 +++++---- OpenMcdf3/System/NullableAttributes.cs | 307 ++++++----- OpenMcdf3/System/Range.cs | 189 ++++--- .../MainForm.Designer.cs | 466 +++++++++++++++++ StructuredStorageExplorer/MainForm.cs | 487 ++++++++++++++++++ StructuredStorageExplorer/MainForm.resx | 141 +++++ StructuredStorageExplorer/OpenMcdf.snk | Bin 0 -> 596 bytes .../PreferencesForm.Designer.cs | 106 ++++ StructuredStorageExplorer/PreferencesForm.cs | 31 ++ .../PreferencesForm.resx | 120 +++++ StructuredStorageExplorer/Program.cs | 15 + .../Properties/Resources.Designer.cs | 123 +++++ .../Properties/Resources.resx | 139 +++++ .../Properties/Settings.Designer.cs | 50 ++ .../Properties/Settings.settings | 12 + .../StreamDataProvider.cs | 160 ++++++ .../StructuredStorageExplorer.csproj | 18 + StructuredStorageExplorer/Utils.cs | 46 ++ StructuredStorageExplorer/app.config | 18 + StructuredStorageExplorer/img/disk.png | Bin 0 -> 620 bytes StructuredStorageExplorer/img/door_out.png | Bin 0 -> 688 bytes StructuredStorageExplorer/img/folder.png | Bin 0 -> 537 bytes StructuredStorageExplorer/img/page_white.png | Bin 0 -> 294 bytes StructuredStorageExplorer/img/storage.png | Bin 0 -> 396 bytes StructuredStorageExplorer/img/stream.png | Bin 0 -> 409 bytes 35 files changed, 2356 insertions(+), 406 deletions(-) create mode 100644 StructuredStorageExplorer/MainForm.Designer.cs create mode 100644 StructuredStorageExplorer/MainForm.cs create mode 100644 StructuredStorageExplorer/MainForm.resx create mode 100644 StructuredStorageExplorer/OpenMcdf.snk create mode 100644 StructuredStorageExplorer/PreferencesForm.Designer.cs create mode 100644 StructuredStorageExplorer/PreferencesForm.cs create mode 100644 StructuredStorageExplorer/PreferencesForm.resx create mode 100644 StructuredStorageExplorer/Program.cs create mode 100644 StructuredStorageExplorer/Properties/Resources.Designer.cs create mode 100644 StructuredStorageExplorer/Properties/Resources.resx create mode 100644 StructuredStorageExplorer/Properties/Settings.Designer.cs create mode 100644 StructuredStorageExplorer/Properties/Settings.settings create mode 100644 StructuredStorageExplorer/StreamDataProvider.cs create mode 100644 StructuredStorageExplorer/StructuredStorageExplorer.csproj create mode 100644 StructuredStorageExplorer/Utils.cs create mode 100644 StructuredStorageExplorer/app.config create mode 100644 StructuredStorageExplorer/img/disk.png create mode 100644 StructuredStorageExplorer/img/door_out.png create mode 100644 StructuredStorageExplorer/img/folder.png create mode 100644 StructuredStorageExplorer/img/page_white.png create mode 100644 StructuredStorageExplorer/img/storage.png create mode 100644 StructuredStorageExplorer/img/stream.png diff --git a/OpenMcdf.Ole/IDictionaryProperty.cs b/OpenMcdf.Ole/IDictionaryProperty.cs index 26246505..8b20b053 100644 --- a/OpenMcdf.Ole/IDictionaryProperty.cs +++ b/OpenMcdf.Ole/IDictionaryProperty.cs @@ -1,6 +1,5 @@ -namespace OpenMcdf.Ole +namespace OpenMcdf.Ole; + +public interface IDictionaryProperty : IProperty { - public interface IDictionaryProperty : IProperty - { - } } \ No newline at end of file diff --git a/OpenMcdf.Ole/ITypedPropertyValue.cs b/OpenMcdf.Ole/ITypedPropertyValue.cs index 9c316265..76198997 100644 --- a/OpenMcdf.Ole/ITypedPropertyValue.cs +++ b/OpenMcdf.Ole/ITypedPropertyValue.cs @@ -1,21 +1,20 @@ -namespace OpenMcdf.Ole +namespace OpenMcdf.Ole; + +public interface ITypedPropertyValue : IProperty { - public interface ITypedPropertyValue : IProperty + VTPropertyType VTType { - VTPropertyType VTType - { - get; - //set; - } + get; + //set; + } - PropertyDimensions PropertyDimensions - { - get; - } + PropertyDimensions PropertyDimensions + { + get; + } - bool IsVariant - { - get; - } + bool IsVariant + { + get; } } diff --git a/OpenMcdf.Ole/OlePropertiesContainer.cs b/OpenMcdf.Ole/OlePropertiesContainer.cs index 073afbdf..9191cd70 100644 --- a/OpenMcdf.Ole/OlePropertiesContainer.cs +++ b/OpenMcdf.Ole/OlePropertiesContainer.cs @@ -1,4 +1,6 @@ -namespace OpenMcdf.Ole; +using OpenMcdf3; + +namespace OpenMcdf.Ole; public enum ContainerType { @@ -84,7 +86,7 @@ public OlePropertiesContainer(int codePage, ContainerType containerType) ContainerType = containerType; } - internal OlePropertiesContainer(Stream cfStream) + public OlePropertiesContainer(CfbStream cfStream) { PropertySetStream pStream = new(); diff --git a/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj b/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj index b8f832e4..219e3058 100644 --- a/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj +++ b/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj @@ -2,7 +2,7 @@ net8.0-windows - 11.0 + 12.0 Exe enable enable diff --git a/OpenMcdf3.Tests/StorageTests.cs b/OpenMcdf3.Tests/StorageTests.cs index 1160186b..de979cf4 100644 --- a/OpenMcdf3.Tests/StorageTests.cs +++ b/OpenMcdf3.Tests/StorageTests.cs @@ -12,7 +12,7 @@ public void Read(string fileName, long storageCount) { using (var rootStorage = RootStorage.OpenRead(fileName)) { - IEnumerable storageEntries = rootStorage.EnumerateEntries(StorageType.Storage); + IEnumerable storageEntries = rootStorage.EnumerateEntries(); Assert.AreEqual(storageCount, storageEntries.Count()); } diff --git a/OpenMcdf3.sln b/OpenMcdf3.sln index 6ddb516e..9c357779 100644 --- a/OpenMcdf3.sln +++ b/OpenMcdf3.sln @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructuredStorage", "Struct EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Ole", "OpenMcdf.Ole\OpenMcdf.Ole.csproj", "{06FFA945-128E-43FA-B541-38987BC1E0D5}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructuredStorageExplorer", "StructuredStorageExplorer\StructuredStorageExplorer.csproj", "{D5DDCC19-80C4-40D2-AEBF-2DA1CCB1D543}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,6 +53,10 @@ Global {06FFA945-128E-43FA-B541-38987BC1E0D5}.Debug|Any CPU.Build.0 = Debug|Any CPU {06FFA945-128E-43FA-B541-38987BC1E0D5}.Release|Any CPU.ActiveCfg = Release|Any CPU {06FFA945-128E-43FA-B541-38987BC1E0D5}.Release|Any CPU.Build.0 = Release|Any CPU + {D5DDCC19-80C4-40D2-AEBF-2DA1CCB1D543}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5DDCC19-80C4-40D2-AEBF-2DA1CCB1D543}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5DDCC19-80C4-40D2-AEBF-2DA1CCB1D543}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5DDCC19-80C4-40D2-AEBF-2DA1CCB1D543}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/OpenMcdf3/CfbStream.cs b/OpenMcdf3/CfbStream.cs index f030356b..5d076bfd 100644 --- a/OpenMcdf3/CfbStream.cs +++ b/OpenMcdf3/CfbStream.cs @@ -25,6 +25,8 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + public EntryInfo EntryInfo => directoryEntry.ToEntryInfo(); + public override bool CanRead => stream.CanRead; public override bool CanSeek => stream.CanSeek; diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index 081fc99c..dadf45df 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -6,7 +6,7 @@ namespace OpenMcdf3; /// /// The storage type of a . /// -public enum StorageType +enum StorageType { Unallocated = 0, Storage = 1, @@ -207,7 +207,14 @@ public void Recycle(StorageType storageType, string name) } } - public EntryInfo ToEntryInfo() => new(NameString, StreamLength); + public EntryType EntryType => Type switch + { + StorageType.Stream => EntryType.Stream, + StorageType.Storage => EntryType.Storage, + _ => throw new InvalidOperationException("Invalid storage type.") + }; + + public EntryInfo ToEntryInfo() => new(EntryType, NameString, StreamLength, CLSID, CreationTime, ModifiedTime); public override string ToString() => $"{Id}: \"{NameString}\""; diff --git a/OpenMcdf3/EntryInfo.cs b/OpenMcdf3/EntryInfo.cs index 87e8f607..c3311498 100644 --- a/OpenMcdf3/EntryInfo.cs +++ b/OpenMcdf3/EntryInfo.cs @@ -1,6 +1,18 @@ namespace OpenMcdf3; +public enum EntryType +{ + Storage, + Stream, +} + /// /// Encapsulates information about an entry in a . /// -public readonly record struct EntryInfo(string Name, long Length); +public readonly record struct EntryInfo( + EntryType Type, + string Name, + long Length, + Guid CLSID, + DateTime CreationTime, + DateTime ModifiedTime); diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf3/Storage.cs index e7a77572..fa3f9722 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf3/Storage.cs @@ -20,6 +20,8 @@ internal Storage(IOContext ioContext, DirectoryEntry directoryEntry) DirectoryEntry = directoryEntry; } + public EntryInfo EntryInfo => DirectoryEntry.ToEntryInfo(); + public IEnumerable EnumerateEntries() { this.ThrowIfDisposed(ioContext.IsDisposed); @@ -28,14 +30,6 @@ public IEnumerable EnumerateEntries() .Select(e => e.ToEntryInfo()); } - public IEnumerable EnumerateEntries(StorageType type) - { - this.ThrowIfDisposed(ioContext.IsDisposed); - - return EnumerateDirectoryEntries(type) - .Select(e => e.ToEntryInfo()); - } - IEnumerable EnumerateDirectoryEntries() { using DirectoryTreeEnumerator treeEnumerator = new(ioContext.DirectoryEntries, DirectoryEntry); diff --git a/OpenMcdf3/System/Index.cs b/OpenMcdf3/System/Index.cs index 52a556be..e99045e8 100644 --- a/OpenMcdf3/System/Index.cs +++ b/OpenMcdf3/System/Index.cs @@ -4,164 +4,163 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -namespace System -{ - /// Represent a type can be used to index a collection either from the start or the end. - /// - /// Index is used by the C# compiler to support the new index syntax - /// - /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; - /// int lastElement = someArray[^1]; // lastElement = 5 - /// - /// +namespace System; + +/// Represent a type can be used to index a collection either from the start or the end. +/// +/// Index is used by the C# compiler to support the new index syntax +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; +/// int lastElement = someArray[^1]; // lastElement = 5 +/// +/// #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - readonly struct Index : IEquatable +readonly struct Index : IEquatable +{ + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Index(int value, bool fromEnd = false) { - private readonly int _value; - - /// Construct an Index using a value and indicating if the index is from the start or from the end. - /// The index value. it has to be zero or positive number. - /// Indicating if the index is from the start or from the end. - /// - /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Index(int value, bool fromEnd = false) + if (value < 0) { - if (value < 0) - { - ThrowValueArgumentOutOfRange_NeedNonNegNumException(); - } - - if (fromEnd) - _value = ~value; - else - _value = value; + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); } - // The following private constructors mainly created for perf reason to avoid the checks - private Index(int value) - { + if (fromEnd) + _value = ~value; + else _value = value; - } + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + _value = value; + } - /// Create an Index pointing at first element. - public static Index Start => new Index(0); + /// Create an Index pointing at first element. + public static Index Start => new Index(0); - /// Create an Index pointing at beyond last element. - public static Index End => new Index(~0); + /// Create an Index pointing at beyond last element. + public static Index End => new Index(~0); - /// Create an Index from the start at the position indicated by the value. - /// The index value from the start. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Index FromStart(int value) + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromStart(int value) + { + if (value < 0) { - if (value < 0) - { - ThrowValueArgumentOutOfRange_NeedNonNegNumException(); - } - - return new Index(value); + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); } - /// Create an Index from the end at the position indicated by the value. - /// The index value from the end. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Index FromEnd(int value) - { - if (value < 0) - { - ThrowValueArgumentOutOfRange_NeedNonNegNumException(); - } + return new Index(value); + } - return new Index(~value); + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromEnd(int value) + { + if (value < 0) + { + ThrowValueArgumentOutOfRange_NeedNonNegNumException(); } - /// Returns the index value. - public int Value + return new Index(~value); + } + + /// Returns the index value. + public int Value + { + get { - get - { - if (_value < 0) - return ~_value; - else - return _value; - } + if (_value < 0) + return ~_value; + else + return _value; } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; - /// Indicates whether the index is from the start or the end. - public bool IsFromEnd => _value < 0; - - /// Calculate the offset from the start using the giving collection length. - /// The length of the collection that the Index will be used with. length has to be a positive value - /// - /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. - /// we don't validate either the returned offset is greater than the input length. - /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and - /// then used to index a collection will get out of range exception which will be same affect as the validation. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int GetOffset(int length) + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int length) + { + int offset = _value; + if (IsFromEnd) { - int offset = _value; - if (IsFromEnd) - { - // offset = length - (~value) - // offset = length + (~(~value) + 1) - // offset = length + value + 1 - - offset += length + 1; - } - return offset; + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; } + return offset; + } - /// Indicates whether the current Index object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals([NotNullWhen(true)] object? value) => value is Index && _value == ((Index)value)._value; + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals([NotNullWhen(true)] object? value) => value is Index && _value == ((Index)value)._value; - /// Indicates whether the current Index object is equal to another Index object. - /// An object to compare with this object - public bool Equals(Index other) => _value == other._value; + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(Index other) => _value == other._value; - /// Returns the hash code for this instance. - public override int GetHashCode() => _value; + /// Returns the hash code for this instance. + public override int GetHashCode() => _value; - /// Converts integer number to an Index. - public static implicit operator Index(int value) => FromStart(value); + /// Converts integer number to an Index. + public static implicit operator Index(int value) => FromStart(value); - /// Converts the value of the current Index object to its equivalent string representation. - public override string ToString() - { - if (IsFromEnd) - return ToStringFromEnd(); + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString() + { + if (IsFromEnd) + return ToStringFromEnd(); - return ((uint)Value).ToString(); - } + return ((uint)Value).ToString(); + } - private static void ThrowValueArgumentOutOfRange_NeedNonNegNumException() - { + private static void ThrowValueArgumentOutOfRange_NeedNonNegNumException() + { #if SYSTEM_PRIVATE_CORELIB - throw new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_NeedNonNegNum); + throw new ArgumentOutOfRangeException("value", SR.ArgumentOutOfRange_NeedNonNegNum); #else - throw new ArgumentOutOfRangeException("value", "value must be non-negative"); + throw new ArgumentOutOfRangeException("value", "value must be non-negative"); #endif - } + } - private string ToStringFromEnd() - { + private string ToStringFromEnd() + { #if (!NETSTANDARD2_0 && !NETFRAMEWORK) - Span span = stackalloc char[11]; // 1 for ^ and 10 for longest possible uint value - bool formatted = ((uint)Value).TryFormat(span.Slice(1), out int charsWritten); - Debug.Assert(formatted); - span[0] = '^'; - return new string(span.Slice(0, charsWritten + 1)); + Span span = stackalloc char[11]; // 1 for ^ and 10 for longest possible uint value + bool formatted = ((uint)Value).TryFormat(span.Slice(1), out int charsWritten); + Debug.Assert(formatted); + span[0] = '^'; + return new string(span.Slice(0, charsWritten + 1)); #else - return '^' + Value.ToString(); + return '^' + Value.ToString(); #endif - } } } \ No newline at end of file diff --git a/OpenMcdf3/System/NullableAttributes.cs b/OpenMcdf3/System/NullableAttributes.cs index 08417f13..d62a560e 100644 --- a/OpenMcdf3/System/NullableAttributes.cs +++ b/OpenMcdf3/System/NullableAttributes.cs @@ -1,201 +1,200 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace System.Diagnostics.CodeAnalysis -{ +namespace System.Diagnostics.CodeAnalysis; + #if !NETSTANDARD2_1 - /// Specifies that null is allowed as an input even if the corresponding type disallows it. - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +/// Specifies that null is allowed as an input even if the corresponding type disallows it. +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - sealed class AllowNullAttribute : Attribute - { } + sealed class AllowNullAttribute : Attribute +{ } - /// Specifies that null is disallowed as an input even if the corresponding type allows it. - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +/// Specifies that null is disallowed as an input even if the corresponding type allows it. +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - sealed class DisallowNullAttribute : Attribute - { } + sealed class DisallowNullAttribute : Attribute +{ } - /// Specifies that an output may be null even if the corresponding type disallows it. - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +/// Specifies that an output may be null even if the corresponding type disallows it. +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - sealed class MaybeNullAttribute : Attribute - { } + sealed class MaybeNullAttribute : Attribute +{ } - /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +/// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - sealed class NotNullAttribute : Attribute - { } + sealed class NotNullAttribute : Attribute +{ } - /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. - [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +/// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - sealed class MaybeNullWhenAttribute : Attribute - { - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter may be null. - /// - public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - - /// Gets the return value condition. - public bool ReturnValue { get; } - } - - /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. - [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + sealed class MaybeNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } +} + +/// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - sealed class NotNullWhenAttribute : Attribute - { - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// - public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; - - /// Gets the return value condition. - public bool ReturnValue { get; } - } - - /// Specifies that the output will be non-null if the named parameter is non-null. - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] + sealed class NotNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } +} + +/// Specifies that the output will be non-null if the named parameter is non-null. +[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - sealed class NotNullIfNotNullAttribute : Attribute - { - /// Initializes the attribute with the associated parameter name. - /// - /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. - /// - public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; - - /// Gets the associated parameter name. - public string ParameterName { get; } - } - - /// Applied to a method that will never return under any circumstance. - [AttributeUsage(AttributeTargets.Method, Inherited = false)] + sealed class NotNullIfNotNullAttribute : Attribute +{ + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + + /// Gets the associated parameter name. + public string ParameterName { get; } +} + +/// Applied to a method that will never return under any circumstance. +[AttributeUsage(AttributeTargets.Method, Inherited = false)] #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - sealed class DoesNotReturnAttribute : Attribute - { } + sealed class DoesNotReturnAttribute : Attribute +{ } - /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. - [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +/// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. +[AttributeUsage(AttributeTargets.Parameter, Inherited = false)] #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - sealed class DoesNotReturnIfAttribute : Attribute - { - /// Initializes the attribute with the specified parameter value. - /// - /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to - /// the associated parameter matches this value. - /// - public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; - - /// Gets the condition parameter value. - public bool ParameterValue { get; } - } + sealed class DoesNotReturnIfAttribute : Attribute +{ + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + + /// Gets the condition parameter value. + public bool ParameterValue { get; } +} #endif - /// Specifies that the method or property will ensure that the listed field and property members have not-null values. - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +/// Specifies that the method or property will ensure that the listed field and property members have not-null values. +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - sealed class MemberNotNullAttribute : Attribute - { - /// Initializes the attribute with a field or property member. - /// - /// The field or property member that is promised to be not-null. - /// - public MemberNotNullAttribute(string member) => Members = [member]; - - /// Initializes the attribute with the list of field and property members. - /// - /// The list of field and property members that are promised to be not-null. - /// - public MemberNotNullAttribute(params string[] members) => Members = members; - - /// Gets field or property member names. - public string[] Members { get; } - } - - /// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + sealed class MemberNotNullAttribute : Attribute +{ + /// Initializes the attribute with a field or property member. + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullAttribute(string member) => Members = [member]; + + /// Initializes the attribute with the list of field and property members. + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullAttribute(params string[] members) => Members = members; + + /// Gets field or property member names. + public string[] Members { get; } +} + +/// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - sealed class MemberNotNullWhenAttribute : Attribute + sealed class MemberNotNullWhenAttribute : Attribute +{ + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// The return value condition. If the method returns this value, the associated field or property member will not be null. + /// + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = [member]; + } + + /// Initializes the attribute with the specified return value condition and list of field and property members. + /// + /// The return value condition. If the method returns this value, the associated field and property members will not be null. + /// + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) { - /// Initializes the attribute with the specified return value condition and a field or property member. - /// - /// The return value condition. If the method returns this value, the associated field or property member will not be null. - /// - /// - /// The field or property member that is promised to be not-null. - /// - public MemberNotNullWhenAttribute(bool returnValue, string member) - { - ReturnValue = returnValue; - Members = [member]; - } - - /// Initializes the attribute with the specified return value condition and list of field and property members. - /// - /// The return value condition. If the method returns this value, the associated field and property members will not be null. - /// - /// - /// The list of field and property members that are promised to be not-null. - /// - public MemberNotNullWhenAttribute(bool returnValue, params string[] members) - { - ReturnValue = returnValue; - Members = members; - } - - /// Gets the return value condition. - public bool ReturnValue { get; } - - /// Gets field or property member names. - public string[] Members { get; } + ReturnValue = returnValue; + Members = members; } + + /// Gets the return value condition. + public bool ReturnValue { get; } + + /// Gets field or property member names. + public string[] Members { get; } } \ No newline at end of file diff --git a/OpenMcdf3/System/Range.cs b/OpenMcdf3/System/Range.cs index 8967a17a..7cb53760 100644 --- a/OpenMcdf3/System/Range.cs +++ b/OpenMcdf3/System/Range.cs @@ -7,122 +7,121 @@ #if NETSTANDARD2_0 || NETFRAMEWORK #endif -namespace System -{ - /// Represent a range has start and end indexes. - /// - /// Range is used by the C# compiler to support the range syntax. - /// - /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; - /// int[] subArray1 = someArray[0..2]; // { 1, 2 } - /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } - /// - /// +namespace System; + +/// Represent a range has start and end indexes. +/// +/// Range is used by the C# compiler to support the range syntax. +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; +/// int[] subArray1 = someArray[0..2]; // { 1, 2 } +/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } +/// +/// #if SYSTEM_PRIVATE_CORELIB - public +public #else - internal +internal #endif - readonly struct Range : IEquatable +readonly struct Range : IEquatable +{ + /// Represent the inclusive start index of the Range. + public Index Start { get; } + + /// Represent the exclusive end index of the Range. + public Index End { get; } + + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range(Index start, Index end) { - /// Represent the inclusive start index of the Range. - public Index Start { get; } + Start = start; + End = end; + } - /// Represent the exclusive end index of the Range. - public Index End { get; } + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals([NotNullWhen(true)] object? value) => + value is Range r && + r.Start.Equals(Start) && + r.End.Equals(End); - /// Construct a Range object using the start and end indexes. - /// Represent the inclusive start index of the range. - /// Represent the exclusive end index of the range. - public Range(Index start, Index end) - { - Start = start; - End = end; - } + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); - /// Indicates whether the current Range object is equal to another object of the same type. - /// An object to compare with this object - public override bool Equals([NotNullWhen(true)] object? value) => - value is Range r && - r.Start.Equals(Start) && - r.End.Equals(End); + /// Returns the hash code for this instance. + public override int GetHashCode() + { + return HashCode.Combine(Start.GetHashCode(), End.GetHashCode()); + } - /// Indicates whether the current Range object is equal to another Range object. - /// An object to compare with this object - public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString() + { +#if (!NETSTANDARD2_0 && !NETFRAMEWORK) + Span span = stackalloc char[2 + (2 * 11)]; // 2 for "..", then for each index 1 for '^' and 10 for longest possible uint + int pos = 0; - /// Returns the hash code for this instance. - public override int GetHashCode() + if (Start.IsFromEnd) { - return HashCode.Combine(Start.GetHashCode(), End.GetHashCode()); + span[0] = '^'; + pos = 1; } + bool formatted = ((uint)Start.Value).TryFormat(span.Slice(pos), out int charsWritten); + Debug.Assert(formatted); + pos += charsWritten; + + span[pos++] = '.'; + span[pos++] = '.'; - /// Converts the value of the current Range object to its equivalent string representation. - public override string ToString() + if (End.IsFromEnd) { -#if (!NETSTANDARD2_0 && !NETFRAMEWORK) - Span span = stackalloc char[2 + (2 * 11)]; // 2 for "..", then for each index 1 for '^' and 10 for longest possible uint - int pos = 0; - - if (Start.IsFromEnd) - { - span[0] = '^'; - pos = 1; - } - bool formatted = ((uint)Start.Value).TryFormat(span.Slice(pos), out int charsWritten); - Debug.Assert(formatted); - pos += charsWritten; - - span[pos++] = '.'; - span[pos++] = '.'; - - if (End.IsFromEnd) - { - span[pos++] = '^'; - } - formatted = ((uint)End.Value).TryFormat(span.Slice(pos), out charsWritten); - Debug.Assert(formatted); - pos += charsWritten; - - return new string(span.Slice(0, pos)); -#else - return Start.ToString() + ".." + End.ToString(); -#endif + span[pos++] = '^'; } + formatted = ((uint)End.Value).TryFormat(span.Slice(pos), out charsWritten); + Debug.Assert(formatted); + pos += charsWritten; - /// Create a Range object starting from start index to the end of the collection. - public static Range StartAt(Index start) => new Range(start, Index.End); - - /// Create a Range object starting from first element in the collection to the end Index. - public static Range EndAt(Index end) => new Range(Index.Start, end); + return new string(span.Slice(0, pos)); +#else + return Start.ToString() + ".." + End.ToString(); +#endif + } - /// Create a Range object starting from first element to the end. - public static Range All => new Range(Index.Start, Index.End); + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt(Index start) => new Range(start, Index.End); - /// Calculate the start offset and length of range object using a collection length. - /// The length of the collection that the range will be used with. length has to be a positive value. - /// - /// For performance reason, we don't validate the input length parameter against negative values. - /// It is expected Range will be used with collections which always have non negative length/count. - /// We validate the range is inside the length scope though. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public (int Offset, int Length) GetOffsetAndLength(int length) - { - int start = Start.GetOffset(length); - int end = End.GetOffset(length); + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt(Index end) => new Range(Index.Start, end); - if ((uint)end > (uint)length || (uint)start > (uint)end) - { - ThrowArgumentOutOfRangeException(); - } + /// Create a Range object starting from first element to the end. + public static Range All => new Range(Index.Start, Index.End); - return (start, end - start); - } + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public (int Offset, int Length) GetOffsetAndLength(int length) + { + int start = Start.GetOffset(length); + int end = End.GetOffset(length); - private static void ThrowArgumentOutOfRangeException() + if ((uint)end > (uint)length || (uint)start > (uint)end) { - throw new ArgumentOutOfRangeException("length"); + ThrowArgumentOutOfRangeException(); } + + return (start, end - start); + } + + private static void ThrowArgumentOutOfRangeException() + { + throw new ArgumentOutOfRangeException("length"); } } \ No newline at end of file diff --git a/StructuredStorageExplorer/MainForm.Designer.cs b/StructuredStorageExplorer/MainForm.Designer.cs new file mode 100644 index 00000000..a20ed86b --- /dev/null +++ b/StructuredStorageExplorer/MainForm.Designer.cs @@ -0,0 +1,466 @@ +namespace StructuredStorageExplorer +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog(); + this.treeView1 = new System.Windows.Forms.TreeView(); + this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); + this.importDataStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); + this.exportDataToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.addStorageStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); + this.addStreamToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.saveFileDialog1 = new System.Windows.Forms.SaveFileDialog(); + this.menuStrip1 = new System.Windows.Forms.MenuStrip(); + this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.openFileMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.newStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); + this.closeStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); + this.updateCurrentFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.saveAsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.preferencesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.openDataFileDialog = new System.Windows.Forms.OpenFileDialog(); + this.statusStrip1 = new System.Windows.Forms.StatusStrip(); + this.fileNameLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.splitContainer1 = new System.Windows.Forms.SplitContainer(); + this.propertyGrid1 = new System.Windows.Forms.PropertyGrid(); + this.splitContainer2 = new System.Windows.Forms.SplitContainer(); + this.tabControl1 = new System.Windows.Forms.TabControl(); + this.tabPage1 = new System.Windows.Forms.TabPage(); + this.hexEditor = new Be.Windows.Forms.HexBox(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.splitContainer3 = new System.Windows.Forms.SplitContainer(); + this.dgvOLEProps = new System.Windows.Forms.DataGridView(); + this.dgvUserDefinedProperties = new System.Windows.Forms.DataGridView(); + this.contextMenuStrip1.SuspendLayout(); + this.menuStrip1.SuspendLayout(); + this.statusStrip1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); + this.splitContainer1.Panel1.SuspendLayout(); + this.splitContainer1.Panel2.SuspendLayout(); + this.splitContainer1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); + this.splitContainer2.Panel1.SuspendLayout(); + this.splitContainer2.Panel2.SuspendLayout(); + this.splitContainer2.SuspendLayout(); + this.tabControl1.SuspendLayout(); + this.tabPage1.SuspendLayout(); + this.tabPage2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).BeginInit(); + this.splitContainer3.Panel1.SuspendLayout(); + this.splitContainer3.Panel2.SuspendLayout(); + this.splitContainer3.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.dgvOLEProps)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.dgvUserDefinedProperties)).BeginInit(); + this.SuspendLayout(); + // + // openFileDialog1 + // + this.openFileDialog1.Filter = "Office files (*.xls *.doc *.ppt)|*.xls;*.doc;*.ppt|Thumbs db files (Thumbs.db)|*." + + "db|MSI Setup files (*.msi)|*.msi|Advanced Authoring Format (*.aaf)|*.aaf|All fi" + + "les (*.*)|*.*"; + this.openFileDialog1.Title = "Open OLE Structured Storage file"; + // + // treeView1 + // + this.treeView1.ContextMenuStrip = this.contextMenuStrip1; + this.treeView1.Dock = System.Windows.Forms.DockStyle.Fill; + this.treeView1.HideSelection = false; + this.treeView1.Location = new System.Drawing.Point(0, 0); + this.treeView1.Name = "treeView1"; + this.treeView1.Size = new System.Drawing.Size(281, 201); + this.treeView1.TabIndex = 4; + this.treeView1.MouseUp += new System.Windows.Forms.MouseEventHandler(this.treeView1_MouseUp); + // + // contextMenuStrip1 + // + this.contextMenuStrip1.ImageScalingSize = new System.Drawing.Size(20, 20); + this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.importDataStripMenuItem1, + this.exportDataToolStripMenuItem, + this.addStorageStripMenuItem1, + this.addStreamToolStripMenuItem, + this.removeToolStripMenuItem}); + this.contextMenuStrip1.Name = "contextMenuStrip1"; + this.contextMenuStrip1.Size = new System.Drawing.Size(148, 114); + this.contextMenuStrip1.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuStrip1_Opening); + // + // importDataStripMenuItem1 + // + this.importDataStripMenuItem1.Name = "importDataStripMenuItem1"; + this.importDataStripMenuItem1.Size = new System.Drawing.Size(147, 22); + this.importDataStripMenuItem1.Text = "Import data..."; + this.importDataStripMenuItem1.Click += new System.EventHandler(this.importDataStripMenuItem1_Click); + // + // exportDataToolStripMenuItem + // + this.exportDataToolStripMenuItem.Name = "exportDataToolStripMenuItem"; + this.exportDataToolStripMenuItem.Size = new System.Drawing.Size(147, 22); + this.exportDataToolStripMenuItem.Text = "Export data..."; + this.exportDataToolStripMenuItem.Click += new System.EventHandler(this.exportDataToolStripMenuItem_Click); + // + // addStorageStripMenuItem1 + // + this.addStorageStripMenuItem1.Name = "addStorageStripMenuItem1"; + this.addStorageStripMenuItem1.Size = new System.Drawing.Size(147, 22); + this.addStorageStripMenuItem1.Text = "Add storage..."; + this.addStorageStripMenuItem1.Click += new System.EventHandler(this.addStorageStripMenuItem1_Click); + // + // addStreamToolStripMenuItem + // + this.addStreamToolStripMenuItem.Name = "addStreamToolStripMenuItem"; + this.addStreamToolStripMenuItem.Size = new System.Drawing.Size(147, 22); + this.addStreamToolStripMenuItem.Text = "Add stream..."; + this.addStreamToolStripMenuItem.Click += new System.EventHandler(this.addStreamToolStripMenuItem_Click); + // + // removeToolStripMenuItem + // + this.removeToolStripMenuItem.Name = "removeToolStripMenuItem"; + this.removeToolStripMenuItem.Size = new System.Drawing.Size(147, 22); + this.removeToolStripMenuItem.Text = "Remove"; + this.removeToolStripMenuItem.Click += new System.EventHandler(this.removeToolStripMenuItem_Click); + // + // saveFileDialog1 + // + this.saveFileDialog1.DefaultExt = "*.bin"; + this.saveFileDialog1.Filter = "Exported data files (*.bin)|*.bin|All files (*.*)|*.*"; + // + // menuStrip1 + // + this.menuStrip1.ImageScalingSize = new System.Drawing.Size(20, 20); + this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.fileToolStripMenuItem, + this.editToolStripMenuItem}); + this.menuStrip1.Location = new System.Drawing.Point(0, 0); + this.menuStrip1.Name = "menuStrip1"; + this.menuStrip1.Padding = new System.Windows.Forms.Padding(6, 1, 0, 1); + this.menuStrip1.Size = new System.Drawing.Size(853, 24); + this.menuStrip1.TabIndex = 5; + this.menuStrip1.Text = "menuStrip1"; + // + // fileToolStripMenuItem + // + this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.openFileMenuItem, + this.newStripMenuItem1, + this.closeStripMenuItem1, + this.toolStripSeparator2, + this.updateCurrentFileToolStripMenuItem, + this.saveAsToolStripMenuItem}); + this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 22); + this.fileToolStripMenuItem.Text = "File"; + // + // openFileMenuItem + // + this.openFileMenuItem.Image = global::StructuredStorageExplorer.Properties.Resources.folder; + this.openFileMenuItem.Name = "openFileMenuItem"; + this.openFileMenuItem.Size = new System.Drawing.Size(187, 26); + this.openFileMenuItem.Text = "Open..."; + this.openFileMenuItem.Click += new System.EventHandler(this.openFileMenuItem_Click); + // + // newStripMenuItem1 + // + this.newStripMenuItem1.Image = global::StructuredStorageExplorer.Properties.Resources.page_white; + this.newStripMenuItem1.Name = "newStripMenuItem1"; + this.newStripMenuItem1.Size = new System.Drawing.Size(187, 26); + this.newStripMenuItem1.Text = "New Compound File"; + this.newStripMenuItem1.Click += new System.EventHandler(this.newStripMenuItem1_Click); + // + // closeStripMenuItem1 + // + this.closeStripMenuItem1.Name = "closeStripMenuItem1"; + this.closeStripMenuItem1.Size = new System.Drawing.Size(187, 26); + this.closeStripMenuItem1.Text = "Close file"; + this.closeStripMenuItem1.Click += new System.EventHandler(this.closeStripMenuItem1_Click); + // + // toolStripSeparator2 + // + this.toolStripSeparator2.Name = "toolStripSeparator2"; + this.toolStripSeparator2.Size = new System.Drawing.Size(184, 6); + // + // updateCurrentFileToolStripMenuItem + // + this.updateCurrentFileToolStripMenuItem.Image = global::StructuredStorageExplorer.Properties.Resources.disk; + this.updateCurrentFileToolStripMenuItem.Name = "updateCurrentFileToolStripMenuItem"; + this.updateCurrentFileToolStripMenuItem.Size = new System.Drawing.Size(187, 26); + this.updateCurrentFileToolStripMenuItem.Text = "Save"; + this.updateCurrentFileToolStripMenuItem.Click += new System.EventHandler(this.updateCurrentFileToolStripMenuItem_Click); + // + // saveAsToolStripMenuItem + // + this.saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem"; + this.saveAsToolStripMenuItem.Size = new System.Drawing.Size(187, 26); + this.saveAsToolStripMenuItem.Text = "Save As..."; + this.saveAsToolStripMenuItem.Click += new System.EventHandler(this.saveAsToolStripMenuItem_Click); + // + // editToolStripMenuItem + // + this.editToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.preferencesToolStripMenuItem}); + this.editToolStripMenuItem.Name = "editToolStripMenuItem"; + this.editToolStripMenuItem.Size = new System.Drawing.Size(39, 22); + this.editToolStripMenuItem.Text = "Edit"; + // + // preferencesToolStripMenuItem + // + this.preferencesToolStripMenuItem.Name = "preferencesToolStripMenuItem"; + this.preferencesToolStripMenuItem.Size = new System.Drawing.Size(135, 22); + this.preferencesToolStripMenuItem.Text = "Preferences"; + this.preferencesToolStripMenuItem.Click += new System.EventHandler(this.preferencesToolStripMenuItem_Click); + // + // statusStrip1 + // + this.statusStrip1.ImageScalingSize = new System.Drawing.Size(20, 20); + this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.fileNameLabel}); + this.statusStrip1.Location = new System.Drawing.Point(0, 436); + this.statusStrip1.Name = "statusStrip1"; + this.statusStrip1.Size = new System.Drawing.Size(853, 22); + this.statusStrip1.TabIndex = 6; + this.statusStrip1.Text = "statusStrip1"; + // + // fileNameLabel + // + this.fileNameLabel.Name = "fileNameLabel"; + this.fileNameLabel.Size = new System.Drawing.Size(0, 17); + // + // splitContainer1 + // + this.splitContainer1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer1.Location = new System.Drawing.Point(0, 0); + this.splitContainer1.Name = "splitContainer1"; + this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer1.Panel1 + // + this.splitContainer1.Panel1.Controls.Add(this.treeView1); + // + // splitContainer1.Panel2 + // + this.splitContainer1.Panel2.Controls.Add(this.propertyGrid1); + this.splitContainer1.Size = new System.Drawing.Size(283, 412); + this.splitContainer1.SplitterDistance = 203; + this.splitContainer1.TabIndex = 5; + // + // propertyGrid1 + // + this.propertyGrid1.Dock = System.Windows.Forms.DockStyle.Fill; + this.propertyGrid1.Location = new System.Drawing.Point(0, 0); + this.propertyGrid1.Name = "propertyGrid1"; + this.propertyGrid1.Size = new System.Drawing.Size(281, 203); + this.propertyGrid1.TabIndex = 0; + this.propertyGrid1.ToolbarVisible = false; + // + // splitContainer2 + // + this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer2.Location = new System.Drawing.Point(0, 24); + this.splitContainer2.Name = "splitContainer2"; + // + // splitContainer2.Panel1 + // + this.splitContainer2.Panel1.Controls.Add(this.splitContainer1); + // + // splitContainer2.Panel2 + // + this.splitContainer2.Panel2.Controls.Add(this.tabControl1); + this.splitContainer2.Size = new System.Drawing.Size(853, 412); + this.splitContainer2.SplitterDistance = 283; + this.splitContainer2.TabIndex = 7; + // + // tabControl1 + // + this.tabControl1.Controls.Add(this.tabPage1); + this.tabControl1.Controls.Add(this.tabPage2); + this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tabControl1.Location = new System.Drawing.Point(0, 0); + this.tabControl1.Name = "tabControl1"; + this.tabControl1.SelectedIndex = 0; + this.tabControl1.Size = new System.Drawing.Size(566, 412); + this.tabControl1.TabIndex = 1; + // + // tabPage1 + // + this.tabPage1.Controls.Add(this.hexEditor); + this.tabPage1.Location = new System.Drawing.Point(4, 22); + this.tabPage1.Name = "tabPage1"; + this.tabPage1.Padding = new System.Windows.Forms.Padding(3, 3, 3, 3); + this.tabPage1.Size = new System.Drawing.Size(558, 386); + this.tabPage1.TabIndex = 0; + this.tabPage1.Text = "Raw Data"; + this.tabPage1.UseVisualStyleBackColor = true; + // + // hexEditor + // + this.hexEditor.BackColor = System.Drawing.Color.WhiteSmoke; + this.hexEditor.Dock = System.Windows.Forms.DockStyle.Fill; + this.hexEditor.Font = new System.Drawing.Font("Courier New", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.hexEditor.InfoForeColor = System.Drawing.Color.Empty; + this.hexEditor.LineInfoVisible = true; + this.hexEditor.Location = new System.Drawing.Point(3, 3); + this.hexEditor.Name = "hexEditor"; + this.hexEditor.ShadowSelectionColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(60)))), ((int)(((byte)(188)))), ((int)(((byte)(255))))); + this.hexEditor.Size = new System.Drawing.Size(552, 380); + this.hexEditor.StringViewVisible = true; + this.hexEditor.TabIndex = 0; + this.hexEditor.UseFixedBytesPerLine = true; + this.hexEditor.VScrollBarVisible = true; + // + // tabPage2 + // + this.tabPage2.Controls.Add(this.splitContainer3); + this.tabPage2.Location = new System.Drawing.Point(4, 22); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.Padding = new System.Windows.Forms.Padding(3, 3, 3, 3); + this.tabPage2.Size = new System.Drawing.Size(558, 396); + this.tabPage2.TabIndex = 1; + this.tabPage2.Text = "OLE Properties"; + this.tabPage2.UseVisualStyleBackColor = true; + // + // splitContainer3 + // + this.splitContainer3.Dock = System.Windows.Forms.DockStyle.Fill; + this.splitContainer3.Location = new System.Drawing.Point(3, 3); + this.splitContainer3.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3); + this.splitContainer3.Name = "splitContainer3"; + this.splitContainer3.Orientation = System.Windows.Forms.Orientation.Horizontal; + // + // splitContainer3.Panel1 + // + this.splitContainer3.Panel1.Controls.Add(this.dgvOLEProps); + // + // splitContainer3.Panel2 + // + this.splitContainer3.Panel2.Controls.Add(this.dgvUserDefinedProperties); + this.splitContainer3.Size = new System.Drawing.Size(552, 390); + this.splitContainer3.SplitterDistance = 195; + this.splitContainer3.SplitterWidth = 3; + this.splitContainer3.TabIndex = 2; + // + // dgvOLEProps + // + this.dgvOLEProps.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dgvOLEProps.Dock = System.Windows.Forms.DockStyle.Fill; + this.dgvOLEProps.Location = new System.Drawing.Point(0, 0); + this.dgvOLEProps.Name = "dgvOLEProps"; + this.dgvOLEProps.RowHeadersWidth = 62; + this.dgvOLEProps.Size = new System.Drawing.Size(552, 195); + this.dgvOLEProps.TabIndex = 0; + // + // dgvUserDefinedProperties + // + this.dgvUserDefinedProperties.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dgvUserDefinedProperties.Dock = System.Windows.Forms.DockStyle.Fill; + this.dgvUserDefinedProperties.Location = new System.Drawing.Point(0, 0); + this.dgvUserDefinedProperties.Name = "dgvUserDefinedProperties"; + this.dgvUserDefinedProperties.RowHeadersWidth = 62; + this.dgvUserDefinedProperties.Size = new System.Drawing.Size(552, 192); + this.dgvUserDefinedProperties.TabIndex = 1; + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(853, 458); + this.Controls.Add(this.splitContainer2); + this.Controls.Add(this.statusStrip1); + this.Controls.Add(this.menuStrip1); + this.MainMenuStrip = this.menuStrip1; + this.Name = "MainForm"; + this.Text = "Structured Storage eXplorer"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing); + this.contextMenuStrip1.ResumeLayout(false); + this.menuStrip1.ResumeLayout(false); + this.menuStrip1.PerformLayout(); + this.statusStrip1.ResumeLayout(false); + this.statusStrip1.PerformLayout(); + this.splitContainer1.Panel1.ResumeLayout(false); + this.splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); + this.splitContainer1.ResumeLayout(false); + this.splitContainer2.Panel1.ResumeLayout(false); + this.splitContainer2.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); + this.splitContainer2.ResumeLayout(false); + this.tabControl1.ResumeLayout(false); + this.tabPage1.ResumeLayout(false); + this.tabPage2.ResumeLayout(false); + this.splitContainer3.Panel1.ResumeLayout(false); + this.splitContainer3.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).EndInit(); + this.splitContainer3.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.dgvOLEProps)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.dgvUserDefinedProperties)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.OpenFileDialog openFileDialog1; + private System.Windows.Forms.TreeView treeView1; + private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; + private System.Windows.Forms.ToolStripMenuItem exportDataToolStripMenuItem; + private System.Windows.Forms.SaveFileDialog saveFileDialog1; + private System.Windows.Forms.ToolStripMenuItem removeToolStripMenuItem; + private System.Windows.Forms.MenuStrip menuStrip1; + private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem updateCurrentFileToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem addStreamToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem importDataStripMenuItem1; + private System.Windows.Forms.OpenFileDialog openDataFileDialog; + private System.Windows.Forms.ToolStripMenuItem addStorageStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem newStripMenuItem1; + private System.Windows.Forms.ToolStripMenuItem openFileMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.StatusStrip statusStrip1; + private System.Windows.Forms.ToolStripStatusLabel fileNameLabel; + private System.Windows.Forms.SplitContainer splitContainer1; + private System.Windows.Forms.PropertyGrid propertyGrid1; + private System.Windows.Forms.SplitContainer splitContainer2; + private Be.Windows.Forms.HexBox hexEditor; + private System.Windows.Forms.ToolStripMenuItem closeStripMenuItem1; + private System.Windows.Forms.TabControl tabControl1; + private System.Windows.Forms.TabPage tabPage1; + private System.Windows.Forms.TabPage tabPage2; + private System.Windows.Forms.DataGridView dgvOLEProps; + private System.Windows.Forms.DataGridView dgvUserDefinedProperties; + private System.Windows.Forms.SplitContainer splitContainer3; + private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem preferencesToolStripMenuItem; + } +} + diff --git a/StructuredStorageExplorer/MainForm.cs b/StructuredStorageExplorer/MainForm.cs new file mode 100644 index 00000000..812af78d --- /dev/null +++ b/StructuredStorageExplorer/MainForm.cs @@ -0,0 +1,487 @@ +#define OLE_PROPERTY + +using OpenMcdf.Ole; +using OpenMcdf3; +using StructuredStorageExplorer.Properties; +using System.Collections; +using System.ComponentModel; +using System.Data; +using System.Globalization; + +// Author Federico Blaseotto + +namespace StructuredStorageExplorer; + +record class NodeSelection(Storage Parent, EntryInfo EntryInfo); + +/// +/// Sample Structured Storage viewer to +/// demonstrate use of OpenMCDF +/// +public partial class MainForm : Form +{ + private RootStorage? cf; + private FileStream? fs; + private bool canUpdate; + + public MainForm() + { + InitializeComponent(); + +#if !OLE_PROPERTY + tabControl1.TabPages.Remove(tabPage2); +#endif + + //Load images for icons from resx + Image folderImage = (Image)Resources.ResourceManager.GetObject("storage"); + Image streamImage = (Image)Resources.ResourceManager.GetObject("stream"); + //Image olePropsImage = (Image)Properties.Resources.ResourceManager.GetObject("oleprops"); + + treeView1.ImageList = new ImageList(); + treeView1.ImageList.Images.Add(folderImage); + treeView1.ImageList.Images.Add(streamImage); + //treeView1.ImageList.Images.Add(olePropsImage); + + saveAsToolStripMenuItem.Enabled = false; + updateCurrentFileToolStripMenuItem.Enabled = false; + } + + private void OpenFile() + { + if (!string.IsNullOrEmpty(openFileDialog1.FileName)) + { + CloseCurrentFile(); + + treeView1.Nodes.Clear(); + fileNameLabel.Text = openFileDialog1.FileName; + LoadFile(openFileDialog1.FileName, true); + canUpdate = true; + saveAsToolStripMenuItem.Enabled = true; + updateCurrentFileToolStripMenuItem.Enabled = true; + } + } + + private void CloseCurrentFile() + { + cf?.Dispose(); + cf = null; + + fs?.Close(); + fs = null; + + treeView1.Nodes.Clear(); + fileNameLabel.Text = string.Empty; + saveAsToolStripMenuItem.Enabled = false; + updateCurrentFileToolStripMenuItem.Enabled = false; + + propertyGrid1.SelectedObject = null; + hexEditor.ByteProvider = null; + +#if OLE_PROPERTY + dgvUserDefinedProperties.DataSource = null; + dgvOLEProps.DataSource = null; +#endif + } + + private void CreateNewFile() + { + CloseCurrentFile(); + + cf = RootStorage.Create(Path.GetTempFileName()); + canUpdate = false; + saveAsToolStripMenuItem.Enabled = true; + + updateCurrentFileToolStripMenuItem.Enabled = false; + + RefreshTree(); + } + + private void RefreshTree() + { + treeView1.Nodes.Clear(); + TreeNode root = treeView1.Nodes.Add("Root Entry", "Root"); + root.ImageIndex = 0; + root.Tag = new NodeSelection(null, cf.EntryInfo); + + // Recursive function to get all storage and streams + AddNodes(root, cf); + } + + private void LoadFile(string fileName, bool enableCommit) + { + fs = new FileStream( + fileName, + FileMode.Open, + enableCommit ? + FileAccess.ReadWrite + : FileAccess.Read); + + try + { + cf?.Dispose(); + cf = null; + + // Load file + cf = RootStorage.Open(fs, enableCommit ? StorageModeFlags.Transacted : StorageModeFlags.None); + + RefreshTree(); + } + catch (Exception ex) + { + cf?.Dispose(); + cf = null; + + fs?.Close(); + fs = null; + + treeView1.Nodes.Clear(); + fileNameLabel.Text = string.Empty; + MessageBox.Show("Internal error: " + ex.Message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + /// + /// Recursive addition of tree nodes foreach child of current item in the storage + /// + /// Current TreeNode + /// Current storage associated with node + private static void AddNodes(TreeNode node, Storage storage) + { + foreach (EntryInfo item in storage.EnumerateEntries()) + { + TreeNode childNode = node.Nodes.Add(item.Name); + + childNode.Tag = new NodeSelection(storage, item); + + if (item.Type is EntryType.Storage) + { + // Storage + childNode.ImageIndex = 0; + childNode.SelectedImageIndex = 0; + + Storage subStorage = storage.OpenStorage(item.Name); + // Recursion into the storage + AddNodes(childNode, subStorage); + } + else + { + // Stream + childNode.ImageIndex = 1; + childNode.SelectedImageIndex = 1; + } + } + } + + private void exportDataToolStripMenuItem_Click(object sender, EventArgs e) + { + // No export if storage + NodeSelection? selection = treeView1.SelectedNode?.Tag as NodeSelection; + if (selection is null || selection.EntryInfo.Type is not EntryType.Stream) + { + MessageBox.Show("Only stream data can be exported", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + // A lot of stream and storage have only non-printable characters. + // We need to sanitize filename. + + string sanitizedFileName = string.Empty; + + foreach (char c in selection.EntryInfo.Name) + { + UnicodeCategory category = char.GetUnicodeCategory(c); + if (category is UnicodeCategory.LetterNumber or UnicodeCategory.LowercaseLetter or UnicodeCategory.UppercaseLetter) + sanitizedFileName += c; + } + + if (string.IsNullOrEmpty(sanitizedFileName)) + { + sanitizedFileName = "tempFileName"; + } + + saveFileDialog1.FileName = $"{sanitizedFileName}.bin"; + + if (saveFileDialog1.ShowDialog() == DialogResult.OK) + { + try + { + using FileStream fs = new(saveFileDialog1.FileName, FileMode.CreateNew, FileAccess.ReadWrite); + using CfbStream cfbStream = selection.Parent.OpenStream(selection.EntryInfo.Name); + cfbStream.CopyTo(fs); + } + catch (Exception ex) + { + treeView1.Nodes.Clear(); + MessageBox.Show($"Internal error: {ex.Message}", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + + private void removeToolStripMenuItem_Click(object sender, EventArgs e) + { + TreeNode n = treeView1.SelectedNode; + if (n?.Parent?.Tag is NodeSelection selection && selection.Parent is not null) + selection.Parent.Delete(selection.EntryInfo.Name); + + RefreshTree(); + } + + private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) + { + saveFileDialog1.FilterIndex = 2; + if (saveFileDialog1.ShowDialog() == DialogResult.OK) + { + //cf.SaveAs(saveFileDialog1.FileName); // TODO + } + } + + private void updateCurrentFileToolStripMenuItem_Click(object sender, EventArgs e) + { + if (canUpdate) + { + if (hexEditor.ByteProvider is not null && hexEditor.ByteProvider.HasChanges()) + hexEditor.ByteProvider.ApplyChanges(); + cf.Commit(); + } + else + { + MessageBox.Show("Cannot update a compound document that is not based on a stream or on a file", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void addStreamToolStripMenuItem_Click(object sender, EventArgs e) + { + string streamName = string.Empty; + + if (Utils.InputBox("Add stream", "Insert stream name", ref streamName) == DialogResult.OK + && treeView1.SelectedNode.Tag is RootStorage storage) + { + try + { + storage.CreateStream(streamName); + } + catch (IOException ex) + { + MessageBox.Show($"Error creating stream: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + RefreshTree(); + } + } + + private void addStorageStripMenuItem1_Click(object sender, EventArgs e) + { + string storageName = string.Empty; + + if (Utils.InputBox("Add storage", "Insert storage name", ref storageName) == DialogResult.OK + && treeView1.SelectedNode.Tag is RootStorage storage) + { + try + { + storage.CreateStorage(storageName); + } + catch (IOException ex) + { + MessageBox.Show($"Error creating storage: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + RefreshTree(); + } + } + + private void importDataStripMenuItem1_Click(object sender, EventArgs e) + { + if (openDataFileDialog.ShowDialog() == DialogResult.OK + && treeView1.SelectedNode.Tag is CfbStream stream) + { + using FileStream f = new(openDataFileDialog.FileName, FileMode.Open, FileAccess.Read, FileShare.Read); + f.CopyTo(stream); + + RefreshTree(); + } + } + + private void MainForm_FormClosing(object sender, FormClosingEventArgs e) + { + cf?.Dispose(); + cf = null; + } + + private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) + { + } + + private void newStripMenuItem1_Click(object sender, EventArgs e) + { + CreateNewFile(); + } + + private void openFileMenuItem_Click(object sender, EventArgs e) + { + if (openFileDialog1.ShowDialog() == DialogResult.OK) + { + try + { + OpenFile(); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + MessageBox.Show($"Cannot open file: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } + + private void treeView1_MouseUp(object sender, MouseEventArgs e) + { + TreeNode n = treeView1.GetNodeAt(e.X, e.Y); + if (n.Tag is not NodeSelection nodeSelection) + { + addStorageStripMenuItem1.Enabled = true; + addStreamToolStripMenuItem.Enabled = true; + importDataStripMenuItem1.Enabled = false; + exportDataToolStripMenuItem.Enabled = false; + removeToolStripMenuItem.Enabled = false; + propertyGrid1.SelectedObject = null; + return; + } + + // Get the node under the mouse cursor. + // We intercept both left and right mouse clicks + // and set the selected TreeNode according. + try + { + if (hexEditor.ByteProvider is not null && hexEditor.ByteProvider.HasChanges()) + { + if (MessageBox.Show("Do you want to save pending changes?", "Save changes", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) + { + hexEditor.ByteProvider.ApplyChanges(); + } + } + + treeView1.SelectedNode = n; + + // The tag property contains the underlying CFItem. + //CFItem target = (CFItem)n.Tag; + + if (nodeSelection.EntryInfo.Type is EntryType.Stream) + { + using CfbStream stream = nodeSelection.Parent.OpenStream(nodeSelection.EntryInfo.Name); + addStorageStripMenuItem1.Enabled = false; + addStreamToolStripMenuItem.Enabled = false; + importDataStripMenuItem1.Enabled = true; + exportDataToolStripMenuItem.Enabled = true; + + hexEditor.ByteProvider = new StreamDataProvider(stream); + +#if OLE_PROPERTY + UpdateOleTab(stream); +#endif + } + else + { + hexEditor.ByteProvider = null; + } + + propertyGrid1.SelectedObject = nodeSelection.EntryInfo; + } + catch (Exception ex) + { + cf?.Dispose(); + cf = null; + + fs?.Close(); + fs = null; + + treeView1.Nodes.Clear(); + fileNameLabel.Text = string.Empty; + + MessageBox.Show($"Internal error: {ex.Message}", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void UpdateOleTab(CfbStream stream) + { + dgvUserDefinedProperties.DataSource = null; + dgvOLEProps.DataSource = null; + + if (stream.EntryInfo.Name is "\u0005SummaryInformation" or "\u0005DocumentSummaryInformation") + { + OlePropertiesContainer c = new(stream); + + DataTable ds = new(); + ds.Columns.Add("Name", typeof(string)); + ds.Columns.Add("Type", typeof(string)); + ds.Columns.Add("Value", typeof(string)); + + foreach (OleProperty p in c.Properties) + { + if (p.Value is not byte[] and IList list) + { + for (int h = 0; h < list.Count; h++) + { + DataRow dr = ds.NewRow(); + dr.ItemArray = [p.PropertyName, p.VTType, list[h]]; + ds.Rows.Add(dr); + } + } + else + { + DataRow dr = ds.NewRow(); + dr.ItemArray = [p.PropertyName, p.VTType, p.Value]; + ds.Rows.Add(dr); + } + } + + ds.AcceptChanges(); + dgvOLEProps.DataSource = ds; + + if (c.HasUserDefinedProperties) + { + DataTable ds2 = new(); + ds2.Columns.Add("Name", typeof(string)); + ds2.Columns.Add("Type", typeof(string)); + ds2.Columns.Add("Value", typeof(string)); + + foreach (OleProperty p in c.UserDefinedProperties.Properties) + { + if (p.Value is not byte[] and IList list) + { + for (int h = 0; h < list.Count; h++) + { + DataRow dr = ds2.NewRow(); + dr.ItemArray = [p.PropertyName, p.VTType, list[h]]; + ds2.Rows.Add(dr); + } + } + else + { + DataRow dr = ds2.NewRow(); + dr.ItemArray = [p.PropertyName, p.VTType, p.Value]; + ds2.Rows.Add(dr); + } + } + + ds2.AcceptChanges(); + dgvUserDefinedProperties.DataSource = ds2; + } + } + } + + private void closeStripMenuItem1_Click(object sender, EventArgs e) + { + if (hexEditor.ByteProvider is not null + && hexEditor.ByteProvider.HasChanges() + && MessageBox.Show("Do you want to save pending changes?", "Save changes", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) + { + hexEditor.ByteProvider.ApplyChanges(); + } + + CloseCurrentFile(); + } + + private void preferencesToolStripMenuItem_Click(object sender, EventArgs e) + { + using PreferencesForm pref = new(); + pref.ShowDialog(); + } +} diff --git a/StructuredStorageExplorer/MainForm.resx b/StructuredStorageExplorer/MainForm.resx new file mode 100644 index 00000000..f8835009 --- /dev/null +++ b/StructuredStorageExplorer/MainForm.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 147, 17 + + + 292, 17 + + + 420, 17 + + + 529, 17 + + + 676, 17 + + + 100 + + \ No newline at end of file diff --git a/StructuredStorageExplorer/OpenMcdf.snk b/StructuredStorageExplorer/OpenMcdf.snk new file mode 100644 index 0000000000000000000000000000000000000000..b2990e73c74b1b002bd693d3d8008085b96f461f GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097nwG6x-K;LO^j4j6cs`(%400Z5@yPQnZ7{$0%=kHi|-&%jj zIZo$C(^orbVlD(&H`PYI`!B&VSLuY?Q>Q)El2oao)5)5QriW~iFixpxnXCq=jeExP zwIDQlYjkbW{FAWhZ)4zsWDM5B0Ai$5KWtA3WU3fix^?x&19(Tr`ai`}{|vA3d;= zvaMECN!3mgvGE5t)=hH7N)+WzrP#-#HOb%u(+-Je%Npw`Uv^Y}ekuG=&$uUWso2C? z_`m=&4^8V#^8!SoI$f{Y;uM2Qdgc1+0wDpGERes_*oK=gF2S}Uz!p3<(}iN-sPo!x z + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.cbEnableValidation = new System.Windows.Forms.CheckBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.btnSavePreferences = new System.Windows.Forms.Button(); + this.btnCancelPreferences = new System.Windows.Forms.Button(); + this.groupBox1.SuspendLayout(); + this.SuspendLayout(); + // + // cbEnableValidation + // + this.cbEnableValidation.AutoSize = true; + this.cbEnableValidation.Location = new System.Drawing.Point(6, 25); + this.cbEnableValidation.Name = "cbEnableValidation"; + this.cbEnableValidation.Size = new System.Drawing.Size(277, 24); + this.cbEnableValidation.TabIndex = 0; + this.cbEnableValidation.Text = "File Validation Exceptions enabled"; + this.cbEnableValidation.UseVisualStyleBackColor = true; + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.cbEnableValidation); + this.groupBox1.Location = new System.Drawing.Point(12, 12); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(555, 259); + this.groupBox1.TabIndex = 1; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Preferences"; + // + // btnSavePreferences + // + this.btnSavePreferences.Location = new System.Drawing.Point(473, 298); + this.btnSavePreferences.Name = "btnSavePreferences"; + this.btnSavePreferences.Size = new System.Drawing.Size(94, 30); + this.btnSavePreferences.TabIndex = 2; + this.btnSavePreferences.Text = "OK"; + this.btnSavePreferences.UseVisualStyleBackColor = true; + this.btnSavePreferences.Click += new System.EventHandler(this.btnSavePreferences_Click); + // + // btnCancelPreferences + // + this.btnCancelPreferences.Location = new System.Drawing.Point(373, 298); + this.btnCancelPreferences.Name = "btnCancelPreferences"; + this.btnCancelPreferences.Size = new System.Drawing.Size(94, 30); + this.btnCancelPreferences.TabIndex = 3; + this.btnCancelPreferences.Text = "Cancel"; + this.btnCancelPreferences.UseVisualStyleBackColor = true; + this.btnCancelPreferences.Click += new System.EventHandler(this.btnCancelPreferences_Click); + // + // PreferencesForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(579, 340); + this.Controls.Add(this.btnCancelPreferences); + this.Controls.Add(this.btnSavePreferences); + this.Controls.Add(this.groupBox1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.Name = "PreferencesForm"; + this.ShowIcon = false; + this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Preferences"; + this.Load += new System.EventHandler(this.PreferencesForm_Load); + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.CheckBox cbEnableValidation; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Button btnSavePreferences; + private System.Windows.Forms.Button btnCancelPreferences; + } +} \ No newline at end of file diff --git a/StructuredStorageExplorer/PreferencesForm.cs b/StructuredStorageExplorer/PreferencesForm.cs new file mode 100644 index 00000000..66db24e7 --- /dev/null +++ b/StructuredStorageExplorer/PreferencesForm.cs @@ -0,0 +1,31 @@ +using StructuredStorageExplorer.Properties; + +namespace StructuredStorageExplorer; + +public partial class PreferencesForm : Form +{ + public PreferencesForm() + { + InitializeComponent(); + } + + private void btnSavePreferences_Click(object sender, EventArgs e) + { + Settings.Default.EnableValidation = cbEnableValidation.Checked; + Settings.Default.Save(); + DialogResult = DialogResult.OK; + Close(); + } + + private void btnCancelPreferences_Click(object sender, EventArgs e) + { + cbEnableValidation.Checked = Settings.Default.EnableValidation; + DialogResult = DialogResult.Cancel; + Close(); + } + + private void PreferencesForm_Load(object sender, EventArgs e) + { + cbEnableValidation.Checked = Settings.Default.EnableValidation; + } +} diff --git a/StructuredStorageExplorer/PreferencesForm.resx b/StructuredStorageExplorer/PreferencesForm.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/StructuredStorageExplorer/PreferencesForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/StructuredStorageExplorer/Program.cs b/StructuredStorageExplorer/Program.cs new file mode 100644 index 00000000..b04337b9 --- /dev/null +++ b/StructuredStorageExplorer/Program.cs @@ -0,0 +1,15 @@ +namespace StructuredStorageExplorer; + +static class Program +{ + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } +} diff --git a/StructuredStorageExplorer/Properties/Resources.Designer.cs b/StructuredStorageExplorer/Properties/Resources.Designer.cs new file mode 100644 index 00000000..530092ea --- /dev/null +++ b/StructuredStorageExplorer/Properties/Resources.Designer.cs @@ -0,0 +1,123 @@ +//------------------------------------------------------------------------------ +// +// Il codice è stato generato da uno strumento. +// Versione runtime:4.0.30319.42000 +// +// Le modifiche apportate a questo file possono provocare un comportamento non corretto e andranno perse se +// il codice viene rigenerato. +// +//------------------------------------------------------------------------------ + +namespace StructuredStorageExplorer.Properties { + using System; + + + /// + /// Classe di risorse fortemente tipizzata per la ricerca di stringhe localizzate e così via. + /// + // Questa classe è stata generata automaticamente dalla classe StronglyTypedResourceBuilder. + // tramite uno strumento quale ResGen o Visual Studio. + // Per aggiungere o rimuovere un membro, modificare il file con estensione ResX ed eseguire nuovamente ResGen + // con l'opzione /str oppure ricompilare il progetto VS. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Restituisce l'istanza di ResourceManager nella cache utilizzata da questa classe. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("StructuredStorageExplorer.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Esegue l'override della proprietà CurrentUICulture del thread corrente per tutte le + /// ricerche di risorse eseguite utilizzando questa classe di risorse fortemente tipizzata. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Cerca una risorsa localizzata di tipo System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap disk { + get { + object obj = ResourceManager.GetObject("disk", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Cerca una risorsa localizzata di tipo System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap door_out { + get { + object obj = ResourceManager.GetObject("door_out", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Cerca una risorsa localizzata di tipo System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap folder { + get { + object obj = ResourceManager.GetObject("folder", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Cerca una risorsa localizzata di tipo System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap page_white { + get { + object obj = ResourceManager.GetObject("page_white", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Cerca una risorsa localizzata di tipo System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap storage { + get { + object obj = ResourceManager.GetObject("storage", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Cerca una risorsa localizzata di tipo System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap stream { + get { + object obj = ResourceManager.GetObject("stream", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/StructuredStorageExplorer/Properties/Resources.resx b/StructuredStorageExplorer/Properties/Resources.resx new file mode 100644 index 00000000..778d80b1 --- /dev/null +++ b/StructuredStorageExplorer/Properties/Resources.resx @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\img\disk.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\img\door_out.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\img\folder.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\img\page_white.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\img\storage.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\img\stream.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/StructuredStorageExplorer/Properties/Settings.Designer.cs b/StructuredStorageExplorer/Properties/Settings.Designer.cs new file mode 100644 index 00000000..ebbdce8a --- /dev/null +++ b/StructuredStorageExplorer/Properties/Settings.Designer.cs @@ -0,0 +1,50 @@ +//------------------------------------------------------------------------------ +// +// Il codice è stato generato da uno strumento. +// Versione runtime:4.0.30319.42000 +// +// Le modifiche apportate a questo file possono provocare un comportamento non corretto e andranno perse se +// il codice viene rigenerato. +// +//------------------------------------------------------------------------------ + +namespace StructuredStorageExplorer.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool CommitEnabled { + get { + return ((bool)(this["CommitEnabled"])); + } + set { + this["CommitEnabled"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool EnableValidation { + get { + return ((bool)(this["EnableValidation"])); + } + set { + this["EnableValidation"] = value; + } + } + } +} diff --git a/StructuredStorageExplorer/Properties/Settings.settings b/StructuredStorageExplorer/Properties/Settings.settings new file mode 100644 index 00000000..7c61266d --- /dev/null +++ b/StructuredStorageExplorer/Properties/Settings.settings @@ -0,0 +1,12 @@ + + + + + + True + + + False + + + \ No newline at end of file diff --git a/StructuredStorageExplorer/StreamDataProvider.cs b/StructuredStorageExplorer/StreamDataProvider.cs new file mode 100644 index 00000000..ffecb71d --- /dev/null +++ b/StructuredStorageExplorer/StreamDataProvider.cs @@ -0,0 +1,160 @@ +using Be.Windows.Forms; +using OpenMcdf3; + +namespace StructuredStorageExplorer; + +public class StreamDataProvider : IByteProvider +{ + /// + /// Modifying stream + /// + readonly CfbStream _modifiedStream; + + /// + /// Contains information about changes. + /// + bool _hasChanges; + + /// + /// Initializes a new instance of the StreamDataProvider class. + /// + /// + public StreamDataProvider(CfbStream modifiedStream) + { + byte[] data = new byte[modifiedStream.Length]; + modifiedStream.Position = 0; + modifiedStream.ReadExactly(data, 0, data.Length); + Bytes = new ByteCollection(data); + _modifiedStream = modifiedStream; + } + + /// + /// Raises the Changed event. + /// + void OnChanged(EventArgs e) + { + _hasChanges = true; + + Changed?.Invoke(this, e); + } + + /// + /// Raises the LengthChanged event. + /// + void OnLengthChanged(EventArgs e) + { + LengthChanged?.Invoke(this, e); + } + + /// + /// Gets the byte collection. + /// + public ByteCollection Bytes { get; } + + #region IByteProvider Members + /// + /// True, when changes are done. + /// + public bool HasChanges() + { + return _hasChanges; + } + + /// + /// Applies changes. + /// + public void ApplyChanges() + { + _hasChanges = false; + + _modifiedStream.Position = 0; + _modifiedStream.Write(Bytes.ToArray()); + } + + /// + /// Occurs, when the write buffer contains new changes. + /// + public event EventHandler Changed; + + /// + /// Occurs, when InsertBytes or DeleteBytes method is called. + /// + public event EventHandler LengthChanged; + + /// + /// Reads a byte from the byte collection. + /// + /// the index of the byte to read + /// the byte + public byte ReadByte(long index) + { return Bytes[(int)index]; } + + /// + /// Write a byte into the byte collection. + /// + /// the index of the byte to write. + /// the byte + public void WriteByte(long index, byte value) + { + Bytes[(int)index] = value; + OnChanged(EventArgs.Empty); + } + + /// + /// Deletes bytes from the byte collection. + /// + /// the start index of the bytes to delete. + /// the length of bytes to delete. + public void DeleteBytes(long index, long length) + { + int internal_index = (int)Math.Max(0, index); + int internal_length = (int)Math.Min((int)Length, length); + Bytes.RemoveRange(internal_index, internal_length); + + OnLengthChanged(EventArgs.Empty); + OnChanged(EventArgs.Empty); + } + + /// + /// Inserts byte into the byte collection. + /// + /// the start index of the bytes in the byte collection + /// the byte array to insert + public void InsertBytes(long index, byte[] bs) + { + Bytes.InsertRange((int)index, bs); + + OnLengthChanged(EventArgs.Empty); + OnChanged(EventArgs.Empty); + } + + /// + /// Gets the length of the bytes in the byte collection. + /// + public long Length => Bytes.Count; + + /// + /// Returns true + /// + public bool SupportsWriteByte() + { + return true; + } + + /// + /// Returns true + /// + public bool SupportsInsertBytes() + { + return true; + } + + /// + /// Returns true + /// + public bool SupportsDeleteBytes() + { + return true; + } + #endregion +} diff --git a/StructuredStorageExplorer/StructuredStorageExplorer.csproj b/StructuredStorageExplorer/StructuredStorageExplorer.csproj new file mode 100644 index 00000000..d5002c46 --- /dev/null +++ b/StructuredStorageExplorer/StructuredStorageExplorer.csproj @@ -0,0 +1,18 @@ + + + net8.0-windows + WinExe + true + 12.0 + enable + enable + + + + + + + + + + \ No newline at end of file diff --git a/StructuredStorageExplorer/Utils.cs b/StructuredStorageExplorer/Utils.cs new file mode 100644 index 00000000..5ecc3b14 --- /dev/null +++ b/StructuredStorageExplorer/Utils.cs @@ -0,0 +1,46 @@ +namespace StructuredStorageExplorer; + +class Utils +{ + public static DialogResult InputBox(string title, string promptText, ref string value) + { + Form form = new Form(); + Label label = new Label(); + TextBox textBox = new TextBox(); + Button buttonOk = new Button(); + Button buttonCancel = new Button(); + + form.Text = title; + label.Text = promptText; + textBox.Text = value; + + buttonOk.Text = "OK"; + buttonCancel.Text = "Cancel"; + buttonOk.DialogResult = DialogResult.OK; + buttonCancel.DialogResult = DialogResult.Cancel; + + label.SetBounds(9, 20, 372, 13); + textBox.SetBounds(12, 36, 372, 20); + buttonOk.SetBounds(228, 72, 75, 23); + buttonCancel.SetBounds(309, 72, 75, 23); + + label.AutoSize = true; + textBox.Anchor = textBox.Anchor | AnchorStyles.Right; + buttonOk.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + + form.ClientSize = new Size(396, 107); + form.Controls.AddRange([label, textBox, buttonOk, buttonCancel]); + form.ClientSize = new Size(Math.Max(300, label.Right + 10), form.ClientSize.Height); + form.FormBorderStyle = FormBorderStyle.FixedDialog; + form.StartPosition = FormStartPosition.CenterScreen; + form.MinimizeBox = false; + form.MaximizeBox = false; + form.AcceptButton = buttonOk; + form.CancelButton = buttonCancel; + + DialogResult dialogResult = form.ShowDialog(); + value = textBox.Text; + return dialogResult; + } +} diff --git a/StructuredStorageExplorer/app.config b/StructuredStorageExplorer/app.config new file mode 100644 index 00000000..6cd5da2a --- /dev/null +++ b/StructuredStorageExplorer/app.config @@ -0,0 +1,18 @@ + + + + +
+ + + + + + True + + + False + + + + diff --git a/StructuredStorageExplorer/img/disk.png b/StructuredStorageExplorer/img/disk.png new file mode 100644 index 0000000000000000000000000000000000000000..99d532e8b1750115952f97302a92d713c0486f97 GIT binary patch literal 620 zcmV-y0+aoTP)~H+MJzd|s z^YP1Hc07G_>)Lgir!F1{Qn4GcTg%?koHo<=1qRN{}nPDolOeI^o4N5I>! zU$N=L=sg~ zDx#dOA*B0N~cqPsWI(^rbbkh)DS0_H_UN0C4l_kvWIm2#Kyy6%BCh z(yIUf003&1xdx>t$*eR2ZvXxT0001Z_R$y3Iju92q*wg58};}zm(OaAH=p|y0002M zh5O5#fxp|~jc?yi@+7$`d4Q6Hl%z;WiWG??NXR{Hx%)pMd~SE0000OQI literal 0 HcmV?d00001 diff --git a/StructuredStorageExplorer/img/door_out.png b/StructuredStorageExplorer/img/door_out.png new file mode 100644 index 0000000000000000000000000000000000000000..2541d2bcbc218b194f79fd99f67d33de1873c6c4 GIT binary patch literal 688 zcmV;h0#E&kP)GS2eE@_I zS~TaE^z1tT1me$mOd>fuB1*9ukjYHe@2!~sjG5tP)N7xSr3G9P+3oKa++V)SLaGru zn`QvjgqvWRa7{oUyOUDA37GDE%9f3r>9Muk`Z$59p<W>iYj7vxikNWw_8sK+%_fnobvCa5%KNOO6e%CReDLPLmVwdHp%H5J z8cVW-n2=oPDz8D+5J{LSmLlCXMPg`l3MX6KVJNMw&n~!g&9zA<=CHFVyLz1l^k+DTiboyDIuKD~MJE&R)Oo;bO6gPHCylVLL*a<{&sTsdkI7k&ZU WO{4dBd(FK70000x(K@^6+>g^d@v4;gkbWsEoXE%32*i1tcpTNXd5CcIl)ECgqz|2rE6EW}s7R?kl za1q`0GCkMruC6-2LANtwVlsgzsp4?{@7$`KBv!G66>Vie3h?3OmEEkjwdLG0PgLVi z`!N((f$A@n17Ldj#`};0I3@iHJ5M{#IZz|UIYRm4(!uV7eYIYIwQf&}_2J~}>pQ^n z6o8--^T(=hkBNQ_k{-_GWE;FMW7!p}f{NG3nHZ{D5<3d8&tLh%a4AqqnjMkr3m&fkMdECD3N5}Unig5wy40;>lo4j~k+e}v)` zR6)J8Mk*u=SpB`p6o)7j?S0T@9?bz#m@l>gc*zk__|*!FMcHwP!gwLJvS~9c0px8E zWC#5QQ<|d}62BjvZR2H60wE-&H;pyTSqH(@-Vl>|&1p(LP>kg~E zYiz5X^`c$+%8#zC{u)yfe-5 zmgid={Z3k(ERKCKrE7DF;=x4^O+ pzO8rLO8p|Ip=x)jHOtWj`bJBmKdh_V<`47(gQu&X%Q~loCIFbEay|e6 literal 0 HcmV?d00001 diff --git a/StructuredStorageExplorer/img/storage.png b/StructuredStorageExplorer/img/storage.png new file mode 100644 index 0000000000000000000000000000000000000000..cbde61822a99090c03c45759518ad41a562e6bca GIT binary patch literal 396 zcmV;70dxL|P)!;NE`7)<>#@EXE+d`cov zvkwEWuoQ;m=eHn3w&2r$faUMszYJV_!VG_Z|HP05i5q#upTaRKY#u({Q32r zfgAwR%kum8Z-(DLup99C-7AJ0XZGSX1nh#}KfhsWe*c=`=D9-*c0uVlHD5Wl1Ly*v z0mpZ)#Hzp{D3#&E%ZE4(c=_Zm1B-x|41=zP3swd1p5DP920-t#ym)w!xji7{tEfEm|Kzbnt{P|730ssE}J8;R$-c>er3NHbV1vLP(!2H*mf zo{~UP63m`@^eY2~0e}AdVfg!xf#KTY_h7?*|NhPJ>(?(h2Ju1SKK907wb%^+xvVuwh7`}v3A^Y>p2AHV)$`26h`SS?H+K!5=N-dA4Q?faNt00000NkvXXu0mjf DU{|iw literal 0 HcmV?d00001 From 753071b77fa750c832059bb75452d36a91b61828 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 11:07:42 +1300 Subject: [PATCH 097/134] Refactor OLE extension --- OpenMcdf.Ole/Common.cs | 8 - OpenMcdf.Ole/DefaultPropertyFactory.cs | 2 +- OpenMcdf.Ole/DictionaryEntry.cs | 44 +++-- OpenMcdf.Ole/DictionaryProperty.cs | 20 +-- .../DocumentSummaryInfoPropertyFactory.cs | 6 +- ...matIdentifiers.cs => FormatIdentifiers.cs} | 2 +- OpenMcdf.Ole/IDictionaryProperty.cs | 5 - OpenMcdf.Ole/IProperty.cs | 11 +- OpenMcdf.Ole/ITypedPropertyValue.cs | 23 ++- OpenMcdf.Ole/Identifiers.cs | 27 --- OpenMcdf.Ole/OlePropertiesContainer.cs | 145 ++++------------ OpenMcdf.Ole/OleProperty.cs | 34 +--- OpenMcdf.Ole/PropertyFactory.cs | 156 ++++++------------ ...yIdentifiers.cs => PropertyIdentifiers.cs} | 30 +++- OpenMcdf.Ole/PropertySet.cs | 27 +-- OpenMcdf.Ole/PropertySetStream.cs | 105 ++++++------ OpenMcdf.Ole/TypedPropertyValue.cs | 18 +- OpenMcdf.Ole/VTPropertyType.cs | 2 + 18 files changed, 242 insertions(+), 423 deletions(-) delete mode 100644 OpenMcdf.Ole/Common.cs rename OpenMcdf.Ole/{WellKnownFormatIdentifiers.cs => FormatIdentifiers.cs} (92%) delete mode 100644 OpenMcdf.Ole/IDictionaryProperty.cs delete mode 100644 OpenMcdf.Ole/Identifiers.cs rename OpenMcdf.Ole/{ProperyIdentifiers.cs => PropertyIdentifiers.cs} (62%) diff --git a/OpenMcdf.Ole/Common.cs b/OpenMcdf.Ole/Common.cs deleted file mode 100644 index 3ee12e48..00000000 --- a/OpenMcdf.Ole/Common.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace OpenMcdf.Ole; - -public enum PropertyDimensions -{ - IsScalar, - IsVector, - IsArray -} diff --git a/OpenMcdf.Ole/DefaultPropertyFactory.cs b/OpenMcdf.Ole/DefaultPropertyFactory.cs index 4b432a7e..7d784623 100644 --- a/OpenMcdf.Ole/DefaultPropertyFactory.cs +++ b/OpenMcdf.Ole/DefaultPropertyFactory.cs @@ -3,5 +3,5 @@ // The default property factory. internal sealed class DefaultPropertyFactory : PropertyFactory { - public static PropertyFactory Instance { get; } = new DefaultPropertyFactory(); + public static DefaultPropertyFactory Default { get; } = new(); } diff --git a/OpenMcdf.Ole/DictionaryEntry.cs b/OpenMcdf.Ole/DictionaryEntry.cs index 68c04b6e..9fa05739 100644 --- a/OpenMcdf.Ole/DictionaryEntry.cs +++ b/OpenMcdf.Ole/DictionaryEntry.cs @@ -5,6 +5,7 @@ namespace OpenMcdf.Ole; public class DictionaryEntry { readonly int codePage; + private byte[]? nameBytes; public DictionaryEntry(int codePage) { @@ -12,21 +13,28 @@ public DictionaryEntry(int codePage) } public uint PropertyIdentifier { get; set; } + public int Length { get; set; } - public string Name => GetName(); - private byte[] nameBytes; + public string Name + { + get + { + if (nameBytes is null) + return string.Empty; + + string result = Encoding.GetEncoding(codePage).GetString(nameBytes); + result = result.Trim('\0'); + return result; + } + } public void Read(BinaryReader br) { PropertyIdentifier = br.ReadUInt32(); Length = br.ReadInt32(); - if (codePage != CodePages.WinUnicode) - { - nameBytes = br.ReadBytes(Length); - } - else + if (codePage == CodePages.WinUnicode) { nameBytes = br.ReadBytes(Length << 1); @@ -34,28 +42,16 @@ public void Read(BinaryReader br) if (m > 0) br.ReadBytes(m); } + else + { + nameBytes = br.ReadBytes(Length); + } } public void Write(BinaryWriter bw) { bw.Write(PropertyIdentifier); bw.Write(Length); - bw.Write(nameBytes); - - //if (codePage == CP_WINUNICODE) - // int m = Length % 4; - - //if (m > 0) - // for (int i = 0; i < m; i++) - // bw.Write((byte)m); - } - - private string GetName() - { - string result = Encoding.GetEncoding(codePage).GetString(nameBytes); - - result = result.Trim('\0'); - - return result; + bw.Write(nameBytes!); } } diff --git a/OpenMcdf.Ole/DictionaryProperty.cs b/OpenMcdf.Ole/DictionaryProperty.cs index 1089307c..7c8a0759 100644 --- a/OpenMcdf.Ole/DictionaryProperty.cs +++ b/OpenMcdf.Ole/DictionaryProperty.cs @@ -2,24 +2,22 @@ namespace OpenMcdf.Ole; -public class DictionaryProperty : IDictionaryProperty +public class DictionaryProperty : IProperty { private readonly int codePage; + private Dictionary? entries = new(); public DictionaryProperty(int codePage) { this.codePage = codePage; - entries = new Dictionary(); } public PropertyType PropertyType => PropertyType.DictionaryProperty; - private Dictionary entries; - - public object Value + public object? Value { get => entries; - set => entries = (Dictionary)value; + set => entries = (Dictionary?)value; } public void Read(BinaryReader br) @@ -30,10 +28,10 @@ public void Read(BinaryReader br) for (uint i = 0; i < numEntries; i++) { - DictionaryEntry de = new DictionaryEntry(codePage); + DictionaryEntry de = new(codePage); de.Read(br); - entries.Add(de.PropertyIdentifier, de.Name); + entries!.Add(de.PropertyIdentifier, de.Name); } int m = (int)(br.BaseStream.Position - curPos) % 4; @@ -58,7 +56,7 @@ public void Write(BinaryWriter bw) { long curPos = bw.BaseStream.Position; - bw.Write(entries.Count); + bw.Write(entries!.Count); foreach (KeyValuePair kv in entries) { @@ -75,7 +73,7 @@ private void WriteEntry(BinaryWriter bw, uint propertyIdentifier, string name) // Write the PropertyIdentifier bw.Write(propertyIdentifier); - // Encode string data with the current codepage + // Encode string data with the current code page byte[] nameBytes = Encoding.GetEncoding(codePage).GetBytes(name); uint byteLength = (uint)nameBytes.Length; @@ -118,7 +116,7 @@ private void WriteEntry(BinaryWriter bw, uint propertyIdentifier, string name) } // Write as much padding as needed to pad fieldLength to a multiple of 4 bytes - private void WritePaddingIfNeeded(BinaryWriter bw, int fieldLength) + private static void WritePaddingIfNeeded(BinaryWriter bw, int fieldLength) { int m = fieldLength % 4; diff --git a/OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs b/OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs index 18602bd8..551e55ed 100644 --- a/OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs +++ b/OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs @@ -3,14 +3,14 @@ // A separate factory for DocumentSummaryInformation properties, to handle special cases with unaligned strings. internal sealed class DocumentSummaryInfoPropertyFactory : PropertyFactory { - public static PropertyFactory Instance { get; } = new DocumentSummaryInfoPropertyFactory(); + public static DocumentSummaryInfoPropertyFactory Default { get; } = new(); protected override ITypedPropertyValue CreateLpstrProperty(VTPropertyType vType, int codePage, uint propertyIdentifier, bool isVariant) { // PIDDSI_HEADINGPAIR and PIDDSI_DOCPARTS use unaligned (unpadded) strings - the others are padded - if (propertyIdentifier == 0x0000000C || propertyIdentifier == 0x0000000D) + if (propertyIdentifier is 0x0000000C or 0x0000000D) return new VT_Unaligned_LPSTR_Property(vType, codePage, isVariant); - return base.CreateLpstrProperty(vType, codePage, propertyIdentifier, isVariant); + return CreateLpstrProperty(vType, codePage, propertyIdentifier, isVariant); } } diff --git a/OpenMcdf.Ole/WellKnownFormatIdentifiers.cs b/OpenMcdf.Ole/FormatIdentifiers.cs similarity index 92% rename from OpenMcdf.Ole/WellKnownFormatIdentifiers.cs rename to OpenMcdf.Ole/FormatIdentifiers.cs index 9161fdbe..9fee7a2d 100644 --- a/OpenMcdf.Ole/WellKnownFormatIdentifiers.cs +++ b/OpenMcdf.Ole/FormatIdentifiers.cs @@ -1,6 +1,6 @@ namespace OpenMcdf.Ole; -public static class WellKnownFormatIdentifiers +public static class FormatIdentifiers { public static readonly Guid SummaryInformation = new("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"); public static readonly Guid DocSummaryInformation = new("{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"); diff --git a/OpenMcdf.Ole/IDictionaryProperty.cs b/OpenMcdf.Ole/IDictionaryProperty.cs deleted file mode 100644 index 8b20b053..00000000 --- a/OpenMcdf.Ole/IDictionaryProperty.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace OpenMcdf.Ole; - -public interface IDictionaryProperty : IProperty -{ -} \ No newline at end of file diff --git a/OpenMcdf.Ole/IProperty.cs b/OpenMcdf.Ole/IProperty.cs index 9d44eed4..6e9e2610 100644 --- a/OpenMcdf.Ole/IProperty.cs +++ b/OpenMcdf.Ole/IProperty.cs @@ -8,14 +8,7 @@ public enum PropertyType public interface IProperty : IBinarySerializable { - object Value - { - get; - set; - } + object? Value { get; set; } - PropertyType PropertyType - { - get; - } + PropertyType PropertyType { get; } } diff --git a/OpenMcdf.Ole/ITypedPropertyValue.cs b/OpenMcdf.Ole/ITypedPropertyValue.cs index 76198997..94c4c5a3 100644 --- a/OpenMcdf.Ole/ITypedPropertyValue.cs +++ b/OpenMcdf.Ole/ITypedPropertyValue.cs @@ -1,20 +1,17 @@ namespace OpenMcdf.Ole; +public enum PropertyDimensions +{ + IsScalar, + IsVector, + IsArray +} + public interface ITypedPropertyValue : IProperty { - VTPropertyType VTType - { - get; - //set; - } + VTPropertyType VTType { get; } - PropertyDimensions PropertyDimensions - { - get; - } + PropertyDimensions PropertyDimensions { get; } - bool IsVariant - { - get; - } + bool IsVariant { get; } } diff --git a/OpenMcdf.Ole/Identifiers.cs b/OpenMcdf.Ole/Identifiers.cs deleted file mode 100644 index 860297d0..00000000 --- a/OpenMcdf.Ole/Identifiers.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace OpenMcdf.Ole; - -public static class Identifiers -{ - public static string GetDescription(uint identifier, ContainerType map, IDictionary customDict = null) - { - IDictionary nameDictionary = customDict; - - if (nameDictionary is null) - { - switch (map) - { - case ContainerType.SummaryInfo: - nameDictionary = PropertyIdentifiers.SummaryInfo; - break; - case ContainerType.DocumentSummaryInfo: - nameDictionary = PropertyIdentifiers.DocumentSummaryInfo; - break; - } - } - - if (nameDictionary?.TryGetValue(identifier, out string? value) == true) - return value; - - return $"0x{identifier:x8}"; - } -} diff --git a/OpenMcdf.Ole/OlePropertiesContainer.cs b/OpenMcdf.Ole/OlePropertiesContainer.cs index 9191cd70..8ff3f80f 100644 --- a/OpenMcdf.Ole/OlePropertiesContainer.cs +++ b/OpenMcdf.Ole/OlePropertiesContainer.cs @@ -15,11 +15,9 @@ public enum ContainerType public class OlePropertiesContainer { - public Dictionary PropertyNames; + public Dictionary? PropertyNames { get; private set; } - public OlePropertiesContainer UserDefinedProperties { get; private set; } - - public bool HasUserDefinedProperties { get; private set; } + public OlePropertiesContainer? UserDefinedProperties { get; private set; } public ContainerType ContainerType { get; } private Guid? FmtID0 { get; } @@ -27,53 +25,7 @@ public class OlePropertiesContainer public PropertyContext Context { get; } private readonly List properties = new(); - internal Stream cfStream; - - /* - Property name Property ID PID Type - Codepage PID_CODEPAGE 1 VT_I2 - Title PID_TITLE 2 VT_LPSTR - Subject PID_SUBJECT 3 VT_LPSTR - Author PID_AUTHOR 4 VT_LPSTR - Keywords PID_KEYWORDS 5 VT_LPSTR - Comments PID_COMMENTS 6 VT_LPSTR - Template PID_TEMPLATE 7 VT_LPSTR - Last Saved By PID_LASTAUTHOR 8 VT_LPSTR - Revision Number PID_REVNUMBER 9 VT_LPSTR - Last Printed PID_LASTPRINTED 11 VT_FILETIME - Create Time/Date PID_CREATE_DTM 12 VT_FILETIME - Last Save Time/Date PID_LASTSAVE_DTM 13 VT_FILETIME - Page Count PID_PAGECOUNT 14 VT_I4 - Word Count PID_WORDCOUNT 15 VT_I4 - Character Count PID_CHARCOUNT 16 VT_I4 - Creating Application PID_APPNAME 18 VT_LPSTR - Security PID_SECURITY 19 VT_I4 - */ - public class SummaryInfoProperties - { - public short CodePage { get; set; } - public string Title { get; set; } - public string Subject { get; set; } - public string Author { get; set; } - public string KeyWords { get; set; } - public string Comments { get; set; } - public string Template { get; set; } - public string LastSavedBy { get; set; } - public string RevisionNumber { get; set; } - public DateTime LastPrinted { get; set; } - public DateTime CreateTime { get; set; } - public DateTime LastSavedTime { get; set; } - public int PageCount { get; set; } - public int WordCount { get; set; } - public int CharacterCount { get; set; } - public string CreatingApplication { get; set; } - public int Security { get; set; } - } - - public static OlePropertiesContainer CreateNewSummaryInfo(SummaryInfoProperties sumInfoProps) - { - return null; - } + internal Stream? cfStream; public OlePropertiesContainer(int codePage, ContainerType containerType) { @@ -92,18 +44,19 @@ public OlePropertiesContainer(CfbStream cfStream) this.cfStream = cfStream; + cfStream.Position = 0; using BinaryReader reader = new(cfStream); pStream.Read(reader); - if (pStream.FMTID0 == WellKnownFormatIdentifiers.SummaryInformation) + if (pStream.FMTID0 == FormatIdentifiers.SummaryInformation) ContainerType = ContainerType.SummaryInfo; - else if (pStream.FMTID0 == WellKnownFormatIdentifiers.DocSummaryInformation) + else if (pStream.FMTID0 == FormatIdentifiers.DocSummaryInformation) ContainerType = ContainerType.DocumentSummaryInfo; else ContainerType = ContainerType.AppSpecific; FmtID0 = pStream.FMTID0; - PropertyNames = (Dictionary?)pStream.PropertySet0.Properties + PropertyNames = (Dictionary?)pStream.PropertySet0!.Properties .FirstOrDefault(p => p.PropertyType == PropertyType.DictionaryProperty)?.Value; Context = new PropertyContext() @@ -113,17 +66,17 @@ public OlePropertiesContainer(CfbStream cfStream) for (int i = 0; i < pStream.PropertySet0.Properties.Count; i++) { - if (pStream.PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier == 0) continue; - //if (pStream.PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier == 1) continue; - //if (pStream.PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier == 0x80000000) continue; + PropertyIdentifierAndOffset propertyIdentifierAndOffset = pStream.PropertySet0.PropertyIdentifierAndOffsets[i]; + if (propertyIdentifierAndOffset.PropertyIdentifier == 0) continue; + //if (propertyIdentifierAndOffset.PropertyIdentifier == 1) continue; + //if (propertyIdentifierAndOffset.PropertyIdentifier == 0x80000000) continue; var p = (ITypedPropertyValue)pStream.PropertySet0.Properties[i]; - PropertyIdentifierAndOffset poi = pStream.PropertySet0.PropertyIdentifierAndOffsets[i]; - var op = new OleProperty(this) + OleProperty op = new(this) { VTType = p.VTType, - PropertyIdentifier = pStream.PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier, + PropertyIdentifier = propertyIdentifierAndOffset.PropertyIdentifier, Value = p.Value }; @@ -132,28 +85,28 @@ public OlePropertiesContainer(CfbStream cfStream) if (pStream.NumPropertySets == 2) { - UserDefinedProperties = new OlePropertiesContainer(pStream.PropertySet1.PropertyContext.CodePage, ContainerType.UserDefinedProperties); - HasUserDefinedProperties = true; + PropertySet propertySet1 = pStream.PropertySet1!; + UserDefinedProperties = new OlePropertiesContainer(propertySet1.PropertyContext.CodePage, ContainerType.UserDefinedProperties); - for (int i = 0; i < pStream.PropertySet1.Properties.Count; i++) + for (int i = 0; i < propertySet1.Properties.Count; i++) { - if (pStream.PropertySet1.PropertyIdentifierAndOffsets[i].PropertyIdentifier is 0 or 0x80000000) + PropertyIdentifierAndOffset propertyIdentifierAndOffset = propertySet1.PropertyIdentifierAndOffsets[i]; + if (propertyIdentifierAndOffset.PropertyIdentifier is 0 or 0x80000000) continue; - var p = (ITypedPropertyValue)pStream.PropertySet1.Properties[i]; - PropertyIdentifierAndOffset poi = pStream.PropertySet1.PropertyIdentifierAndOffsets[i]; + var p = (ITypedPropertyValue)propertySet1.Properties[i]; OleProperty op = new(UserDefinedProperties) { VTType = p.VTType, - PropertyIdentifier = pStream.PropertySet1.PropertyIdentifierAndOffsets[i].PropertyIdentifier, + PropertyIdentifier = propertyIdentifierAndOffset.PropertyIdentifier, Value = p.Value }; UserDefinedProperties.properties.Add(op); } - var existingPropertyNames = (Dictionary?)pStream.PropertySet1.Properties + var existingPropertyNames = (Dictionary?)propertySet1.Properties .FirstOrDefault(p => p.PropertyType == PropertyType.DictionaryProperty)?.Value; UserDefinedProperties.PropertyNames = existingPropertyNames ?? new Dictionary(); @@ -162,10 +115,9 @@ public OlePropertiesContainer(CfbStream cfStream) public IList Properties => properties; - public OleProperty NewProperty(VTPropertyType vtPropertyType, uint propertyIdentifier, string? propertyName = null) + public OleProperty CreateProperty(VTPropertyType vtPropertyType, uint propertyIdentifier, string? propertyName = null) { - //throw new NotImplementedException("API Unstable - Work in progress - Milestone 2.3.0.0"); - var op = new OleProperty(this) + OleProperty op = new(this) { VTType = vtPropertyType, PropertyIdentifier = propertyIdentifier @@ -174,18 +126,13 @@ public OleProperty NewProperty(VTPropertyType vtPropertyType, uint propertyIdent return op; } - public void AddProperty(OleProperty property) - { - //throw new NotImplementedException("API Unstable - Work in progress - Milestone 2.3.0.0"); - properties.Add(property); - } + public void Add(OleProperty property) => properties.Add(property); public void RemoveProperty(uint propertyIdentifier) { - //throw new NotImplementedException("API Unstable - Work in progress - Milestone 2.3.0.0"); OleProperty? toRemove = properties.FirstOrDefault(o => o.PropertyIdentifier == propertyIdentifier); - if (toRemove != null) + if (toRemove is not null) properties.Remove(toRemove); } @@ -204,7 +151,7 @@ public OlePropertiesContainer CreateUserDefinedProperties(int codePage) if (ContainerType != ContainerType.DocumentSummaryInfo) throw new InvalidOperationException($"Only a DocumentSummaryInfo can contain user defined properties. Current container type is {ContainerType}"); - // Create the container, and add the codepage to the initial set of properties + // Create the container, and add the code page to the initial set of properties UserDefinedProperties = new OlePropertiesContainer(codePage, ContainerType.UserDefinedProperties) { PropertyNames = new Dictionary() @@ -218,19 +165,15 @@ public OlePropertiesContainer CreateUserDefinedProperties(int codePage) }; UserDefinedProperties.properties.Add(op); - HasUserDefinedProperties = true; return UserDefinedProperties; } public void Save(Stream cfStream) { - //throw new NotImplementedException("API Unstable - Work in progress - Milestone 2.3.0.0"); - //properties.Sort((a, b) => a.PropertyIdentifier.CompareTo(b.PropertyIdentifier)); - using BinaryWriter bw = new(cfStream); - Guid fmtId0 = FmtID0 ?? (ContainerType == ContainerType.SummaryInfo ? WellKnownFormatIdentifiers.SummaryInformation : WellKnownFormatIdentifiers.DocSummaryInformation); + Guid fmtId0 = FmtID0 ?? (ContainerType == ContainerType.SummaryInfo ? FormatIdentifiers.SummaryInformation : FormatIdentifiers.DocSummaryInformation); PropertySetStream ps = new() { @@ -249,54 +192,46 @@ public void Save(Stream cfStream) PropertySet0 = new PropertySet { - NumProperties = (uint)Properties.Count, - PropertyIdentifierAndOffsets = new List(), - Properties = new List(), PropertyContext = Context } }; // If we're writing an AppSpecific property set and have property names, then add a dictionary property - if (ContainerType == ContainerType.AppSpecific && PropertyNames != null && PropertyNames.Count > 0) + if (ContainerType == ContainerType.AppSpecific && PropertyNames is not null && PropertyNames.Count > 0) { - AddDictionaryPropertyToPropertySet(PropertyNames, ps.PropertySet0); - ps.PropertySet0.NumProperties += 1; + ps.PropertySet0.Add(PropertyNames); } PropertyFactory factory = - ContainerType == ContainerType.DocumentSummaryInfo ? DocumentSummaryInfoPropertyFactory.Instance : DefaultPropertyFactory.Instance; + ContainerType == ContainerType.DocumentSummaryInfo ? DocumentSummaryInfoPropertyFactory.Default : DefaultPropertyFactory.Default; foreach (OleProperty op in Properties) { - ITypedPropertyValue p = factory.NewProperty(op.VTType, Context.CodePage, op.PropertyIdentifier); + ITypedPropertyValue p = factory.CreateProperty(op.VTType, Context.CodePage, op.PropertyIdentifier); p.Value = op.Value; ps.PropertySet0.Properties.Add(p); ps.PropertySet0.PropertyIdentifierAndOffsets.Add(new PropertyIdentifierAndOffset() { PropertyIdentifier = op.PropertyIdentifier, Offset = 0 }); } - if (HasUserDefinedProperties) + if (UserDefinedProperties is not null) { ps.NumPropertySets = 2; ps.PropertySet1 = new PropertySet { - // Number of user defined properties, plus 1 for the name dictionary - NumProperties = (uint)UserDefinedProperties.Properties.Count + 1, - PropertyIdentifierAndOffsets = new List(), - Properties = new List(), PropertyContext = UserDefinedProperties.Context }; - ps.FMTID1 = WellKnownFormatIdentifiers.UserDefinedProperties; + ps.FMTID1 = FormatIdentifiers.UserDefinedProperties; ps.Offset1 = 0; // Add the dictionary containing the property names - AddDictionaryPropertyToPropertySet(UserDefinedProperties.PropertyNames, ps.PropertySet1); + ps.PropertySet1.Add(UserDefinedProperties.PropertyNames!); // Add the properties themselves foreach (OleProperty op in UserDefinedProperties.Properties) { - ITypedPropertyValue p = DefaultPropertyFactory.Instance.NewProperty(op.VTType, ps.PropertySet1.PropertyContext.CodePage, op.PropertyIdentifier); + ITypedPropertyValue p = DefaultPropertyFactory.Default.CreateProperty(op.VTType, ps.PropertySet1.PropertyContext.CodePage, op.PropertyIdentifier); p.Value = op.Value; ps.PropertySet1.Properties.Add(p); ps.PropertySet1.PropertyIdentifierAndOffsets.Add(new PropertyIdentifierAndOffset() { PropertyIdentifier = op.PropertyIdentifier, Offset = 0 }); @@ -305,14 +240,4 @@ public void Save(Stream cfStream) ps.Write(bw); } - - private void AddDictionaryPropertyToPropertySet(Dictionary propertyNames, PropertySet propertySet) - { - IDictionaryProperty dictionaryProperty = new DictionaryProperty(propertySet.PropertyContext.CodePage) - { - Value = propertyNames - }; - propertySet.Properties.Add(dictionaryProperty); - propertySet.PropertyIdentifierAndOffsets.Add(new PropertyIdentifierAndOffset() { PropertyIdentifier = 0, Offset = 0 }); - } } diff --git a/OpenMcdf.Ole/OleProperty.cs b/OpenMcdf.Ole/OleProperty.cs index 6d8cefbf..67ab4bd5 100644 --- a/OpenMcdf.Ole/OleProperty.cs +++ b/OpenMcdf.Ole/OleProperty.cs @@ -1,33 +1,22 @@ namespace OpenMcdf.Ole; -public class OleProperty +public sealed class OleProperty { private readonly OlePropertiesContainer container; + object? value; internal OleProperty(OlePropertiesContainer container) { this.container = container; } - public string PropertyName => DecodePropertyIdentifier(); + public string PropertyName => PropertyIdentifiers.GetDescription(PropertyIdentifier, container.ContainerType, container.PropertyNames); - private string DecodePropertyIdentifier() - { - return Identifiers.GetDescription(PropertyIdentifier, container.ContainerType, container.PropertyNames); - } - - //public string Description { get { return description; } public uint PropertyIdentifier { get; internal set; } - public VTPropertyType VTType - { - get; - internal set; - } - - object value; + public VTPropertyType VTType { get; internal set; } - public object Value + public object? Value { get { @@ -47,18 +36,9 @@ public object Value set => this.value = value; } - public override bool Equals(object obj) - { - if (obj is not OleProperty other) - return false; - - return other.PropertyIdentifier == PropertyIdentifier; - } + public override bool Equals(object? obj) => obj is OleProperty other && other.PropertyIdentifier == PropertyIdentifier; - public override int GetHashCode() - { - return (int)PropertyIdentifier; - } + public override int GetHashCode() => (int)PropertyIdentifier; public override string ToString() => $"{PropertyName} - {VTType} - {Value}"; } diff --git a/OpenMcdf.Ole/PropertyFactory.cs b/OpenMcdf.Ole/PropertyFactory.cs index c2c201ac..968e7f30 100644 --- a/OpenMcdf.Ole/PropertyFactory.cs +++ b/OpenMcdf.Ole/PropertyFactory.cs @@ -11,7 +11,7 @@ static PropertyFactory() #endif } - public ITypedPropertyValue NewProperty(VTPropertyType vType, int codePage, uint propertyIdentifier, bool isVariant = false) + public ITypedPropertyValue CreateProperty(VTPropertyType vType, int codePage, uint propertyIdentifier, bool isVariant = false) { ITypedPropertyValue pr = (VTPropertyType)((ushort)vType & 0x00FF) switch { @@ -39,35 +39,29 @@ public ITypedPropertyValue NewProperty(VTPropertyType vType, int codePage, uint VTPropertyType.VT_CF => new VT_CF_Property(vType, isVariant), VTPropertyType.VT_BLOB_OBJECT or VTPropertyType.VT_BLOB => new VT_BLOB_Property(vType, isVariant), VTPropertyType.VT_CLSID => new VT_CLSID_Property(vType, isVariant), - _ => throw new Exception("Unrecognized property type"), + _ => throw new ArgumentException("Unrecognized property type"), }; return pr; } - protected virtual ITypedPropertyValue CreateLpstrProperty(VTPropertyType vType, int codePage, uint propertyIdentifier, bool isVariant) - { - return new VT_LPSTR_Property(vType, codePage, isVariant); - } + protected virtual ITypedPropertyValue CreateLpstrProperty(VTPropertyType vType, int codePage, uint propertyIdentifier, bool isVariant) => new VT_LPSTR_Property(vType, codePage, isVariant); #region Property implementations - private class VT_EMPTY_Property : TypedPropertyValue + private sealed class VT_EMPTY_Property : TypedPropertyValue { public VT_EMPTY_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { } - public override object ReadScalarValue(BinaryReader br) - { - return null; - } + public override object? ReadScalarValue(BinaryReader br) => null; public override void WriteScalarValue(BinaryWriter bw, object pValue) { } } - private class VT_I1_Property : TypedPropertyValue + private sealed class VT_I1_Property : TypedPropertyValue { public VT_I1_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -79,13 +73,10 @@ public override sbyte ReadScalarValue(BinaryReader br) return r; } - public override void WriteScalarValue(BinaryWriter bw, sbyte pValue) - { - bw.Write(pValue); - } + public override void WriteScalarValue(BinaryWriter bw, sbyte pValue) => bw.Write(pValue); } - private class VT_UI1_Property : TypedPropertyValue + private sealed class VT_UI1_Property : TypedPropertyValue { public VT_UI1_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -97,13 +88,10 @@ public override byte ReadScalarValue(BinaryReader br) return r; } - public override void WriteScalarValue(BinaryWriter bw, byte pValue) - { - bw.Write(pValue); - } + public override void WriteScalarValue(BinaryWriter bw, byte pValue) => bw.Write(pValue); } - private class VT_UI4_Property : TypedPropertyValue + private sealed class VT_UI4_Property : TypedPropertyValue { public VT_UI4_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -115,13 +103,10 @@ public override uint ReadScalarValue(BinaryReader br) return r; } - public override void WriteScalarValue(BinaryWriter bw, uint pValue) - { - bw.Write(pValue); - } + public override void WriteScalarValue(BinaryWriter bw, uint pValue) => bw.Write(pValue); } - private class VT_UI8_Property : TypedPropertyValue + private sealed class VT_UI8_Property : TypedPropertyValue { public VT_UI8_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -133,13 +118,10 @@ public override ulong ReadScalarValue(BinaryReader br) return r; } - public override void WriteScalarValue(BinaryWriter bw, ulong pValue) - { - bw.Write(pValue); - } + public override void WriteScalarValue(BinaryWriter bw, ulong pValue) => bw.Write(pValue); } - private class VT_I2_Property : TypedPropertyValue + private sealed class VT_I2_Property : TypedPropertyValue { public VT_I2_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -151,13 +133,10 @@ public override short ReadScalarValue(BinaryReader br) return r; } - public override void WriteScalarValue(BinaryWriter bw, short pValue) - { - bw.Write(pValue); - } + public override void WriteScalarValue(BinaryWriter bw, short pValue) => bw.Write(pValue); } - private class VT_UI2_Property : TypedPropertyValue + private sealed class VT_UI2_Property : TypedPropertyValue { public VT_UI2_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -169,13 +148,10 @@ public override ushort ReadScalarValue(BinaryReader br) return r; } - public override void WriteScalarValue(BinaryWriter bw, ushort pValue) - { - bw.Write(pValue); - } + public override void WriteScalarValue(BinaryWriter bw, ushort pValue) => bw.Write(pValue); } - private class VT_I4_Property : TypedPropertyValue + private sealed class VT_I4_Property : TypedPropertyValue { public VT_I4_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -187,13 +163,10 @@ public override int ReadScalarValue(BinaryReader br) return r; } - public override void WriteScalarValue(BinaryWriter bw, int pValue) - { - bw.Write(pValue); - } + public override void WriteScalarValue(BinaryWriter bw, int pValue) => bw.Write(pValue); } - private class VT_I8_Property : TypedPropertyValue + private sealed class VT_I8_Property : TypedPropertyValue { public VT_I8_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -205,13 +178,10 @@ public override long ReadScalarValue(BinaryReader br) return r; } - public override void WriteScalarValue(BinaryWriter bw, long pValue) - { - bw.Write(pValue); - } + public override void WriteScalarValue(BinaryWriter bw, long pValue) => bw.Write(pValue); } - private class VT_INT_Property : TypedPropertyValue + private sealed class VT_INT_Property : TypedPropertyValue { public VT_INT_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -223,13 +193,10 @@ public override int ReadScalarValue(BinaryReader br) return r; } - public override void WriteScalarValue(BinaryWriter bw, int pValue) - { - bw.Write(pValue); - } + public override void WriteScalarValue(BinaryWriter bw, int pValue) => bw.Write(pValue); } - private class VT_UINT_Property : TypedPropertyValue + private sealed class VT_UINT_Property : TypedPropertyValue { public VT_UINT_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -241,13 +208,10 @@ public override uint ReadScalarValue(BinaryReader br) return r; } - public override void WriteScalarValue(BinaryWriter bw, uint pValue) - { - bw.Write(pValue); - } + public override void WriteScalarValue(BinaryWriter bw, uint pValue) => bw.Write(pValue); } - private class VT_R4_Property : TypedPropertyValue + private sealed class VT_R4_Property : TypedPropertyValue { public VT_R4_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -259,13 +223,10 @@ public override float ReadScalarValue(BinaryReader br) return r; } - public override void WriteScalarValue(BinaryWriter bw, float pValue) - { - bw.Write(pValue); - } + public override void WriteScalarValue(BinaryWriter bw, float pValue) => bw.Write(pValue); } - private class VT_R8_Property : TypedPropertyValue + private sealed class VT_R8_Property : TypedPropertyValue { public VT_R8_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -277,13 +238,10 @@ public override double ReadScalarValue(BinaryReader br) return r; } - public override void WriteScalarValue(BinaryWriter bw, double pValue) - { - bw.Write(pValue); - } + public override void WriteScalarValue(BinaryWriter bw, double pValue) => bw.Write(pValue); } - private class VT_CY_Property : TypedPropertyValue + private sealed class VT_CY_Property : TypedPropertyValue { public VT_CY_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -298,13 +256,10 @@ public override long ReadScalarValue(BinaryReader br) return tmp; } - public override void WriteScalarValue(BinaryWriter bw, long pValue) - { - bw.Write(pValue * 10000); - } + public override void WriteScalarValue(BinaryWriter bw, long pValue) => bw.Write(pValue * 10000); } - private class VT_DATE_Property : TypedPropertyValue + private sealed class VT_DATE_Property : TypedPropertyValue { public VT_DATE_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -317,15 +272,11 @@ public override DateTime ReadScalarValue(BinaryReader br) return DateTime.FromOADate(temp); } - public override void WriteScalarValue(BinaryWriter bw, DateTime pValue) - { - bw.Write(pValue.ToOADate()); - } + public override void WriteScalarValue(BinaryWriter bw, DateTime pValue) => bw.Write(pValue.ToOADate()); } protected class VT_LPSTR_Property : TypedPropertyValue { - private byte[] data; private readonly int codePage; public VT_LPSTR_Property(VTPropertyType vType, int codePage, bool isVariant) : base(vType, isVariant) @@ -336,7 +287,7 @@ public VT_LPSTR_Property(VTPropertyType vType, int codePage, bool isVariant) : b public override string ReadScalarValue(BinaryReader br) { uint size = br.ReadUInt32(); - data = br.ReadBytes((int)size); + byte[] data = br.ReadBytes((int)size); string result = Encoding.GetEncoding(codePage).GetString(data); //result = result.Trim(new char[] { '\0' }); @@ -363,7 +314,7 @@ public override void WriteScalarValue(BinaryWriter bw, string pValue) } else if (codePage == CodePages.WinUnicode) { - data = Encoding.GetEncoding(codePage).GetBytes(pValue); + byte[] data = Encoding.GetEncoding(codePage).GetBytes(pValue); //if (data.Length >= 2 && data[data.Length - 2] == '\0' && data[data.Length - 1] == '\0') // addNullTerminator = false; @@ -389,7 +340,7 @@ public override void WriteScalarValue(BinaryWriter bw, string pValue) } else { - data = Encoding.GetEncoding(codePage).GetBytes(pValue); + byte[] data = Encoding.GetEncoding(codePage).GetBytes(pValue); //if (data.Length >= 1 && data[data.Length - 1] == '\0') // addNullTerminator = false; @@ -415,7 +366,7 @@ public override void WriteScalarValue(BinaryWriter bw, string pValue) } } - protected class VT_Unaligned_LPSTR_Property : VT_LPSTR_Property + protected sealed class VT_Unaligned_LPSTR_Property : VT_LPSTR_Property { public VT_Unaligned_LPSTR_Property(VTPropertyType vType, int codePage, bool isVariant) : base(vType, codePage, isVariant) { @@ -423,9 +374,8 @@ public VT_Unaligned_LPSTR_Property(VTPropertyType vType, int codePage, bool isVa } } - private class VT_LPWSTR_Property : TypedPropertyValue + private sealed class VT_LPWSTR_Property : TypedPropertyValue { - private byte[] data; private readonly int codePage; public VT_LPWSTR_Property(VTPropertyType vType, int codePage, bool isVariant) : base(vType, isVariant) @@ -436,7 +386,7 @@ public VT_LPWSTR_Property(VTPropertyType vType, int codePage, bool isVariant) : public override string ReadScalarValue(BinaryReader br) { uint nChars = br.ReadUInt32(); - data = br.ReadBytes((int)((nChars - 1) * 2)); //WChar- null terminator + byte[] data = br.ReadBytes((int)((nChars - 1) * 2)); //WChar- null terminator br.ReadBytes(2); // Skip null terminator string result = Encoding.Unicode.GetString(data); //result = result.Trim(new char[] { '\0' }); @@ -446,7 +396,7 @@ public override string ReadScalarValue(BinaryReader br) public override void WriteScalarValue(BinaryWriter bw, string pValue) { - data = Encoding.Unicode.GetBytes(pValue); + byte[] data = Encoding.Unicode.GetBytes(pValue); // The written data length field is the number of characters (not bytes) and must include a null terminator // add a null terminator if there isn't one already @@ -473,7 +423,7 @@ public override void WriteScalarValue(BinaryWriter bw, string pValue) } } - private class VT_FILETIME_Property : TypedPropertyValue + private sealed class VT_FILETIME_Property : TypedPropertyValue { public VT_FILETIME_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -486,13 +436,10 @@ public override DateTime ReadScalarValue(BinaryReader br) return DateTime.FromFileTimeUtc(tmp); } - public override void WriteScalarValue(BinaryWriter bw, DateTime pValue) - { - bw.Write(pValue.ToFileTimeUtc()); - } + public override void WriteScalarValue(BinaryWriter bw, DateTime pValue) => bw.Write(pValue.ToFileTimeUtc()); } - private class VT_DECIMAL_Property : TypedPropertyValue + private sealed class VT_DECIMAL_Property : TypedPropertyValue { public VT_DECIMAL_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -535,7 +482,7 @@ public override void WriteScalarValue(BinaryWriter bw, decimal pValue) } } - private class VT_BOOL_Property : TypedPropertyValue + private sealed class VT_BOOL_Property : TypedPropertyValue { public VT_BOOL_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -548,13 +495,10 @@ public override bool ReadScalarValue(BinaryReader br) //br.ReadUInt16();//padding } - public override void WriteScalarValue(BinaryWriter bw, bool pValue) - { - bw.Write(pValue ? (ushort)0xFFFF : (ushort)0); - } + public override void WriteScalarValue(BinaryWriter bw, bool pValue) => bw.Write(pValue ? (ushort)0xFFFF : (ushort)0); } - private class VT_CF_Property : TypedPropertyValue + private sealed class VT_CF_Property : TypedPropertyValue { public VT_CF_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -582,7 +526,7 @@ public override void WriteScalarValue(BinaryWriter bw, object pValue) } } - private class VT_BLOB_Property : TypedPropertyValue + private sealed class VT_BLOB_Property : TypedPropertyValue { public VT_BLOB_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -609,7 +553,7 @@ public override void WriteScalarValue(BinaryWriter bw, object pValue) } } - private class VT_CLSID_Property : TypedPropertyValue + private sealed class VT_CLSID_Property : TypedPropertyValue { public VT_CLSID_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { @@ -628,7 +572,7 @@ public override void WriteScalarValue(BinaryWriter bw, object pValue) } } - private class VT_VariantVector : TypedPropertyValue + private sealed class VT_VariantVector : TypedPropertyValue { private readonly int codePage; private readonly PropertyFactory factory; @@ -647,7 +591,7 @@ public override object ReadScalarValue(BinaryReader br) VTPropertyType vType = (VTPropertyType)br.ReadUInt16(); br.ReadUInt16(); // Ushort Padding - ITypedPropertyValue p = factory.NewProperty(vType, codePage, propertyIdentifier, true); + ITypedPropertyValue p = factory.CreateProperty(vType, codePage, propertyIdentifier, true); p.Read(br); return p; } diff --git a/OpenMcdf.Ole/ProperyIdentifiers.cs b/OpenMcdf.Ole/PropertyIdentifiers.cs similarity index 62% rename from OpenMcdf.Ole/ProperyIdentifiers.cs rename to OpenMcdf.Ole/PropertyIdentifiers.cs index 6dd53bf3..de87ae7a 100644 --- a/OpenMcdf.Ole/ProperyIdentifiers.cs +++ b/OpenMcdf.Ole/PropertyIdentifiers.cs @@ -4,7 +4,7 @@ namespace OpenMcdf.Ole; public static class PropertyIdentifiers { - public static ImmutableDictionary SummaryInfo { get; } = new Dictionary() + readonly static ImmutableDictionary SummaryInfo = new Dictionary() { {0x00000001, "CodePageString" }, {0x00000002, "PIDSI_TITLE" }, @@ -15,7 +15,6 @@ public static class PropertyIdentifiers {0x00000007, "PIDSI_TEMPLATE" }, {0x00000008, "PIDSI_LASTAUTHOR" }, {0x00000009, "PIDSI_REVNUMBER" }, - {0x00000012, "PIDSI_APPNAME" }, {0x0000000A, "PIDSI_EDITTIME" }, {0x0000000B, "PIDSI_LASTPRINTED" }, {0x0000000C, "PIDSI_CREATE_DTM" }, @@ -23,10 +22,11 @@ public static class PropertyIdentifiers {0x0000000E, "PIDSI_PAGECOUNT" }, {0x0000000F, "PIDSI_WORDCOUNT" }, {0x00000010, "PIDSI_CHARCOUNT" }, + {0x00000012, "PIDSI_APPNAME" }, {0x00000013, "PIDSI_DOC_SECURITY" } }.ToImmutableDictionary(); - public static ImmutableDictionary DocumentSummaryInfo { get; } = new Dictionary() + readonly static ImmutableDictionary DocumentSummaryInfo = new Dictionary() { {0x00000001, "CodePageString" }, {0x00000002, "PIDDSI_CATEGORY" }, @@ -45,4 +45,28 @@ public static class PropertyIdentifiers {0x0000000F, "PIDDSI_COMPANY" }, {0x00000010, "PIDDSI_LINKSDIRTY" } }.ToImmutableDictionary(); + + public static string GetDescription(uint identifier, ContainerType map, IDictionary? customDictionary = null) + { + IDictionary nameDictionary; + + if (customDictionary is null) + { + nameDictionary = map switch + { + ContainerType.SummaryInfo => SummaryInfo, + ContainerType.DocumentSummaryInfo => DocumentSummaryInfo, + _ => throw new ArgumentException("Unknown container type", nameof(map)), + }; + } + else + { + nameDictionary = customDictionary; + } + + if (nameDictionary.TryGetValue(identifier, out string? value)) + return value; + + return $"0x{identifier:x8}"; + } } diff --git a/OpenMcdf.Ole/PropertySet.cs b/OpenMcdf.Ole/PropertySet.cs index dcc8f990..196cf0d9 100644 --- a/OpenMcdf.Ole/PropertySet.cs +++ b/OpenMcdf.Ole/PropertySet.cs @@ -2,31 +2,34 @@ internal sealed class PropertySet { - public PropertyContext PropertyContext - { - get; set; - } + public PropertyContext PropertyContext { get; set; } = new(); public uint Size { get; set; } - public uint NumProperties { get; set; } + public List PropertyIdentifierAndOffsets { get; } = new(); - public List PropertyIdentifierAndOffsets { get; set; } = new List(); - - public List Properties { get; set; } = new List(); + public List Properties { get; } = new(); public void LoadContext(int propertySetOffset, BinaryReader br) { long currPos = br.BaseStream.Position; - - PropertyContext = new PropertyContext(); - int codePageOffset = (int)(propertySetOffset + PropertyIdentifierAndOffsets.Where(pio => pio.PropertyIdentifier == 1).First().Offset); + int codePageOffset = (int)(propertySetOffset + PropertyIdentifierAndOffsets.First(pio => pio.PropertyIdentifier == 1).Offset); br.BaseStream.Seek(codePageOffset, SeekOrigin.Begin); - VTPropertyType vType = (VTPropertyType)br.ReadUInt16(); + var vType = (VTPropertyType)br.ReadUInt16(); br.ReadUInt16(); // Ushort Padding PropertyContext.CodePage = (ushort)br.ReadInt16(); br.BaseStream.Position = currPos; } + + public void Add(IDictionary propertyNames) + { + DictionaryProperty dictionaryProperty = new(PropertyContext.CodePage) + { + Value = propertyNames + }; + Properties.Add(dictionaryProperty); + PropertyIdentifierAndOffsets.Add(new PropertyIdentifierAndOffset() { PropertyIdentifier = 0, Offset = 0 }); + } } diff --git a/OpenMcdf.Ole/PropertySetStream.cs b/OpenMcdf.Ole/PropertySetStream.cs index 3c3acb97..2f3de824 100644 --- a/OpenMcdf.Ole/PropertySetStream.cs +++ b/OpenMcdf.Ole/PropertySetStream.cs @@ -2,6 +2,14 @@ internal sealed class PropertySetStream { + private sealed class OffsetContainer + { + public int OffsetPS { get; set; } + + public List PropertyIdentifierOffsets { get; } = new(); + public List PropertyOffsets { get; } = new(); + } + public ushort ByteOrder { get; set; } public ushort Version { get; set; } public uint SystemIdentifier { get; set; } @@ -11,10 +19,8 @@ internal sealed class PropertySetStream public uint Offset0 { get; set; } public Guid FMTID1 { get; set; } public uint Offset1 { get; set; } - public PropertySet PropertySet0 { get; set; } - public PropertySet PropertySet1 { get; set; } - - //private SummaryInfoMap map; + public PropertySet? PropertySet0 { get; set; } + public PropertySet? PropertySet1 { get; set; } public PropertySetStream() { @@ -36,17 +42,19 @@ public void Read(BinaryReader br) Offset1 = br.ReadUInt32(); } + + uint size = br.ReadUInt32(); + uint propertyCount = br.ReadUInt32(); PropertySet0 = new PropertySet { - Size = br.ReadUInt32(), - NumProperties = br.ReadUInt32() + Size = size, }; // Create appropriate property factory based on the stream type - PropertyFactory factory = FMTID0 == WellKnownFormatIdentifiers.DocSummaryInformation ? DocumentSummaryInfoPropertyFactory.Instance : DefaultPropertyFactory.Instance; + PropertyFactory factory = FMTID0 == FormatIdentifiers.DocSummaryInformation ? DocumentSummaryInfoPropertyFactory.Default : DefaultPropertyFactory.Default; // Read property offsets (P0) - for (int i = 0; i < PropertySet0.NumProperties; i++) + for (int i = 0; i < propertyCount; i++) { PropertyIdentifierAndOffset pio = new() { @@ -59,23 +67,28 @@ public void Read(BinaryReader br) PropertySet0.LoadContext((int)Offset0, br); //Read CodePage, Locale // Read properties (P0) - for (int i = 0; i < PropertySet0.NumProperties; i++) + for (int i = 0; i < propertyCount; i++) { - br.BaseStream.Seek(Offset0 + PropertySet0.PropertyIdentifierAndOffsets[i].Offset, SeekOrigin.Begin); - PropertySet0.Properties.Add(ReadProperty(PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier, PropertySet0.PropertyContext.CodePage, br, factory)); + PropertyIdentifierAndOffset propertyIdentifierAndOffset = PropertySet0.PropertyIdentifierAndOffsets[i]; + br.BaseStream.Seek(Offset0 + propertyIdentifierAndOffset.Offset, SeekOrigin.Begin); + IProperty property = ReadProperty(propertyIdentifierAndOffset.PropertyIdentifier, PropertySet0.PropertyContext.CodePage, br, factory); + PropertySet0.Properties.Add(property); } if (NumPropertySets == 2) { br.BaseStream.Seek(Offset1, SeekOrigin.Begin); + + size = br.ReadUInt32(); + propertyCount = br.ReadUInt32(); + PropertySet1 = new PropertySet { - Size = br.ReadUInt32(), - NumProperties = br.ReadUInt32() + Size = size }; // Read property offsets - for (int i = 0; i < PropertySet1.NumProperties; i++) + for (int i = 0; i < propertyCount; i++) { PropertyIdentifierAndOffset pio = new() { @@ -88,32 +101,20 @@ public void Read(BinaryReader br) PropertySet1.LoadContext((int)Offset1, br); // Read properties - for (int i = 0; i < PropertySet1.NumProperties; i++) + for (int i = 0; i < propertyCount; i++) { - br.BaseStream.Seek(Offset1 + PropertySet1.PropertyIdentifierAndOffsets[i].Offset, SeekOrigin.Begin); - PropertySet1.Properties.Add(ReadProperty(PropertySet1.PropertyIdentifierAndOffsets[i].PropertyIdentifier, PropertySet1.PropertyContext.CodePage, br, DefaultPropertyFactory.Instance)); + PropertyIdentifierAndOffset idAndOffset = PropertySet1.PropertyIdentifierAndOffsets[i]; + br.BaseStream.Seek(Offset1 + idAndOffset.Offset, SeekOrigin.Begin); + IProperty property = ReadProperty(idAndOffset.PropertyIdentifier, PropertySet1.PropertyContext.CodePage, br, DefaultPropertyFactory.Default); + PropertySet1.Properties.Add(property); } } } - private class OffsetContainer - { - public int OffsetPS { get; set; } - - public List PropertyIdentifierOffsets { get; set; } - public List PropertyOffsets { get; set; } - - public OffsetContainer() - { - PropertyOffsets = new List(); - PropertyIdentifierOffsets = new List(); - } - } - public void Write(BinaryWriter bw) { - var oc0 = new OffsetContainer(); - var oc1 = new OffsetContainer(); + OffsetContainer oc0 = new(); + OffsetContainer oc1 = new(); bw.Write(ByteOrder); bw.Write(Version); @@ -130,17 +131,17 @@ public void Write(BinaryWriter bw) } oc0.OffsetPS = (int)bw.BaseStream.Position; - bw.Write(PropertySet0.Size); - bw.Write(PropertySet0.NumProperties); + bw.Write(PropertySet0!.Size); + bw.Write(PropertySet0.Properties.Count); // w property offsets - for (int i = 0; i < PropertySet0.NumProperties; i++) + for (int i = 0; i < PropertySet0.Properties.Count; i++) { - oc0.PropertyIdentifierOffsets.Add(bw.BaseStream.Position); //Offset of 4 to Offset value + oc0.PropertyIdentifierOffsets.Add(bw.BaseStream.Position); // Offset of 4 to Offset value PropertySet0.PropertyIdentifierAndOffsets[i].Write(bw); } - for (int i = 0; i < PropertySet0.NumProperties; i++) + for (int i = 0; i < PropertySet0.Properties.Count; i++) { oc0.PropertyOffsets.Add(bw.BaseStream.Position); PropertySet0.Properties[i].Write(bw); @@ -160,8 +161,8 @@ public void Write(BinaryWriter bw) { oc1.OffsetPS = (int)bw.BaseStream.Position; - bw.Write(PropertySet1.Size); - bw.Write(PropertySet1.NumProperties); + bw.Write(PropertySet1!.Size); + bw.Write(PropertySet1.Properties.Count); // w property offsets for (int i = 0; i < PropertySet1.PropertyIdentifierAndOffsets.Count; i++) @@ -170,7 +171,7 @@ public void Write(BinaryWriter bw) PropertySet1.PropertyIdentifierAndOffsets[i].Write(bw); } - for (int i = 0; i < PropertySet1.NumProperties; i++) + for (int i = 0; i < PropertySet1.Properties.Count; i++) { oc1.PropertyOffsets.Add(bw.BaseStream.Position); PropertySet1.Properties[i].Write(bw); @@ -203,7 +204,7 @@ public void Write(BinaryWriter bw) bw.Write((int)(oc0.PropertyOffsets[i] - oc0.OffsetPS)); } - if (PropertySet1 != null) + if (PropertySet1 is not null) { for (int i = 0; i < PropertySet1.PropertyIdentifierAndOffsets.Count; i++) { @@ -215,21 +216,19 @@ public void Write(BinaryWriter bw) private static IProperty ReadProperty(uint propertyIdentifier, int codePage, BinaryReader br, PropertyFactory factory) { - if (propertyIdentifier != 0) - { - var vType = (VTPropertyType)br.ReadUInt16(); - br.ReadUInt16(); // Ushort Padding - - ITypedPropertyValue pr = factory.NewProperty(vType, codePage, propertyIdentifier); - pr.Read(br); - - return pr; - } - else + if (propertyIdentifier == 0) { DictionaryProperty dictionaryProperty = new(codePage); dictionaryProperty.Read(br); return dictionaryProperty; } + + var vType = (VTPropertyType)br.ReadUInt16(); + br.ReadUInt16(); // Ushort Padding + + ITypedPropertyValue pr = factory.CreateProperty(vType, codePage, propertyIdentifier); + pr.Read(br); + + return pr; } } diff --git a/OpenMcdf.Ole/TypedPropertyValue.cs b/OpenMcdf.Ole/TypedPropertyValue.cs index 80d0e447..6bd2e11a 100644 --- a/OpenMcdf.Ole/TypedPropertyValue.cs +++ b/OpenMcdf.Ole/TypedPropertyValue.cs @@ -3,13 +3,12 @@ internal abstract class TypedPropertyValue : ITypedPropertyValue { private readonly VTPropertyType _VTType; + protected object? propertyValue; public PropertyType PropertyType => PropertyType.TypedPropertyValue; public VTPropertyType VTType => _VTType; - protected object propertyValue; - public TypedPropertyValue(VTPropertyType vtType, bool isVariant = false) { _VTType = vtType; @@ -23,7 +22,7 @@ public TypedPropertyValue(VTPropertyType vtType, bool isVariant = false) protected virtual bool NeedsPadding { get; set; } = true; - private PropertyDimensions CheckPropertyDimensions(VTPropertyType vtType) + private static PropertyDimensions CheckPropertyDimensions(VTPropertyType vtType) { if ((((ushort)vtType) & 0x1000) != 0) return PropertyDimensions.IsVector; @@ -32,14 +31,13 @@ private PropertyDimensions CheckPropertyDimensions(VTPropertyType vtType) return PropertyDimensions.IsScalar; } - public virtual object Value + public virtual object? Value { get => propertyValue; - set => propertyValue = value; } - public abstract T ReadScalarValue(BinaryReader br); + public abstract T? ReadScalarValue(BinaryReader br); public void Read(BinaryReader br) { @@ -64,11 +62,11 @@ public void Read(BinaryReader br) { uint nItems = br.ReadUInt32(); - List res = new List(); + List res = new(); for (int i = 0; i < nItems; i++) { - T s = ReadScalarValue(br); + T s = ReadScalarValue(br)!; res.Add(s); @@ -108,7 +106,7 @@ public void Write(BinaryWriter bw) bw.Write((ushort)_VTType); bw.Write((ushort)0); - WriteScalarValue(bw, (T)propertyValue); + WriteScalarValue(bw, (T)propertyValue!); size = (int)(bw.BaseStream.Position - currentPos); m = size % 4; @@ -124,7 +122,7 @@ public void Write(BinaryWriter bw) bw.Write((ushort)_VTType); bw.Write((ushort)0); - bw.Write((uint)((List)propertyValue).Count); + bw.Write((uint)((List)propertyValue!).Count); for (int i = 0; i < ((List)propertyValue).Count; i++) { diff --git a/OpenMcdf.Ole/VTPropertyType.cs b/OpenMcdf.Ole/VTPropertyType.cs index f6084dd3..67562fd5 100644 --- a/OpenMcdf.Ole/VTPropertyType.cs +++ b/OpenMcdf.Ole/VTPropertyType.cs @@ -1,5 +1,7 @@ namespace OpenMcdf.Ole; +#pragma warning disable CA1707 // Remove the underscores from member name + /// /// VARENUM /// From 7d68c67753cd60be41ccfe2d4669e1bdeb3390be Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 11:08:27 +1300 Subject: [PATCH 098/134] Allow opening old word docs --- OpenMcdf3/CfbBinaryReader.cs | 5 +++-- OpenMcdf3/DirectoryEntry.cs | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/OpenMcdf3/CfbBinaryReader.cs b/OpenMcdf3/CfbBinaryReader.cs index 5ffdb220..2d1e00a2 100644 --- a/OpenMcdf3/CfbBinaryReader.cs +++ b/OpenMcdf3/CfbBinaryReader.cs @@ -1,4 +1,5 @@ -using System.Text; +using System.Diagnostics; +using System.Text; #if NETSTANDARD2_0 using System.Buffers; @@ -78,7 +79,7 @@ public Header ReadHeader() if (header.MajorVersion is not (ushort)Version.V3 and not (ushort)Version.V4) throw new FormatException($"Unsupported major version: {header.MajorVersion}."); else if (header.MinorVersion is not Header.ExpectedMinorVersion) - throw new FormatException($"Unsupported minor version: {header.MinorVersion}."); + Trace.WriteLine($"Unexpected minor version: {header.MinorVersion}."); ushort byteOrder = ReadUInt16(); if (byteOrder != Header.LittleEndian) throw new FormatException($"Unsupported byte order: {byteOrder:X4}. Only little-endian is supported ({Header.LittleEndian:X4})."); diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index dadf45df..fff18c13 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -211,6 +211,7 @@ public void Recycle(StorageType storageType, string name) { StorageType.Stream => EntryType.Stream, StorageType.Storage => EntryType.Storage, + StorageType.Root => EntryType.Storage, _ => throw new InvalidOperationException("Invalid storage type.") }; From 2a6b0a30502775148e961d83b4e621b45fb18eb6 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 14:18:03 +1300 Subject: [PATCH 099/134] Fix warnings --- OpenMcdf3/DirectoryEntry.cs | 11 ++++++++ OpenMcdf3/Header.cs | 28 ++++++++++++++----- .../StreamDataProvider.cs | 4 +-- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf3/DirectoryEntry.cs index fff18c13..8b9b36f1 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf3/DirectoryEntry.cs @@ -154,11 +154,22 @@ public string NameString set => NameLength = (ushort)(Encoding.Unicode.GetBytes(value, 0, value.Length, Name, 0) + 2); } + public override int GetHashCode() + { + HashCode code = new(); + code.Add(Id); + code.Add(NameLength); + foreach (byte b in Name) + code.Add(b); + return code.GetHashCode(); + } + public override bool Equals(object? obj) => Equals(obj as DirectoryEntry); public bool Equals(DirectoryEntry? other) { return other is not null + && Id == other.Id && Name.SequenceEqual(other.Name) && NameLength == other.NameLength && Type == other.Type diff --git a/OpenMcdf3/Header.cs b/OpenMcdf3/Header.cs index 3c99d722..900a2936 100644 --- a/OpenMcdf3/Header.cs +++ b/OpenMcdf3/Header.cs @@ -139,6 +139,27 @@ public Header(Version version = Version.V3) } } + public override int GetHashCode() + { + HashCode code = new(); + code.Add(CLSID); + code.Add(MinorVersion); + code.Add(MajorVersion); + code.Add(SectorShift); + code.Add(DirectorySectorCount); + code.Add(FatSectorCount); + code.Add(FirstDirectorySectorId); + code.Add(TransactionSignature); + code.Add(FatSectorCount); + code.Add(FirstMiniFatSectorId); + code.Add(MiniFatSectorCount); + code.Add(FirstDifatSectorId); + code.Add(DifatSectorCount); + foreach (uint value in Difat) + code.Add(value); + return code.ToHashCode(); + } + public override bool Equals(object? obj) => Equals(obj as Header); public bool Equals(Header? other) @@ -159,12 +180,5 @@ public bool Equals(Header? other) && Difat.SequenceEqual(other.Difat); } - public override int GetHashCode() - { - return HashCode.Combine( - HashCode.Combine(CLSID, MinorVersion, MajorVersion, SectorShift, DirectorySectorCount, FatSectorCount, FirstDirectorySectorId, TransactionSignature), - HashCode.Combine(FirstMiniFatSectorId, MiniFatSectorCount, FirstDifatSectorId, DifatSectorCount, Difat)); - } - public override string ToString() => $"MajorVersion: {MajorVersion}, MinorVersion: {MinorVersion}, FirstDirectorySectorId: {FirstDirectorySectorId}, FirstMiniFatSectorId: {FirstMiniFatSectorId}"; } diff --git a/StructuredStorageExplorer/StreamDataProvider.cs b/StructuredStorageExplorer/StreamDataProvider.cs index ffecb71d..80aab085 100644 --- a/StructuredStorageExplorer/StreamDataProvider.cs +++ b/StructuredStorageExplorer/StreamDataProvider.cs @@ -74,12 +74,12 @@ public void ApplyChanges() /// /// Occurs, when the write buffer contains new changes. /// - public event EventHandler Changed; + public event EventHandler? Changed; /// /// Occurs, when InsertBytes or DeleteBytes method is called. /// - public event EventHandler LengthChanged; + public event EventHandler? LengthChanged; /// /// Reads a byte from the byte collection. From 97ca43a985d6f10f785c983418aaa3ce9d0eb5d6 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 14:28:02 +1300 Subject: [PATCH 100/134] Refactor explorer --- StructuredStorageExplorer/MainForm.cs | 185 ++++++++++++-------------- StructuredStorageExplorer/Utils.cs | 24 ++-- 2 files changed, 95 insertions(+), 114 deletions(-) diff --git a/StructuredStorageExplorer/MainForm.cs b/StructuredStorageExplorer/MainForm.cs index 812af78d..4e64cb00 100644 --- a/StructuredStorageExplorer/MainForm.cs +++ b/StructuredStorageExplorer/MainForm.cs @@ -12,7 +12,33 @@ namespace StructuredStorageExplorer; -record class NodeSelection(Storage Parent, EntryInfo EntryInfo); +sealed record class NodeSelection(Storage? Parent, EntryInfo EntryInfo) +{ + public string SanitizedFileName + { + get + { + // A lot of stream and storage have only non-printable characters. + // We need to sanitize filename. + + string sanitizedFileName = string.Empty; + + foreach (char c in EntryInfo.Name) + { + UnicodeCategory category = char.GetUnicodeCategory(c); + if (category is UnicodeCategory.LetterNumber or UnicodeCategory.LowercaseLetter or UnicodeCategory.UppercaseLetter) + sanitizedFileName += c; + } + + if (string.IsNullOrEmpty(sanitizedFileName)) + { + sanitizedFileName = "tempFileName"; + } + + return $"{sanitizedFileName}.bin"; + } + } +} /// /// Sample Structured Storage viewer to @@ -21,8 +47,6 @@ record class NodeSelection(Storage Parent, EntryInfo EntryInfo); public partial class MainForm : Form { private RootStorage? cf; - private FileStream? fs; - private bool canUpdate; public MainForm() { @@ -32,15 +56,10 @@ public MainForm() tabControl1.TabPages.Remove(tabPage2); #endif - //Load images for icons from resx - Image folderImage = (Image)Resources.ResourceManager.GetObject("storage"); - Image streamImage = (Image)Resources.ResourceManager.GetObject("stream"); - //Image olePropsImage = (Image)Properties.Resources.ResourceManager.GetObject("oleprops"); - - treeView1.ImageList = new ImageList(); - treeView1.ImageList.Images.Add(folderImage); - treeView1.ImageList.Images.Add(streamImage); - //treeView1.ImageList.Images.Add(olePropsImage); + ImageList imageList = new(); + imageList.Images.Add(Resources.storage); + imageList.Images.Add(Resources.stream); + treeView1.ImageList = imageList; saveAsToolStripMenuItem.Enabled = false; updateCurrentFileToolStripMenuItem.Enabled = false; @@ -52,12 +71,7 @@ private void OpenFile() { CloseCurrentFile(); - treeView1.Nodes.Clear(); - fileNameLabel.Text = openFileDialog1.FileName; - LoadFile(openFileDialog1.FileName, true); - canUpdate = true; - saveAsToolStripMenuItem.Enabled = true; - updateCurrentFileToolStripMenuItem.Enabled = true; + LoadFile(openFileDialog1.FileName); } } @@ -66,9 +80,6 @@ private void CloseCurrentFile() cf?.Dispose(); cf = null; - fs?.Close(); - fs = null; - treeView1.Nodes.Clear(); fileNameLabel.Text = string.Empty; saveAsToolStripMenuItem.Enabled = false; @@ -87,56 +98,62 @@ private void CreateNewFile() { CloseCurrentFile(); - cf = RootStorage.Create(Path.GetTempFileName()); - canUpdate = false; - saveAsToolStripMenuItem.Enabled = true; + try + { + string fileName = Path.GetTempFileName(); - updateCurrentFileToolStripMenuItem.Enabled = false; + cf = RootStorage.Create(fileName); - RefreshTree(); + fileNameLabel.Text = fileName; + saveAsToolStripMenuItem.Enabled = true; + updateCurrentFileToolStripMenuItem.Enabled = true; + + RefreshTree(); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FormatException) + { + CloseCurrentFile(); + + MessageBox.Show($"Error creating file: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } } private void RefreshTree() { treeView1.Nodes.Clear(); - TreeNode root = treeView1.Nodes.Add("Root Entry", "Root"); - root.ImageIndex = 0; - root.Tag = new NodeSelection(null, cf.EntryInfo); - // Recursive function to get all storage and streams - AddNodes(root, cf); + if (cf is not null) + { + TreeNode root = treeView1.Nodes.Add(cf.EntryInfo.Name); + root.ImageIndex = 0; + root.Tag = new NodeSelection(null, cf.EntryInfo); + + // Recursive function to get all storage and streams + AddNodes(root, cf); + } } - private void LoadFile(string fileName, bool enableCommit) + private void LoadFile(string fileName) { - fs = new FileStream( - fileName, - FileMode.Open, - enableCommit ? - FileAccess.ReadWrite - : FileAccess.Read); - try { cf?.Dispose(); cf = null; // Load file - cf = RootStorage.Open(fs, enableCommit ? StorageModeFlags.Transacted : StorageModeFlags.None); + cf = RootStorage.Open(fileName, FileMode.Open); + + fileNameLabel.Text = fileName; + saveAsToolStripMenuItem.Enabled = true; + updateCurrentFileToolStripMenuItem.Enabled = true; RefreshTree(); } - catch (Exception ex) + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FormatException) { - cf?.Dispose(); - cf = null; - - fs?.Close(); - fs = null; + CloseCurrentFile(); - treeView1.Nodes.Clear(); - fileNameLabel.Text = string.Empty; - MessageBox.Show("Internal error: " + ex.Message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show($"Error opening file: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } @@ -150,22 +167,18 @@ private static void AddNodes(TreeNode node, Storage storage) foreach (EntryInfo item in storage.EnumerateEntries()) { TreeNode childNode = node.Nodes.Add(item.Name); - childNode.Tag = new NodeSelection(storage, item); if (item.Type is EntryType.Storage) { - // Storage childNode.ImageIndex = 0; childNode.SelectedImageIndex = 0; Storage subStorage = storage.OpenStorage(item.Name); - // Recursion into the storage AddNodes(childNode, subStorage); } else { - // Stream childNode.ImageIndex = 1; childNode.SelectedImageIndex = 1; } @@ -175,31 +188,13 @@ private static void AddNodes(TreeNode node, Storage storage) private void exportDataToolStripMenuItem_Click(object sender, EventArgs e) { // No export if storage - NodeSelection? selection = treeView1.SelectedNode?.Tag as NodeSelection; - if (selection is null || selection.EntryInfo.Type is not EntryType.Stream) + if (treeView1.SelectedNode?.Tag is not NodeSelection selection || selection.EntryInfo.Type is not EntryType.Stream || selection.Parent is null) { MessageBox.Show("Only stream data can be exported", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } - // A lot of stream and storage have only non-printable characters. - // We need to sanitize filename. - - string sanitizedFileName = string.Empty; - - foreach (char c in selection.EntryInfo.Name) - { - UnicodeCategory category = char.GetUnicodeCategory(c); - if (category is UnicodeCategory.LetterNumber or UnicodeCategory.LowercaseLetter or UnicodeCategory.UppercaseLetter) - sanitizedFileName += c; - } - - if (string.IsNullOrEmpty(sanitizedFileName)) - { - sanitizedFileName = "tempFileName"; - } - - saveFileDialog1.FileName = $"{sanitizedFileName}.bin"; + saveFileDialog1.FileName = selection.SanitizedFileName; if (saveFileDialog1.ShowDialog() == DialogResult.OK) { @@ -209,18 +204,16 @@ private void exportDataToolStripMenuItem_Click(object sender, EventArgs e) using CfbStream cfbStream = selection.Parent.OpenStream(selection.EntryInfo.Name); cfbStream.CopyTo(fs); } - catch (Exception ex) + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { - treeView1.Nodes.Clear(); - MessageBox.Show($"Internal error: {ex.Message}", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show($"Error saving file: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } private void removeToolStripMenuItem_Click(object sender, EventArgs e) { - TreeNode n = treeView1.SelectedNode; - if (n?.Parent?.Tag is NodeSelection selection && selection.Parent is not null) + if (treeView1.SelectedNode?.Tag is NodeSelection selection && selection.Parent is not null) selection.Parent.Delete(selection.EntryInfo.Name); RefreshTree(); @@ -237,16 +230,12 @@ private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) private void updateCurrentFileToolStripMenuItem_Click(object sender, EventArgs e) { - if (canUpdate) - { - if (hexEditor.ByteProvider is not null && hexEditor.ByteProvider.HasChanges()) - hexEditor.ByteProvider.ApplyChanges(); - cf.Commit(); - } - else - { - MessageBox.Show("Cannot update a compound document that is not based on a stream or on a file", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } + if (cf is null) + return; + + if (hexEditor.ByteProvider is not null && hexEditor.ByteProvider.HasChanges()) + hexEditor.ByteProvider.ApplyChanges(); + cf.Commit(); } private void addStreamToolStripMenuItem_Click(object sender, EventArgs e) @@ -303,8 +292,7 @@ private void importDataStripMenuItem1_Click(object sender, EventArgs e) private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { - cf?.Dispose(); - cf = null; + CloseCurrentFile(); } private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) @@ -365,7 +353,7 @@ private void treeView1_MouseUp(object sender, MouseEventArgs e) if (nodeSelection.EntryInfo.Type is EntryType.Stream) { - using CfbStream stream = nodeSelection.Parent.OpenStream(nodeSelection.EntryInfo.Name); + using CfbStream stream = nodeSelection.Parent!.OpenStream(nodeSelection.EntryInfo.Name); addStorageStripMenuItem1.Enabled = false; addStreamToolStripMenuItem.Enabled = false; importDataStripMenuItem1.Enabled = true; @@ -384,18 +372,11 @@ private void treeView1_MouseUp(object sender, MouseEventArgs e) propertyGrid1.SelectedObject = nodeSelection.EntryInfo; } - catch (Exception ex) + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FormatException) { - cf?.Dispose(); - cf = null; - - fs?.Close(); - fs = null; - - treeView1.Nodes.Clear(); - fileNameLabel.Text = string.Empty; + CloseCurrentFile(); - MessageBox.Show($"Internal error: {ex.Message}", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show($"Error: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } @@ -435,7 +416,7 @@ private void UpdateOleTab(CfbStream stream) ds.AcceptChanges(); dgvOLEProps.DataSource = ds; - if (c.HasUserDefinedProperties) + if (c.UserDefinedProperties is not null) { DataTable ds2 = new(); ds2.Columns.Add("Name", typeof(string)); diff --git a/StructuredStorageExplorer/Utils.cs b/StructuredStorageExplorer/Utils.cs index 5ecc3b14..def2be8c 100644 --- a/StructuredStorageExplorer/Utils.cs +++ b/StructuredStorageExplorer/Utils.cs @@ -1,42 +1,42 @@ namespace StructuredStorageExplorer; -class Utils +static class Utils { public static DialogResult InputBox(string title, string promptText, ref string value) { - Form form = new Form(); - Label label = new Label(); - TextBox textBox = new TextBox(); - Button buttonOk = new Button(); - Button buttonCancel = new Button(); + using Form form = new(); + using Label label = new(); + using TextBox textBox = new(); + using Button buttonOK = new(); + using Button buttonCancel = new(); form.Text = title; label.Text = promptText; textBox.Text = value; - buttonOk.Text = "OK"; + buttonOK.Text = "OK"; buttonCancel.Text = "Cancel"; - buttonOk.DialogResult = DialogResult.OK; + buttonOK.DialogResult = DialogResult.OK; buttonCancel.DialogResult = DialogResult.Cancel; label.SetBounds(9, 20, 372, 13); textBox.SetBounds(12, 36, 372, 20); - buttonOk.SetBounds(228, 72, 75, 23); + buttonOK.SetBounds(228, 72, 75, 23); buttonCancel.SetBounds(309, 72, 75, 23); label.AutoSize = true; textBox.Anchor = textBox.Anchor | AnchorStyles.Right; - buttonOk.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + buttonOK.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; form.ClientSize = new Size(396, 107); - form.Controls.AddRange([label, textBox, buttonOk, buttonCancel]); + form.Controls.AddRange([label, textBox, buttonOK, buttonCancel]); form.ClientSize = new Size(Math.Max(300, label.Right + 10), form.ClientSize.Height); form.FormBorderStyle = FormBorderStyle.FixedDialog; form.StartPosition = FormStartPosition.CenterScreen; form.MinimizeBox = false; form.MaximizeBox = false; - form.AcceptButton = buttonOk; + form.AcceptButton = buttonOK; form.CancelButton = buttonCancel; DialogResult dialogResult = form.ShowDialog(); From a6a7ddfb9f7138abb214c692e7226b82f94ee5d3 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 14:53:40 +1300 Subject: [PATCH 101/134] Disable warnings for netstandard2.0 --- .editorconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.editorconfig b/.editorconfig index faaf28ca..9f6b96e1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -349,6 +349,10 @@ dotnet_style_qualification_for_property = false:none dotnet_style_qualification_for_method = false:none dotnet_style_qualification_for_event = false:none dotnet_style_prefer_collection_expression = when_types_exactly_match:suggestion +dotnet_diagnostic.CA1513.severity = none +dotnet_diagnostic.CA1510.severity = none +dotnet_diagnostic.CA1847.severity = none +dotnet_diagnostic.CA1512.severity = none [*.{hlsl}] From 0b52b2d17742c7551886e84c0df82da40d199457 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 15:20:26 +1300 Subject: [PATCH 102/134] Enable spell checking --- .editorconfig | 23 +++-------------------- OpenMcdf.Ole/PropertyFactory.cs | 4 ++-- OpenMcdf3.Tests/AssemblyInfo.cs | 2 +- OpenMcdf3.sln | 1 + OpenMcdf3/AssemblyInfo.cs | 2 +- exclusion.dic | 22 ++++++++++++++++++++++ 6 files changed, 30 insertions(+), 24 deletions(-) create mode 100644 exclusion.dic diff --git a/.editorconfig b/.editorconfig index 9f6b96e1..6e5fa16a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,31 +4,14 @@ root = true #### Visual Studio Spell Checker #### [*] -spelling_exclusion_path = .\ignored-words.txt +spelling_exclusion_path = .\exclusion.dic #### VS Spell Checker #### [*] -vsspell_section_id = 57c612f5006c4c25a138fae34ef5a504 +vsspell_section_id = root vsspell_determine_resource_file_language_from_name = true -vsspell_additional_dictionary_folders_57c612f5006c4c25a138fae34ef5a504 = .\dictionaries -vsspell_ignored_words_57c612f5006c4c25a138fae34ef5a504 = clear_inherited|File:ignored-words.txt|Readit - -[*.{cs,da,de,el,es,fr,fi,it,ko,nb-NO,nl-BE,pl,pt,pt-BR,ru,sk,sv}.resx] -vsspell_spell_check_as_you_type = false -vsspell_include_in_project_spell_check = false - -[*.{Designer.cs,ai,crproj,csproj,props,runsettings,targets,vcxproj,wixproj,wxi,wxl,wxs,xaml,xml}] -vsspell_spell_check_as_you_type = false -vsspell_include_in_project_spell_check = false - -[Service References/*/*] -vsspell_spell_check_as_you_type = false -vsspell_include_in_project_spell_check = false - -[{ignored-words.txt,ControlWords.cs,LanguageIdTests.cs,Phoneme.cs,UnicodeData.txt}] -vsspell_spell_check_as_you_type = false -vsspell_include_in_project_spell_check = false +vsspell_ignored_words_root = clear_inherited|File:exclusion.dic # C# files [*.cs] diff --git a/OpenMcdf.Ole/PropertyFactory.cs b/OpenMcdf.Ole/PropertyFactory.cs index 968e7f30..f8a699f1 100644 --- a/OpenMcdf.Ole/PropertyFactory.cs +++ b/OpenMcdf.Ole/PropertyFactory.cs @@ -326,7 +326,7 @@ public override void WriteScalarValue(BinaryWriter bw, string pValue) // var mod = dataLength % 4; // pad to multiple of 4 bytes - bw.Write(dataLength); // datalength of string + null char (unicode) + bw.Write(dataLength); // data length of string + null char (unicode) bw.Write(data); // string //if (addNullTerminator) @@ -352,7 +352,7 @@ public override void WriteScalarValue(BinaryWriter bw, string pValue) uint mod = dataLength % 4; // pad to multiple of 4 bytes - bw.Write(dataLength); // datalength of string + null char (unicode) + bw.Write(dataLength); // data length of string + null char (unicode) bw.Write(data); // string //if (addNullTerminator) diff --git a/OpenMcdf3.Tests/AssemblyInfo.cs b/OpenMcdf3.Tests/AssemblyInfo.cs index 6df06f30..02626394 100644 --- a/OpenMcdf3.Tests/AssemblyInfo.cs +++ b/OpenMcdf3.Tests/AssemblyInfo.cs @@ -3,7 +3,7 @@ // In SDK-style projects such as this one, several assembly attributes that were historically // defined in this file are now automatically added during build and populated with // values defined in project properties. For details of which attributes are included -// and how to customise this process see: https://aka.ms/assembly-info-properties +// and how to customize this process see: https://aka.ms/assembly-info-properties // Setting ComVisible to false makes the types in this assembly not visible to COM diff --git a/OpenMcdf3.sln b/OpenMcdf3.sln index 9c357779..38cdc0f8 100644 --- a/OpenMcdf3.sln +++ b/OpenMcdf3.sln @@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .globalconfig = .globalconfig + exclusion.dic = exclusion.dic EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3.Benchmarks", "OpenMcdf3.Benchmarks\OpenMcdf3.Benchmarks.csproj", "{44C718AD-F7FE-4733-80A8-636E5E7E63F3}" diff --git a/OpenMcdf3/AssemblyInfo.cs b/OpenMcdf3/AssemblyInfo.cs index c16894d6..b7349b84 100644 --- a/OpenMcdf3/AssemblyInfo.cs +++ b/OpenMcdf3/AssemblyInfo.cs @@ -4,7 +4,7 @@ // In SDK-style projects such as this one, several assembly attributes that were historically // defined in this file are now automatically added during build and populated with // values defined in project properties. For details of which attributes are included -// and how to customise this process see: https://aka.ms/assembly-info-properties +// and how to customize this process see: https://aka.ms/assembly-info-properties // Setting ComVisible to false makes the types in this assembly not visible to COM diff --git a/exclusion.dic b/exclusion.dic new file mode 100644 index 00000000..7cd19242 --- /dev/null +++ b/exclusion.dic @@ -0,0 +1,22 @@ +Blaseotto +CFB +CLSID +codepage +depersist +DIFAT +endian +enqueuing +enum +enums +Java +LINQ +MCDF +metadata +namespace +namespaces +netstandard +OpenMcdf +toolbar +typelib +Unicode +versioned From 43c449c745979087f66344bf5340606d5b381082 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 15:21:27 +1300 Subject: [PATCH 103/134] Rename to OpenMcdf --- .../FileStreamRead.cs | 4 ++-- .../FileStreamTransactedWrite.cs | 4 ++-- .../FileStreamWrite.cs | 4 ++-- .../MemoryStreamRead.cs | 4 ++-- .../MemoryStreamTransactedWrite.cs | 4 ++-- .../MemoryStreamWrite.cs | 4 ++-- .../OpenMcdf.Benchmarks.csproj | 2 +- .../OpenMcdfBenchmarks.cs | 2 +- .../Program.cs | 2 +- .../StructuredStorageBenchmarks.cs | 2 +- OpenMcdf.Ole/OlePropertiesContainer.cs | 2 +- OpenMcdf.Ole/OpenMcdf.Ole.csproj | 3 ++- .../OpenMcdf.Perf.csproj | 2 +- {OpenMcdf3.Perf => OpenMcdf.Perf}/Program.cs | 2 +- {OpenMcdf3.Tests => OpenMcdf.Tests}/AssemblyInfo.cs | 0 .../BinaryReaderTests.cs | 2 +- .../BinaryWriterTests.cs | 2 +- {OpenMcdf3.Tests => OpenMcdf.Tests}/DebugWriter.cs | 2 +- .../EntryInfoTests.cs | 2 +- .../MultipleStorage.cfs | Bin .../MultipleStorage2.cfs | Bin .../MultipleStorage3.cfs | Bin .../MultipleStorage4.cfs | Bin .../OpenMcdf.Tests.csproj | 2 +- {OpenMcdf3.Tests => OpenMcdf.Tests}/StorageTests.cs | 4 +++- {OpenMcdf3.Tests => OpenMcdf.Tests}/StreamAssert.cs | 2 +- {OpenMcdf3.Tests => OpenMcdf.Tests}/StreamTests.cs | 2 +- .../TestStream_v3_0.cfs | Bin .../TestStream_v3_4095.cfs | Bin .../TestStream_v3_4096.cfs | Bin .../TestStream_v3_4097.cfs | Bin .../TestStream_v3_511.cfs | Bin .../TestStream_v3_512.cfs | Bin .../TestStream_v3_513.cfs | Bin .../TestStream_v3_63.cfs | Bin .../TestStream_v3_64.cfs | Bin .../TestStream_v3_65.cfs | Bin .../TestStream_v3_65536.cfs | Bin .../TestStream_v4_0.cfs | Bin .../TestStream_v4_4095.cfs | Bin .../TestStream_v4_4096.cfs | Bin .../TestStream_v4_4097.cfs | Bin .../TestStream_v4_511.cfs | Bin .../TestStream_v4_512.cfs | Bin .../TestStream_v4_513.cfs | Bin .../TestStream_v4_63.cfs | Bin .../TestStream_v4_64.cfs | Bin .../TestStream_v4_65.cfs | Bin OpenMcdf3.sln => OpenMcdf.sln | 8 ++++---- {OpenMcdf3 => OpenMcdf}/AssemblyInfo.cs | 2 +- {OpenMcdf3 => OpenMcdf}/CfbBinaryReader.cs | 2 +- {OpenMcdf3 => OpenMcdf}/CfbBinaryWriter.cs | 2 +- {OpenMcdf3 => OpenMcdf}/CfbStream.cs | 2 +- {OpenMcdf3 => OpenMcdf}/DifatSectorEnumerator.cs | 2 +- {OpenMcdf3 => OpenMcdf}/DirectoryEntries.cs | 2 +- {OpenMcdf3 => OpenMcdf}/DirectoryEntry.cs | 2 +- {OpenMcdf3 => OpenMcdf}/DirectoryEntryComparer.cs | 2 +- {OpenMcdf3 => OpenMcdf}/DirectoryEntryEnumerator.cs | 2 +- {OpenMcdf3 => OpenMcdf}/DirectoryTree.cs | 2 +- {OpenMcdf3 => OpenMcdf}/DirectoryTreeEnumerator.cs | 2 +- {OpenMcdf3 => OpenMcdf}/EntryInfo.cs | 2 +- {OpenMcdf3 => OpenMcdf}/Fat.cs | 2 +- {OpenMcdf3 => OpenMcdf}/FatChainEntry.cs | 2 +- {OpenMcdf3 => OpenMcdf}/FatChainEnumerator.cs | 2 +- {OpenMcdf3 => OpenMcdf}/FatEntry.cs | 2 +- {OpenMcdf3 => OpenMcdf}/FatEnumerator.cs | 2 +- {OpenMcdf3 => OpenMcdf}/FatSectorEnumerator.cs | 2 +- {OpenMcdf3 => OpenMcdf}/FatStream.cs | 2 +- {OpenMcdf3 => OpenMcdf}/Header.cs | 2 +- {OpenMcdf3 => OpenMcdf}/IOContext.cs | 2 +- {OpenMcdf3 => OpenMcdf}/MiniFat.cs | 2 +- {OpenMcdf3 => OpenMcdf}/MiniFatChainEnumerator.cs | 2 +- {OpenMcdf3 => OpenMcdf}/MiniFatEnumerator.cs | 2 +- {OpenMcdf3 => OpenMcdf}/MiniFatStream.cs | 2 +- {OpenMcdf3 => OpenMcdf}/MiniSector.cs | 2 +- .../OpenMcdf3.csproj => OpenMcdf/OpenMcdf.csproj | 2 ++ {OpenMcdf3 => OpenMcdf}/RootStorage.cs | 2 +- {OpenMcdf3 => OpenMcdf}/Sector.cs | 2 +- {OpenMcdf3 => OpenMcdf}/SectorDataCache.cs | 2 +- {OpenMcdf3 => OpenMcdf}/SectorType.cs | 2 +- {OpenMcdf3 => OpenMcdf}/Storage.cs | 2 +- {OpenMcdf3 => OpenMcdf}/StreamExtensions.cs | 2 +- {OpenMcdf3 => OpenMcdf}/System/.editorconfig | 0 {OpenMcdf3 => OpenMcdf}/System/.globalconfig | 0 {OpenMcdf3 => OpenMcdf}/System/CompilerServices.cs | 0 {OpenMcdf3 => OpenMcdf}/System/Index.cs | 0 .../System/NullableAttributes.cs | 0 {OpenMcdf3 => OpenMcdf}/System/Range.cs | 0 {OpenMcdf3 => OpenMcdf}/ThrowHelper.cs | 2 +- {OpenMcdf3 => OpenMcdf}/TransactedStream.cs | 2 +- StructuredStorageExplorer/MainForm.cs | 2 +- StructuredStorageExplorer/StreamDataProvider.cs | 2 +- .../StructuredStorageExplorer.csproj | 2 +- 93 files changed, 74 insertions(+), 69 deletions(-) rename {OpenMcdf3.Benchmarks => OpenMcdf.Benchmarks}/FileStreamRead.cs (95%) rename {OpenMcdf3.Benchmarks => OpenMcdf.Benchmarks}/FileStreamTransactedWrite.cs (95%) rename {OpenMcdf3.Benchmarks => OpenMcdf.Benchmarks}/FileStreamWrite.cs (95%) rename {OpenMcdf3.Benchmarks => OpenMcdf.Benchmarks}/MemoryStreamRead.cs (95%) rename {OpenMcdf3.Benchmarks => OpenMcdf.Benchmarks}/MemoryStreamTransactedWrite.cs (94%) rename {OpenMcdf3.Benchmarks => OpenMcdf.Benchmarks}/MemoryStreamWrite.cs (95%) rename OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj => OpenMcdf.Benchmarks/OpenMcdf.Benchmarks.csproj (88%) rename {OpenMcdf3.Benchmarks => OpenMcdf.Benchmarks}/OpenMcdfBenchmarks.cs (97%) rename {OpenMcdf3.Benchmarks => OpenMcdf.Benchmarks}/Program.cs (86%) rename {OpenMcdf3.Benchmarks => OpenMcdf.Benchmarks}/StructuredStorageBenchmarks.cs (98%) rename OpenMcdf3.Perf/OpenMcdf3.Perf.csproj => OpenMcdf.Perf/OpenMcdf.Perf.csproj (80%) rename {OpenMcdf3.Perf => OpenMcdf.Perf}/Program.cs (98%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/AssemblyInfo.cs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/BinaryReaderTests.cs (97%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/BinaryWriterTests.cs (98%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/DebugWriter.cs (94%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/EntryInfoTests.cs (93%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/MultipleStorage.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/MultipleStorage2.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/MultipleStorage3.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/MultipleStorage4.cfs (100%) rename OpenMcdf3.Tests/OpenMcdf3.Tests.csproj => OpenMcdf.Tests/OpenMcdf.Tests.csproj (98%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/StorageTests.cs (99%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/StreamAssert.cs (96%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/StreamTests.cs (99%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v3_0.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v3_4095.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v3_4096.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v3_4097.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v3_511.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v3_512.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v3_513.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v3_63.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v3_64.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v3_65.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v3_65536.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v4_0.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v4_4095.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v4_4096.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v4_4097.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v4_511.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v4_512.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v4_513.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v4_63.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v4_64.cfs (100%) rename {OpenMcdf3.Tests => OpenMcdf.Tests}/TestStream_v4_65.cfs (100%) rename OpenMcdf3.sln => OpenMcdf.sln (86%) rename {OpenMcdf3 => OpenMcdf}/AssemblyInfo.cs (94%) rename {OpenMcdf3 => OpenMcdf}/CfbBinaryReader.cs (99%) rename {OpenMcdf3 => OpenMcdf}/CfbBinaryWriter.cs (99%) rename {OpenMcdf3 => OpenMcdf}/CfbStream.cs (99%) rename {OpenMcdf3 => OpenMcdf}/DifatSectorEnumerator.cs (99%) rename {OpenMcdf3 => OpenMcdf}/DirectoryEntries.cs (99%) rename {OpenMcdf3 => OpenMcdf}/DirectoryEntry.cs (99%) rename {OpenMcdf3 => OpenMcdf}/DirectoryEntryComparer.cs (97%) rename {OpenMcdf3 => OpenMcdf}/DirectoryEntryEnumerator.cs (98%) rename {OpenMcdf3 => OpenMcdf}/DirectoryTree.cs (99%) rename {OpenMcdf3 => OpenMcdf}/DirectoryTreeEnumerator.cs (98%) rename {OpenMcdf3 => OpenMcdf}/EntryInfo.cs (93%) rename {OpenMcdf3 => OpenMcdf}/Fat.cs (99%) rename {OpenMcdf3 => OpenMcdf}/FatChainEntry.cs (93%) rename {OpenMcdf3 => OpenMcdf}/FatChainEnumerator.cs (99%) rename {OpenMcdf3 => OpenMcdf}/FatEntry.cs (93%) rename {OpenMcdf3 => OpenMcdf}/FatEnumerator.cs (98%) rename {OpenMcdf3 => OpenMcdf}/FatSectorEnumerator.cs (99%) rename {OpenMcdf3 => OpenMcdf}/FatStream.cs (99%) rename {OpenMcdf3 => OpenMcdf}/Header.cs (99%) rename {OpenMcdf3 => OpenMcdf}/IOContext.cs (99%) rename {OpenMcdf3 => OpenMcdf}/MiniFat.cs (99%) rename {OpenMcdf3 => OpenMcdf}/MiniFatChainEnumerator.cs (99%) rename {OpenMcdf3 => OpenMcdf}/MiniFatEnumerator.cs (99%) rename {OpenMcdf3 => OpenMcdf}/MiniFatStream.cs (99%) rename {OpenMcdf3 => OpenMcdf}/MiniSector.cs (98%) rename OpenMcdf3/OpenMcdf3.csproj => OpenMcdf/OpenMcdf.csproj (90%) rename {OpenMcdf3 => OpenMcdf}/RootStorage.cs (99%) rename {OpenMcdf3 => OpenMcdf}/Sector.cs (98%) rename {OpenMcdf3 => OpenMcdf}/SectorDataCache.cs (97%) rename {OpenMcdf3 => OpenMcdf}/SectorType.cs (95%) rename {OpenMcdf3 => OpenMcdf}/Storage.cs (99%) rename {OpenMcdf3 => OpenMcdf}/StreamExtensions.cs (98%) rename {OpenMcdf3 => OpenMcdf}/System/.editorconfig (100%) rename {OpenMcdf3 => OpenMcdf}/System/.globalconfig (100%) rename {OpenMcdf3 => OpenMcdf}/System/CompilerServices.cs (100%) rename {OpenMcdf3 => OpenMcdf}/System/Index.cs (100%) rename {OpenMcdf3 => OpenMcdf}/System/NullableAttributes.cs (100%) rename {OpenMcdf3 => OpenMcdf}/System/Range.cs (100%) rename {OpenMcdf3 => OpenMcdf}/ThrowHelper.cs (99%) rename {OpenMcdf3 => OpenMcdf}/TransactedStream.cs (99%) diff --git a/OpenMcdf3.Benchmarks/FileStreamRead.cs b/OpenMcdf.Benchmarks/FileStreamRead.cs similarity index 95% rename from OpenMcdf3.Benchmarks/FileStreamRead.cs rename to OpenMcdf.Benchmarks/FileStreamRead.cs index ed3b3e1d..3dd43341 100644 --- a/OpenMcdf3.Benchmarks/FileStreamRead.cs +++ b/OpenMcdf.Benchmarks/FileStreamRead.cs @@ -1,8 +1,8 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; -using OpenMcdf3.Benchmarks; +using OpenMcdf.Benchmarks; -namespace OpenMcdf3.Benchmark; +namespace OpenMcdf.Benchmark; [MediumRunJob] [MemoryDiagnoser] diff --git a/OpenMcdf3.Benchmarks/FileStreamTransactedWrite.cs b/OpenMcdf.Benchmarks/FileStreamTransactedWrite.cs similarity index 95% rename from OpenMcdf3.Benchmarks/FileStreamTransactedWrite.cs rename to OpenMcdf.Benchmarks/FileStreamTransactedWrite.cs index 3f7b1a3d..ff5b5ddf 100644 --- a/OpenMcdf3.Benchmarks/FileStreamTransactedWrite.cs +++ b/OpenMcdf.Benchmarks/FileStreamTransactedWrite.cs @@ -1,8 +1,8 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; -using OpenMcdf3.Benchmarks; +using OpenMcdf.Benchmarks; -namespace OpenMcdf3.Benchmark; +namespace OpenMcdf.Benchmark; [MediumRunJob] [MemoryDiagnoser] diff --git a/OpenMcdf3.Benchmarks/FileStreamWrite.cs b/OpenMcdf.Benchmarks/FileStreamWrite.cs similarity index 95% rename from OpenMcdf3.Benchmarks/FileStreamWrite.cs rename to OpenMcdf.Benchmarks/FileStreamWrite.cs index d5b0d8ce..afc7c12b 100644 --- a/OpenMcdf3.Benchmarks/FileStreamWrite.cs +++ b/OpenMcdf.Benchmarks/FileStreamWrite.cs @@ -1,8 +1,8 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; -using OpenMcdf3.Benchmarks; +using OpenMcdf.Benchmarks; -namespace OpenMcdf3.Benchmark; +namespace OpenMcdf.Benchmark; [MediumRunJob] [MemoryDiagnoser] diff --git a/OpenMcdf3.Benchmarks/MemoryStreamRead.cs b/OpenMcdf.Benchmarks/MemoryStreamRead.cs similarity index 95% rename from OpenMcdf3.Benchmarks/MemoryStreamRead.cs rename to OpenMcdf.Benchmarks/MemoryStreamRead.cs index 56a40285..8d48a54a 100644 --- a/OpenMcdf3.Benchmarks/MemoryStreamRead.cs +++ b/OpenMcdf.Benchmarks/MemoryStreamRead.cs @@ -1,8 +1,8 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; -using OpenMcdf3.Benchmarks; +using OpenMcdf.Benchmarks; -namespace OpenMcdf3.Benchmark; +namespace OpenMcdf.Benchmark; [ShortRunJob] [MemoryDiagnoser] diff --git a/OpenMcdf3.Benchmarks/MemoryStreamTransactedWrite.cs b/OpenMcdf.Benchmarks/MemoryStreamTransactedWrite.cs similarity index 94% rename from OpenMcdf3.Benchmarks/MemoryStreamTransactedWrite.cs rename to OpenMcdf.Benchmarks/MemoryStreamTransactedWrite.cs index 73c6ef87..46783fc6 100644 --- a/OpenMcdf3.Benchmarks/MemoryStreamTransactedWrite.cs +++ b/OpenMcdf.Benchmarks/MemoryStreamTransactedWrite.cs @@ -1,8 +1,8 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; -using OpenMcdf3.Benchmarks; +using OpenMcdf.Benchmarks; -namespace OpenMcdf3.Benchmark; +namespace OpenMcdf.Benchmark; [ShortRunJob] [MemoryDiagnoser] diff --git a/OpenMcdf3.Benchmarks/MemoryStreamWrite.cs b/OpenMcdf.Benchmarks/MemoryStreamWrite.cs similarity index 95% rename from OpenMcdf3.Benchmarks/MemoryStreamWrite.cs rename to OpenMcdf.Benchmarks/MemoryStreamWrite.cs index db669337..4935822e 100644 --- a/OpenMcdf3.Benchmarks/MemoryStreamWrite.cs +++ b/OpenMcdf.Benchmarks/MemoryStreamWrite.cs @@ -1,8 +1,8 @@ using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Columns; -using OpenMcdf3.Benchmarks; +using OpenMcdf.Benchmarks; -namespace OpenMcdf3.Benchmark; +namespace OpenMcdf.Benchmark; [ShortRunJob] [MemoryDiagnoser] diff --git a/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj b/OpenMcdf.Benchmarks/OpenMcdf.Benchmarks.csproj similarity index 88% rename from OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj rename to OpenMcdf.Benchmarks/OpenMcdf.Benchmarks.csproj index 219e3058..cdcc1431 100644 --- a/OpenMcdf3.Benchmarks/OpenMcdf3.Benchmarks.csproj +++ b/OpenMcdf.Benchmarks/OpenMcdf.Benchmarks.csproj @@ -13,7 +13,7 @@ - + diff --git a/OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs b/OpenMcdf.Benchmarks/OpenMcdfBenchmarks.cs similarity index 97% rename from OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs rename to OpenMcdf.Benchmarks/OpenMcdfBenchmarks.cs index f4232b5c..e2486bcc 100644 --- a/OpenMcdf3.Benchmarks/OpenMcdfBenchmarks.cs +++ b/OpenMcdf.Benchmarks/OpenMcdfBenchmarks.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3.Benchmarks; +namespace OpenMcdf.Benchmarks; internal static class OpenMcdfBenchmarks { diff --git a/OpenMcdf3.Benchmarks/Program.cs b/OpenMcdf.Benchmarks/Program.cs similarity index 86% rename from OpenMcdf3.Benchmarks/Program.cs rename to OpenMcdf.Benchmarks/Program.cs index 05756ebc..2dd439f8 100644 --- a/OpenMcdf3.Benchmarks/Program.cs +++ b/OpenMcdf.Benchmarks/Program.cs @@ -1,6 +1,6 @@ using BenchmarkDotNet.Running; -namespace OpenMcdf3.Benchmarks; +namespace OpenMcdf.Benchmarks; internal sealed class Program { diff --git a/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs b/OpenMcdf.Benchmarks/StructuredStorageBenchmarks.cs similarity index 98% rename from OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs rename to OpenMcdf.Benchmarks/StructuredStorageBenchmarks.cs index eda2ffd3..c34938ca 100644 --- a/OpenMcdf3.Benchmarks/StructuredStorageBenchmarks.cs +++ b/OpenMcdf.Benchmarks/StructuredStorageBenchmarks.cs @@ -1,6 +1,6 @@ using StructuredStorage; -namespace OpenMcdf3.Benchmarks; +namespace OpenMcdf.Benchmarks; internal static class StructuredStorageBenchmarks { diff --git a/OpenMcdf.Ole/OlePropertiesContainer.cs b/OpenMcdf.Ole/OlePropertiesContainer.cs index 8ff3f80f..e47220f4 100644 --- a/OpenMcdf.Ole/OlePropertiesContainer.cs +++ b/OpenMcdf.Ole/OlePropertiesContainer.cs @@ -1,4 +1,4 @@ -using OpenMcdf3; +using OpenMcdf; namespace OpenMcdf.Ole; diff --git a/OpenMcdf.Ole/OpenMcdf.Ole.csproj b/OpenMcdf.Ole/OpenMcdf.Ole.csproj index 101f0831..12de727d 100644 --- a/OpenMcdf.Ole/OpenMcdf.Ole.csproj +++ b/OpenMcdf.Ole/OpenMcdf.Ole.csproj @@ -5,12 +5,13 @@ enable enable + - + \ No newline at end of file diff --git a/OpenMcdf3.Perf/OpenMcdf3.Perf.csproj b/OpenMcdf.Perf/OpenMcdf.Perf.csproj similarity index 80% rename from OpenMcdf3.Perf/OpenMcdf3.Perf.csproj rename to OpenMcdf.Perf/OpenMcdf.Perf.csproj index 9d82e145..e41e919c 100644 --- a/OpenMcdf3.Perf/OpenMcdf3.Perf.csproj +++ b/OpenMcdf.Perf/OpenMcdf.Perf.csproj @@ -8,7 +8,7 @@ - + diff --git a/OpenMcdf3.Perf/Program.cs b/OpenMcdf.Perf/Program.cs similarity index 98% rename from OpenMcdf3.Perf/Program.cs rename to OpenMcdf.Perf/Program.cs index 751cf9b1..41a62486 100644 --- a/OpenMcdf3.Perf/Program.cs +++ b/OpenMcdf.Perf/Program.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace OpenMcdf3.Perf; +namespace OpenMcdf.Perf; internal sealed class Program { diff --git a/OpenMcdf3.Tests/AssemblyInfo.cs b/OpenMcdf.Tests/AssemblyInfo.cs similarity index 100% rename from OpenMcdf3.Tests/AssemblyInfo.cs rename to OpenMcdf.Tests/AssemblyInfo.cs diff --git a/OpenMcdf3.Tests/BinaryReaderTests.cs b/OpenMcdf.Tests/BinaryReaderTests.cs similarity index 97% rename from OpenMcdf3.Tests/BinaryReaderTests.cs rename to OpenMcdf.Tests/BinaryReaderTests.cs index e5bfa095..9dc8bb34 100644 --- a/OpenMcdf3.Tests/BinaryReaderTests.cs +++ b/OpenMcdf.Tests/BinaryReaderTests.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3.Tests; +namespace OpenMcdf.Tests; [TestClass] public sealed class BinaryReaderTests diff --git a/OpenMcdf3.Tests/BinaryWriterTests.cs b/OpenMcdf.Tests/BinaryWriterTests.cs similarity index 98% rename from OpenMcdf3.Tests/BinaryWriterTests.cs rename to OpenMcdf.Tests/BinaryWriterTests.cs index cd45cff0..2a245715 100644 --- a/OpenMcdf3.Tests/BinaryWriterTests.cs +++ b/OpenMcdf.Tests/BinaryWriterTests.cs @@ -1,6 +1,6 @@ using System.Text; -namespace OpenMcdf3.Tests; +namespace OpenMcdf.Tests; [TestClass] public sealed class BinaryWriterTests diff --git a/OpenMcdf3.Tests/DebugWriter.cs b/OpenMcdf.Tests/DebugWriter.cs similarity index 94% rename from OpenMcdf3.Tests/DebugWriter.cs rename to OpenMcdf.Tests/DebugWriter.cs index c1b66277..24762f30 100644 --- a/OpenMcdf3.Tests/DebugWriter.cs +++ b/OpenMcdf.Tests/DebugWriter.cs @@ -1,7 +1,7 @@ using System.Diagnostics; using System.Text; -namespace OpenMcdf3.Tests; +namespace OpenMcdf.Tests; internal sealed class DebugWriter : TextWriter { diff --git a/OpenMcdf3.Tests/EntryInfoTests.cs b/OpenMcdf.Tests/EntryInfoTests.cs similarity index 93% rename from OpenMcdf3.Tests/EntryInfoTests.cs rename to OpenMcdf.Tests/EntryInfoTests.cs index 60d6a320..b431a7ed 100644 --- a/OpenMcdf3.Tests/EntryInfoTests.cs +++ b/OpenMcdf.Tests/EntryInfoTests.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3.Tests; +namespace OpenMcdf.Tests; [TestClass] public sealed class EntryInfoTests diff --git a/OpenMcdf3.Tests/MultipleStorage.cfs b/OpenMcdf.Tests/MultipleStorage.cfs similarity index 100% rename from OpenMcdf3.Tests/MultipleStorage.cfs rename to OpenMcdf.Tests/MultipleStorage.cfs diff --git a/OpenMcdf3.Tests/MultipleStorage2.cfs b/OpenMcdf.Tests/MultipleStorage2.cfs similarity index 100% rename from OpenMcdf3.Tests/MultipleStorage2.cfs rename to OpenMcdf.Tests/MultipleStorage2.cfs diff --git a/OpenMcdf3.Tests/MultipleStorage3.cfs b/OpenMcdf.Tests/MultipleStorage3.cfs similarity index 100% rename from OpenMcdf3.Tests/MultipleStorage3.cfs rename to OpenMcdf.Tests/MultipleStorage3.cfs diff --git a/OpenMcdf3.Tests/MultipleStorage4.cfs b/OpenMcdf.Tests/MultipleStorage4.cfs similarity index 100% rename from OpenMcdf3.Tests/MultipleStorage4.cfs rename to OpenMcdf.Tests/MultipleStorage4.cfs diff --git a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj b/OpenMcdf.Tests/OpenMcdf.Tests.csproj similarity index 98% rename from OpenMcdf3.Tests/OpenMcdf3.Tests.csproj rename to OpenMcdf.Tests/OpenMcdf.Tests.csproj index 7b0ea833..2736a5b8 100644 --- a/OpenMcdf3.Tests/OpenMcdf3.Tests.csproj +++ b/OpenMcdf.Tests/OpenMcdf.Tests.csproj @@ -22,7 +22,7 @@ - + diff --git a/OpenMcdf3.Tests/StorageTests.cs b/OpenMcdf.Tests/StorageTests.cs similarity index 99% rename from OpenMcdf3.Tests/StorageTests.cs rename to OpenMcdf.Tests/StorageTests.cs index de979cf4..1043e93c 100644 --- a/OpenMcdf3.Tests/StorageTests.cs +++ b/OpenMcdf.Tests/StorageTests.cs @@ -1,4 +1,6 @@ -namespace OpenMcdf3.Tests; +namespace OpenMcdf.Tests; + +using Version = OpenMcdf.Version; [TestClass] public sealed class StorageTests diff --git a/OpenMcdf3.Tests/StreamAssert.cs b/OpenMcdf.Tests/StreamAssert.cs similarity index 96% rename from OpenMcdf3.Tests/StreamAssert.cs rename to OpenMcdf.Tests/StreamAssert.cs index 328cbe86..4d8c8c07 100644 --- a/OpenMcdf3.Tests/StreamAssert.cs +++ b/OpenMcdf.Tests/StreamAssert.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3.Tests; +namespace OpenMcdf.Tests; internal static class StreamAssert { diff --git a/OpenMcdf3.Tests/StreamTests.cs b/OpenMcdf.Tests/StreamTests.cs similarity index 99% rename from OpenMcdf3.Tests/StreamTests.cs rename to OpenMcdf.Tests/StreamTests.cs index d9237544..0401e7c9 100644 --- a/OpenMcdf3.Tests/StreamTests.cs +++ b/OpenMcdf.Tests/StreamTests.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3.Tests; +namespace OpenMcdf.Tests; [TestClass] public sealed class StreamTests diff --git a/OpenMcdf3.Tests/TestStream_v3_0.cfs b/OpenMcdf.Tests/TestStream_v3_0.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v3_0.cfs rename to OpenMcdf.Tests/TestStream_v3_0.cfs diff --git a/OpenMcdf3.Tests/TestStream_v3_4095.cfs b/OpenMcdf.Tests/TestStream_v3_4095.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v3_4095.cfs rename to OpenMcdf.Tests/TestStream_v3_4095.cfs diff --git a/OpenMcdf3.Tests/TestStream_v3_4096.cfs b/OpenMcdf.Tests/TestStream_v3_4096.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v3_4096.cfs rename to OpenMcdf.Tests/TestStream_v3_4096.cfs diff --git a/OpenMcdf3.Tests/TestStream_v3_4097.cfs b/OpenMcdf.Tests/TestStream_v3_4097.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v3_4097.cfs rename to OpenMcdf.Tests/TestStream_v3_4097.cfs diff --git a/OpenMcdf3.Tests/TestStream_v3_511.cfs b/OpenMcdf.Tests/TestStream_v3_511.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v3_511.cfs rename to OpenMcdf.Tests/TestStream_v3_511.cfs diff --git a/OpenMcdf3.Tests/TestStream_v3_512.cfs b/OpenMcdf.Tests/TestStream_v3_512.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v3_512.cfs rename to OpenMcdf.Tests/TestStream_v3_512.cfs diff --git a/OpenMcdf3.Tests/TestStream_v3_513.cfs b/OpenMcdf.Tests/TestStream_v3_513.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v3_513.cfs rename to OpenMcdf.Tests/TestStream_v3_513.cfs diff --git a/OpenMcdf3.Tests/TestStream_v3_63.cfs b/OpenMcdf.Tests/TestStream_v3_63.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v3_63.cfs rename to OpenMcdf.Tests/TestStream_v3_63.cfs diff --git a/OpenMcdf3.Tests/TestStream_v3_64.cfs b/OpenMcdf.Tests/TestStream_v3_64.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v3_64.cfs rename to OpenMcdf.Tests/TestStream_v3_64.cfs diff --git a/OpenMcdf3.Tests/TestStream_v3_65.cfs b/OpenMcdf.Tests/TestStream_v3_65.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v3_65.cfs rename to OpenMcdf.Tests/TestStream_v3_65.cfs diff --git a/OpenMcdf3.Tests/TestStream_v3_65536.cfs b/OpenMcdf.Tests/TestStream_v3_65536.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v3_65536.cfs rename to OpenMcdf.Tests/TestStream_v3_65536.cfs diff --git a/OpenMcdf3.Tests/TestStream_v4_0.cfs b/OpenMcdf.Tests/TestStream_v4_0.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v4_0.cfs rename to OpenMcdf.Tests/TestStream_v4_0.cfs diff --git a/OpenMcdf3.Tests/TestStream_v4_4095.cfs b/OpenMcdf.Tests/TestStream_v4_4095.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v4_4095.cfs rename to OpenMcdf.Tests/TestStream_v4_4095.cfs diff --git a/OpenMcdf3.Tests/TestStream_v4_4096.cfs b/OpenMcdf.Tests/TestStream_v4_4096.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v4_4096.cfs rename to OpenMcdf.Tests/TestStream_v4_4096.cfs diff --git a/OpenMcdf3.Tests/TestStream_v4_4097.cfs b/OpenMcdf.Tests/TestStream_v4_4097.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v4_4097.cfs rename to OpenMcdf.Tests/TestStream_v4_4097.cfs diff --git a/OpenMcdf3.Tests/TestStream_v4_511.cfs b/OpenMcdf.Tests/TestStream_v4_511.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v4_511.cfs rename to OpenMcdf.Tests/TestStream_v4_511.cfs diff --git a/OpenMcdf3.Tests/TestStream_v4_512.cfs b/OpenMcdf.Tests/TestStream_v4_512.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v4_512.cfs rename to OpenMcdf.Tests/TestStream_v4_512.cfs diff --git a/OpenMcdf3.Tests/TestStream_v4_513.cfs b/OpenMcdf.Tests/TestStream_v4_513.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v4_513.cfs rename to OpenMcdf.Tests/TestStream_v4_513.cfs diff --git a/OpenMcdf3.Tests/TestStream_v4_63.cfs b/OpenMcdf.Tests/TestStream_v4_63.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v4_63.cfs rename to OpenMcdf.Tests/TestStream_v4_63.cfs diff --git a/OpenMcdf3.Tests/TestStream_v4_64.cfs b/OpenMcdf.Tests/TestStream_v4_64.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v4_64.cfs rename to OpenMcdf.Tests/TestStream_v4_64.cfs diff --git a/OpenMcdf3.Tests/TestStream_v4_65.cfs b/OpenMcdf.Tests/TestStream_v4_65.cfs similarity index 100% rename from OpenMcdf3.Tests/TestStream_v4_65.cfs rename to OpenMcdf.Tests/TestStream_v4_65.cfs diff --git a/OpenMcdf3.sln b/OpenMcdf.sln similarity index 86% rename from OpenMcdf3.sln rename to OpenMcdf.sln index 38cdc0f8..0e7f8a06 100644 --- a/OpenMcdf3.sln +++ b/OpenMcdf.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.11.35327.3 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3", "OpenMcdf3\OpenMcdf3.csproj", "{B90DDE7E-803A-4890-82F0-09DAD0FF66D8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf", "OpenMcdf\OpenMcdf.csproj", "{B90DDE7E-803A-4890-82F0-09DAD0FF66D8}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3.Tests", "OpenMcdf3.Tests\OpenMcdf3.Tests.csproj", "{96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Tests", "OpenMcdf.Tests\OpenMcdf.Tests.csproj", "{96A9DA9C-E4C2-4531-A2E4-154F1FBF7532}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{34030FA7-0A06-43D1-85DD-ADD39D502C3C}" ProjectSection(SolutionItems) = preProject @@ -14,9 +14,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution exclusion.dic = exclusion.dic EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3.Benchmarks", "OpenMcdf3.Benchmarks\OpenMcdf3.Benchmarks.csproj", "{44C718AD-F7FE-4733-80A8-636E5E7E63F3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Benchmarks", "OpenMcdf.Benchmarks\OpenMcdf.Benchmarks.csproj", "{44C718AD-F7FE-4733-80A8-636E5E7E63F3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf3.Perf", "OpenMcdf3.Perf\OpenMcdf3.Perf.csproj", "{8167F453-A244-4FE2-9B33-A7B80B1B7AB1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Perf", "OpenMcdf.Perf\OpenMcdf.Perf.csproj", "{8167F453-A244-4FE2-9B33-A7B80B1B7AB1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructuredStorage", "StructuredStorage\StructuredStorage.csproj", "{D7861D73-B42C-403E-9B9E-F921BC70F0D3}" EndProject diff --git a/OpenMcdf3/AssemblyInfo.cs b/OpenMcdf/AssemblyInfo.cs similarity index 94% rename from OpenMcdf3/AssemblyInfo.cs rename to OpenMcdf/AssemblyInfo.cs index b7349b84..3905293e 100644 --- a/OpenMcdf3/AssemblyInfo.cs +++ b/OpenMcdf/AssemblyInfo.cs @@ -16,4 +16,4 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM. [assembly: Guid("a96ebb34-8c16-4c7e-b9f7-651ba754b722")] -[assembly: InternalsVisibleTo("OpenMcdf3.Tests")] +[assembly: InternalsVisibleTo("OpenMcdf.Tests")] diff --git a/OpenMcdf3/CfbBinaryReader.cs b/OpenMcdf/CfbBinaryReader.cs similarity index 99% rename from OpenMcdf3/CfbBinaryReader.cs rename to OpenMcdf/CfbBinaryReader.cs index 2d1e00a2..371ca7db 100644 --- a/OpenMcdf3/CfbBinaryReader.cs +++ b/OpenMcdf/CfbBinaryReader.cs @@ -5,7 +5,7 @@ using System.Buffers; #endif -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Reads CFB data types from a stream. diff --git a/OpenMcdf3/CfbBinaryWriter.cs b/OpenMcdf/CfbBinaryWriter.cs similarity index 99% rename from OpenMcdf3/CfbBinaryWriter.cs rename to OpenMcdf/CfbBinaryWriter.cs index 6b4f9ba4..ec18ed11 100644 --- a/OpenMcdf3/CfbBinaryWriter.cs +++ b/OpenMcdf/CfbBinaryWriter.cs @@ -1,6 +1,6 @@ using System.Text; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Writes CFB data types to a stream. diff --git a/OpenMcdf3/CfbStream.cs b/OpenMcdf/CfbStream.cs similarity index 99% rename from OpenMcdf3/CfbStream.cs rename to OpenMcdf/CfbStream.cs index 5d076bfd..a905f177 100644 --- a/OpenMcdf3/CfbStream.cs +++ b/OpenMcdf/CfbStream.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Represents a stream in a compound file. diff --git a/OpenMcdf3/DifatSectorEnumerator.cs b/OpenMcdf/DifatSectorEnumerator.cs similarity index 99% rename from OpenMcdf3/DifatSectorEnumerator.cs rename to OpenMcdf/DifatSectorEnumerator.cs index 4e306901..a2c3589e 100644 --- a/OpenMcdf3/DifatSectorEnumerator.cs +++ b/OpenMcdf/DifatSectorEnumerator.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Diagnostics; -namespace OpenMcdf3; +namespace OpenMcdf; internal class DifatSectorEnumerator : IEnumerator { diff --git a/OpenMcdf3/DirectoryEntries.cs b/OpenMcdf/DirectoryEntries.cs similarity index 99% rename from OpenMcdf3/DirectoryEntries.cs rename to OpenMcdf/DirectoryEntries.cs index e217737b..a6163fec 100644 --- a/OpenMcdf3/DirectoryEntries.cs +++ b/OpenMcdf/DirectoryEntries.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; internal sealed class DirectoryEntries : IDisposable { diff --git a/OpenMcdf3/DirectoryEntry.cs b/OpenMcdf/DirectoryEntry.cs similarity index 99% rename from OpenMcdf3/DirectoryEntry.cs rename to OpenMcdf/DirectoryEntry.cs index 8b9b36f1..6eac4af8 100644 --- a/OpenMcdf3/DirectoryEntry.cs +++ b/OpenMcdf/DirectoryEntry.cs @@ -1,7 +1,7 @@ using System.Runtime.InteropServices; using System.Text; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// The storage type of a . diff --git a/OpenMcdf3/DirectoryEntryComparer.cs b/OpenMcdf/DirectoryEntryComparer.cs similarity index 97% rename from OpenMcdf3/DirectoryEntryComparer.cs rename to OpenMcdf/DirectoryEntryComparer.cs index f4799b15..ae255374 100644 --- a/OpenMcdf3/DirectoryEntryComparer.cs +++ b/OpenMcdf/DirectoryEntryComparer.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace OpenMcdf3; +namespace OpenMcdf; internal class DirectoryEntryComparer : IComparer { diff --git a/OpenMcdf3/DirectoryEntryEnumerator.cs b/OpenMcdf/DirectoryEntryEnumerator.cs similarity index 98% rename from OpenMcdf3/DirectoryEntryEnumerator.cs rename to OpenMcdf/DirectoryEntryEnumerator.cs index 1e73a062..f80f78d1 100644 --- a/OpenMcdf3/DirectoryEntryEnumerator.cs +++ b/OpenMcdf/DirectoryEntryEnumerator.cs @@ -1,6 +1,6 @@ using System.Collections; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Enumerates instances from a . diff --git a/OpenMcdf3/DirectoryTree.cs b/OpenMcdf/DirectoryTree.cs similarity index 99% rename from OpenMcdf3/DirectoryTree.cs rename to OpenMcdf/DirectoryTree.cs index f3cfccec..65b69ca5 100644 --- a/OpenMcdf3/DirectoryTree.cs +++ b/OpenMcdf/DirectoryTree.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace OpenMcdf3; +namespace OpenMcdf; internal class DirectoryTree { diff --git a/OpenMcdf3/DirectoryTreeEnumerator.cs b/OpenMcdf/DirectoryTreeEnumerator.cs similarity index 98% rename from OpenMcdf3/DirectoryTreeEnumerator.cs rename to OpenMcdf/DirectoryTreeEnumerator.cs index 9b1eae0c..6bbd1e50 100644 --- a/OpenMcdf3/DirectoryTreeEnumerator.cs +++ b/OpenMcdf/DirectoryTreeEnumerator.cs @@ -1,6 +1,6 @@ using System.Collections; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Enumerates the children of a . diff --git a/OpenMcdf3/EntryInfo.cs b/OpenMcdf/EntryInfo.cs similarity index 93% rename from OpenMcdf3/EntryInfo.cs rename to OpenMcdf/EntryInfo.cs index c3311498..d88809d4 100644 --- a/OpenMcdf3/EntryInfo.cs +++ b/OpenMcdf/EntryInfo.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; public enum EntryType { diff --git a/OpenMcdf3/Fat.cs b/OpenMcdf/Fat.cs similarity index 99% rename from OpenMcdf3/Fat.cs rename to OpenMcdf/Fat.cs index 9e3a2b0a..30d75e39 100644 --- a/OpenMcdf3/Fat.cs +++ b/OpenMcdf/Fat.cs @@ -2,7 +2,7 @@ using System.Collections; using System.Diagnostics; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Encapsulates getting and setting entries in the FAT. diff --git a/OpenMcdf3/FatChainEntry.cs b/OpenMcdf/FatChainEntry.cs similarity index 93% rename from OpenMcdf3/FatChainEntry.cs rename to OpenMcdf/FatChainEntry.cs index ba7302fc..0f5fdde8 100644 --- a/OpenMcdf3/FatChainEntry.cs +++ b/OpenMcdf/FatChainEntry.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; internal record struct FatChainEntry(uint Index, uint Value) { diff --git a/OpenMcdf3/FatChainEnumerator.cs b/OpenMcdf/FatChainEnumerator.cs similarity index 99% rename from OpenMcdf3/FatChainEnumerator.cs rename to OpenMcdf/FatChainEnumerator.cs index beb09245..300fa0b3 100644 --- a/OpenMcdf3/FatChainEnumerator.cs +++ b/OpenMcdf/FatChainEnumerator.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Diagnostics; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Enumerates the s in a FAT sector chain. diff --git a/OpenMcdf3/FatEntry.cs b/OpenMcdf/FatEntry.cs similarity index 93% rename from OpenMcdf3/FatEntry.cs rename to OpenMcdf/FatEntry.cs index db01412c..915eb85e 100644 --- a/OpenMcdf3/FatEntry.cs +++ b/OpenMcdf/FatEntry.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Encapsulates an entry in the File Allocation Table (FAT). diff --git a/OpenMcdf3/FatEnumerator.cs b/OpenMcdf/FatEnumerator.cs similarity index 98% rename from OpenMcdf3/FatEnumerator.cs rename to OpenMcdf/FatEnumerator.cs index ee59bbb3..4d8eed07 100644 --- a/OpenMcdf3/FatEnumerator.cs +++ b/OpenMcdf/FatEnumerator.cs @@ -1,6 +1,6 @@ using System.Collections; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Enumerates the entries in a FAT. diff --git a/OpenMcdf3/FatSectorEnumerator.cs b/OpenMcdf/FatSectorEnumerator.cs similarity index 99% rename from OpenMcdf3/FatSectorEnumerator.cs rename to OpenMcdf/FatSectorEnumerator.cs index d4464514..1381dc9e 100644 --- a/OpenMcdf3/FatSectorEnumerator.cs +++ b/OpenMcdf/FatSectorEnumerator.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Diagnostics; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Enumerates the FAT sectors of a compound file. diff --git a/OpenMcdf3/FatStream.cs b/OpenMcdf/FatStream.cs similarity index 99% rename from OpenMcdf3/FatStream.cs rename to OpenMcdf/FatStream.cs index bc0dd9a4..d3ec8f59 100644 --- a/OpenMcdf3/FatStream.cs +++ b/OpenMcdf/FatStream.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Provides a for a stream object in a compound file./> diff --git a/OpenMcdf3/Header.cs b/OpenMcdf/Header.cs similarity index 99% rename from OpenMcdf3/Header.cs rename to OpenMcdf/Header.cs index 900a2936..eb277c70 100644 --- a/OpenMcdf3/Header.cs +++ b/OpenMcdf/Header.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; /// /// The structure at the beginning of a compound file. diff --git a/OpenMcdf3/IOContext.cs b/OpenMcdf/IOContext.cs similarity index 99% rename from OpenMcdf3/IOContext.cs rename to OpenMcdf/IOContext.cs index 7966c611..da0cefd4 100644 --- a/OpenMcdf3/IOContext.cs +++ b/OpenMcdf/IOContext.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; enum IOContextFlags { diff --git a/OpenMcdf3/MiniFat.cs b/OpenMcdf/MiniFat.cs similarity index 99% rename from OpenMcdf3/MiniFat.cs rename to OpenMcdf/MiniFat.cs index bbb22d6b..1ed7bc71 100644 --- a/OpenMcdf3/MiniFat.cs +++ b/OpenMcdf/MiniFat.cs @@ -2,7 +2,7 @@ using System.Collections; using System.Diagnostics; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Encapsulates getting and setting entries in the mini FAT. diff --git a/OpenMcdf3/MiniFatChainEnumerator.cs b/OpenMcdf/MiniFatChainEnumerator.cs similarity index 99% rename from OpenMcdf3/MiniFatChainEnumerator.cs rename to OpenMcdf/MiniFatChainEnumerator.cs index e261243f..1b02e56b 100644 --- a/OpenMcdf3/MiniFatChainEnumerator.cs +++ b/OpenMcdf/MiniFatChainEnumerator.cs @@ -1,7 +1,7 @@ using System.Collections; using System.Diagnostics; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Enumerates the s in a Mini FAT sector chain. diff --git a/OpenMcdf3/MiniFatEnumerator.cs b/OpenMcdf/MiniFatEnumerator.cs similarity index 99% rename from OpenMcdf3/MiniFatEnumerator.cs rename to OpenMcdf/MiniFatEnumerator.cs index 4d108a1d..d7cd55a0 100644 --- a/OpenMcdf3/MiniFatEnumerator.cs +++ b/OpenMcdf/MiniFatEnumerator.cs @@ -1,6 +1,6 @@ using System.Collections; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Enumerates the s from the Mini FAT. diff --git a/OpenMcdf3/MiniFatStream.cs b/OpenMcdf/MiniFatStream.cs similarity index 99% rename from OpenMcdf3/MiniFatStream.cs rename to OpenMcdf/MiniFatStream.cs index b3a1beea..6afaf9f5 100644 --- a/OpenMcdf3/MiniFatStream.cs +++ b/OpenMcdf/MiniFatStream.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Provides a for reading a from the mini FAT stream. diff --git a/OpenMcdf3/MiniSector.cs b/OpenMcdf/MiniSector.cs similarity index 98% rename from OpenMcdf3/MiniSector.cs rename to OpenMcdf/MiniSector.cs index daf5b16e..e20f1340 100644 --- a/OpenMcdf3/MiniSector.cs +++ b/OpenMcdf/MiniSector.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Encapsulates information about a mini sector in a compound file. diff --git a/OpenMcdf3/OpenMcdf3.csproj b/OpenMcdf/OpenMcdf.csproj similarity index 90% rename from OpenMcdf3/OpenMcdf3.csproj rename to OpenMcdf/OpenMcdf.csproj index 884c3d20..7a2d5751 100644 --- a/OpenMcdf3/OpenMcdf3.csproj +++ b/OpenMcdf/OpenMcdf.csproj @@ -6,6 +6,8 @@ enable enable true + 3.0.0.0 + ironfede,Jeremy Powell diff --git a/OpenMcdf3/RootStorage.cs b/OpenMcdf/RootStorage.cs similarity index 99% rename from OpenMcdf3/RootStorage.cs rename to OpenMcdf/RootStorage.cs index b50fb065..1120a8a4 100644 --- a/OpenMcdf3/RootStorage.cs +++ b/OpenMcdf/RootStorage.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; public enum Version : ushort { diff --git a/OpenMcdf3/Sector.cs b/OpenMcdf/Sector.cs similarity index 98% rename from OpenMcdf3/Sector.cs rename to OpenMcdf/Sector.cs index 07beb12d..d7d36573 100644 --- a/OpenMcdf3/Sector.cs +++ b/OpenMcdf/Sector.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Encapsulates information about a sector in a compound file. diff --git a/OpenMcdf3/SectorDataCache.cs b/OpenMcdf/SectorDataCache.cs similarity index 97% rename from OpenMcdf3/SectorDataCache.cs rename to OpenMcdf/SectorDataCache.cs index 77085b56..8c04e001 100644 --- a/OpenMcdf3/SectorDataCache.cs +++ b/OpenMcdf/SectorDataCache.cs @@ -1,7 +1,7 @@ using System.Collections.Concurrent; using System.Runtime.InteropServices; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Caches data for adding new sectors to the FAT. diff --git a/OpenMcdf3/SectorType.cs b/OpenMcdf/SectorType.cs similarity index 95% rename from OpenMcdf3/SectorType.cs rename to OpenMcdf/SectorType.cs index 83582262..7735f962 100644 --- a/OpenMcdf3/SectorType.cs +++ b/OpenMcdf/SectorType.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Defines the types of sectors in a compound file. diff --git a/OpenMcdf3/Storage.cs b/OpenMcdf/Storage.cs similarity index 99% rename from OpenMcdf3/Storage.cs rename to OpenMcdf/Storage.cs index fa3f9722..27b01609 100644 --- a/OpenMcdf3/Storage.cs +++ b/OpenMcdf/Storage.cs @@ -1,4 +1,4 @@ -namespace OpenMcdf3; +namespace OpenMcdf; /// /// An object in a compound file that is analogous to a file system directory. diff --git a/OpenMcdf3/StreamExtensions.cs b/OpenMcdf/StreamExtensions.cs similarity index 98% rename from OpenMcdf3/StreamExtensions.cs rename to OpenMcdf/StreamExtensions.cs index 5d904c00..5c6e2cb6 100644 --- a/OpenMcdf3/StreamExtensions.cs +++ b/OpenMcdf/StreamExtensions.cs @@ -2,7 +2,7 @@ using System.Buffers; #endif -namespace OpenMcdf3; +namespace OpenMcdf; internal static class StreamExtensions { diff --git a/OpenMcdf3/System/.editorconfig b/OpenMcdf/System/.editorconfig similarity index 100% rename from OpenMcdf3/System/.editorconfig rename to OpenMcdf/System/.editorconfig diff --git a/OpenMcdf3/System/.globalconfig b/OpenMcdf/System/.globalconfig similarity index 100% rename from OpenMcdf3/System/.globalconfig rename to OpenMcdf/System/.globalconfig diff --git a/OpenMcdf3/System/CompilerServices.cs b/OpenMcdf/System/CompilerServices.cs similarity index 100% rename from OpenMcdf3/System/CompilerServices.cs rename to OpenMcdf/System/CompilerServices.cs diff --git a/OpenMcdf3/System/Index.cs b/OpenMcdf/System/Index.cs similarity index 100% rename from OpenMcdf3/System/Index.cs rename to OpenMcdf/System/Index.cs diff --git a/OpenMcdf3/System/NullableAttributes.cs b/OpenMcdf/System/NullableAttributes.cs similarity index 100% rename from OpenMcdf3/System/NullableAttributes.cs rename to OpenMcdf/System/NullableAttributes.cs diff --git a/OpenMcdf3/System/Range.cs b/OpenMcdf/System/Range.cs similarity index 100% rename from OpenMcdf3/System/Range.cs rename to OpenMcdf/System/Range.cs diff --git a/OpenMcdf3/ThrowHelper.cs b/OpenMcdf/ThrowHelper.cs similarity index 99% rename from OpenMcdf3/ThrowHelper.cs rename to OpenMcdf/ThrowHelper.cs index 83ada20e..50c74084 100644 --- a/OpenMcdf3/ThrowHelper.cs +++ b/OpenMcdf/ThrowHelper.cs @@ -1,6 +1,6 @@ using System.Text; -namespace OpenMcdf3; +namespace OpenMcdf; /// /// Extensions to consistently throw exceptions. diff --git a/OpenMcdf3/TransactedStream.cs b/OpenMcdf/TransactedStream.cs similarity index 99% rename from OpenMcdf3/TransactedStream.cs rename to OpenMcdf/TransactedStream.cs index 42273a2a..dd616bee 100644 --- a/OpenMcdf3/TransactedStream.cs +++ b/OpenMcdf/TransactedStream.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace OpenMcdf3; +namespace OpenMcdf; internal class TransactedStream : Stream { diff --git a/StructuredStorageExplorer/MainForm.cs b/StructuredStorageExplorer/MainForm.cs index 4e64cb00..3ba8ec50 100644 --- a/StructuredStorageExplorer/MainForm.cs +++ b/StructuredStorageExplorer/MainForm.cs @@ -1,7 +1,7 @@ #define OLE_PROPERTY using OpenMcdf.Ole; -using OpenMcdf3; +using OpenMcdf; using StructuredStorageExplorer.Properties; using System.Collections; using System.ComponentModel; diff --git a/StructuredStorageExplorer/StreamDataProvider.cs b/StructuredStorageExplorer/StreamDataProvider.cs index 80aab085..e99c23b7 100644 --- a/StructuredStorageExplorer/StreamDataProvider.cs +++ b/StructuredStorageExplorer/StreamDataProvider.cs @@ -1,5 +1,5 @@ using Be.Windows.Forms; -using OpenMcdf3; +using OpenMcdf; namespace StructuredStorageExplorer; diff --git a/StructuredStorageExplorer/StructuredStorageExplorer.csproj b/StructuredStorageExplorer/StructuredStorageExplorer.csproj index d5002c46..0d089f2d 100644 --- a/StructuredStorageExplorer/StructuredStorageExplorer.csproj +++ b/StructuredStorageExplorer/StructuredStorageExplorer.csproj @@ -13,6 +13,6 @@ - + \ No newline at end of file From 36e34c77b6fee6e7bd2a81e17263ac358466c649 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 16:33:30 +1300 Subject: [PATCH 104/134] Allow switching streams --- OpenMcdf.Tests/RootStorageTests.cs | 37 +++++++++ OpenMcdf/CfbStream.cs | 14 ++-- OpenMcdf/ContextBase.cs | 16 ++++ OpenMcdf/DifatSectorEnumerator.cs | 29 ++++--- OpenMcdf/DirectoryEntries.cs | 28 +++---- OpenMcdf/Fat.cs | 41 +++++----- OpenMcdf/FatChainEnumerator.cs | 30 +++---- OpenMcdf/FatEnumerator.cs | 8 +- OpenMcdf/FatSectorEnumerator.cs | 35 ++++---- OpenMcdf/FatStream.cs | 48 +++++------ OpenMcdf/MiniFat.cs | 37 +++++---- OpenMcdf/MiniFatChainEnumerator.cs | 25 +++--- OpenMcdf/MiniFatEnumerator.cs | 15 ++-- OpenMcdf/MiniFatStream.cs | 42 +++++----- OpenMcdf/{IOContext.cs => RootContext.cs} | 17 ++-- OpenMcdf/RootContextSite.cs | 16 ++++ OpenMcdf/RootStorage.cs | 98 ++++++++++++++--------- OpenMcdf/Storage.cs | 43 +++++----- OpenMcdf/TransactedStream.cs | 4 +- 19 files changed, 338 insertions(+), 245 deletions(-) create mode 100644 OpenMcdf.Tests/RootStorageTests.cs create mode 100644 OpenMcdf/ContextBase.cs rename OpenMcdf/{IOContext.cs => RootContext.cs} (90%) create mode 100644 OpenMcdf/RootContextSite.cs diff --git a/OpenMcdf.Tests/RootStorageTests.cs b/OpenMcdf.Tests/RootStorageTests.cs new file mode 100644 index 00000000..9a328cd0 --- /dev/null +++ b/OpenMcdf.Tests/RootStorageTests.cs @@ -0,0 +1,37 @@ +namespace OpenMcdf.Tests; + +[TestClass] +public sealed class RootStorageTests +{ + [TestMethod] + [DataRow(Version.V3, 0)] + [DataRow(Version.V3, 1)] + [DataRow(Version.V3, 2)] + [DataRow(Version.V3, 4)] // Required 2 sectors including root + [DataRow(Version.V4, 0)] + [DataRow(Version.V4, 1)] + [DataRow(Version.V4, 2)] + [DataRow(Version.V4, 32)] // Required 2 sectors including root + public void SwitchStream(Version version, int subStorageCount) + { + using MemoryStream memoryStream = new(); + using MemoryStream switchedMemoryStream = new(); + using (var rootStorage = RootStorage.Create(memoryStream, version, StorageModeFlags.LeaveOpen)) + { + for (int i = 0; i < subStorageCount; i++) + rootStorage.CreateStorage($"Test{i}"); + + rootStorage.SwitchTo(switchedMemoryStream); + } + + memoryStream.Position = 0; + using (var rootStorage = RootStorage.Open(switchedMemoryStream, StorageModeFlags.LeaveOpen)) + { + IEnumerable entries = rootStorage.EnumerateEntries(); + Assert.AreEqual(subStorageCount, entries.Count()); + + for (int i = 0; i < subStorageCount; i++) + rootStorage.OpenStorage($"Test{i}"); + } + } +} diff --git a/OpenMcdf/CfbStream.cs b/OpenMcdf/CfbStream.cs index a905f177..e4159fc8 100644 --- a/OpenMcdf/CfbStream.cs +++ b/OpenMcdf/CfbStream.cs @@ -5,17 +5,17 @@ /// public sealed class CfbStream : Stream { - private readonly IOContext ioContext; + private readonly RootContextSite rootContextSite; private readonly DirectoryEntry directoryEntry; private Stream stream; - internal CfbStream(IOContext ioContext, DirectoryEntry directoryEntry) + internal CfbStream(RootContextSite rootContextSite, DirectoryEntry directoryEntry) { - this.ioContext = ioContext; + this.rootContextSite = rootContextSite; this.directoryEntry = directoryEntry; stream = directoryEntry.StreamLength < Header.MiniStreamCutoffSize - ? new MiniFatStream(ioContext, directoryEntry) - : new FatStream(ioContext, directoryEntry); + ? new MiniFatStream(rootContextSite, directoryEntry) + : new FatStream(rootContextSite, directoryEntry); } protected override void Dispose(bool disposing) @@ -53,7 +53,7 @@ public override void SetLength(long value) miniStream.Position = 0; DirectoryEntry newDirectoryEntry = directoryEntry.Clone(); - FatStream fatStream = new(ioContext, newDirectoryEntry); + FatStream fatStream = new(rootContextSite, newDirectoryEntry); fatStream.SetLength(value); // Reserve enough space up front miniStream.CopyTo(fatStream); fatStream.Position = position; @@ -68,7 +68,7 @@ public override void SetLength(long value) fatStream.Position = 0; DirectoryEntry newDirectoryEntry = directoryEntry.Clone(); - MiniFatStream miniFatStream = new(ioContext, newDirectoryEntry); + MiniFatStream miniFatStream = new(rootContextSite, newDirectoryEntry); fatStream.SetLength(value); // Truncate the stream fatStream.CopyTo(miniFatStream); miniFatStream.Position = position; diff --git a/OpenMcdf/ContextBase.cs b/OpenMcdf/ContextBase.cs new file mode 100644 index 00000000..ccf84c5d --- /dev/null +++ b/OpenMcdf/ContextBase.cs @@ -0,0 +1,16 @@ +namespace OpenMcdf; + +/// +/// Supports switching the object. +/// +public abstract class ContextBase +{ + internal RootContextSite ContextSite { get; } + + internal RootContext Context => ContextSite.Context; + + internal ContextBase(RootContextSite site) + { + ContextSite = site; + } +} diff --git a/OpenMcdf/DifatSectorEnumerator.cs b/OpenMcdf/DifatSectorEnumerator.cs index a2c3589e..c48baf4d 100644 --- a/OpenMcdf/DifatSectorEnumerator.cs +++ b/OpenMcdf/DifatSectorEnumerator.cs @@ -3,19 +3,18 @@ namespace OpenMcdf; -internal class DifatSectorEnumerator : IEnumerator +internal class DifatSectorEnumerator : ContextBase, IEnumerator { - private readonly IOContext ioContext; public readonly uint DifatElementsPerSector; bool start = true; uint index = uint.MaxValue; Sector current = Sector.EndOfChain; private uint difatSectorId = SectorType.EndOfChain; - public DifatSectorEnumerator(IOContext ioContext) + public DifatSectorEnumerator(RootContextSite rootContextSite) + : base(rootContextSite) { - this.ioContext = ioContext; - DifatElementsPerSector = (uint)((ioContext.SectorSize / sizeof(uint)) - 1); + DifatElementsPerSector = (uint)((Context.SectorSize / sizeof(uint)) - 1); } /// @@ -45,7 +44,7 @@ public bool MoveNext() { start = false; index = uint.MaxValue; - difatSectorId = ioContext.Header.FirstDifatSectorId; + difatSectorId = Context.Header.FirstDifatSectorId; } uint nextIndex = index + 1; @@ -57,16 +56,16 @@ public bool MoveNext() return false; } - current = new(difatSectorId, ioContext.SectorSize); + current = new(difatSectorId, Context.SectorSize); index = nextIndex; - ioContext.Reader.Position = current.EndPosition - sizeof(uint); - difatSectorId = ioContext.Reader.ReadUInt32(); + Context.Reader.Position = current.EndPosition - sizeof(uint); + difatSectorId = Context.Reader.ReadUInt32(); return true; } public bool MoveTo(uint index) { - if (index >= ioContext.Header.DifatSectorCount) + if (index >= Context.Header.DifatSectorCount) return false; if (start && !MoveNext()) @@ -95,10 +94,10 @@ public void Reset() public void Add() { - Sector newDifatSector = new(ioContext.SectorCount, ioContext.SectorSize); + Sector newDifatSector = new(Context.SectorCount, Context.SectorSize); - Header header = ioContext.Header; - CfbBinaryWriter writer = ioContext.Writer; + Header header = Context.Header; + CfbBinaryWriter writer = Context.Writer; if (header.FirstDifatSectorId == SectorType.EndOfChain) { header.FirstDifatSectorId = newDifatSector.Id; @@ -118,10 +117,10 @@ public void Add() writer.Position = newDifatSector.EndPosition - sizeof(uint); writer.Write(SectorType.EndOfChain); - ioContext.ExtendStreamLength(newDifatSector.EndPosition); + Context.ExtendStreamLength(newDifatSector.EndPosition); header.DifatSectorCount++; - ioContext.Fat[newDifatSector.Id] = SectorType.Difat; + Context.Fat[newDifatSector.Id] = SectorType.Difat; start = false; index = header.DifatSectorCount - 1; diff --git a/OpenMcdf/DirectoryEntries.cs b/OpenMcdf/DirectoryEntries.cs index a6163fec..69ef5927 100644 --- a/OpenMcdf/DirectoryEntries.cs +++ b/OpenMcdf/DirectoryEntries.cs @@ -1,18 +1,17 @@ namespace OpenMcdf; -internal sealed class DirectoryEntries : IDisposable +internal sealed class DirectoryEntries : ContextBase, IDisposable { - private readonly IOContext ioContext; private readonly FatChainEnumerator fatChainEnumerator; private readonly DirectoryEntryEnumerator directoryEntryEnumerator; private readonly int entriesPerSector; - public DirectoryEntries(IOContext ioContext) + public DirectoryEntries(RootContextSite rootContextSite) + : base(rootContextSite) { - this.ioContext = ioContext; - fatChainEnumerator = new FatChainEnumerator(ioContext, ioContext.Header.FirstDirectorySectorId); + fatChainEnumerator = new FatChainEnumerator(Context.Fat, Context.Header.FirstDirectorySectorId); directoryEntryEnumerator = new DirectoryEntryEnumerator(this); - entriesPerSector = ioContext.SectorSize / DirectoryEntry.Length; + entriesPerSector = Context.SectorSize / DirectoryEntry.Length; } public void Dispose() @@ -48,8 +47,8 @@ public bool TryGetDictionaryEntry(uint streamId, out DirectoryEntry? entry) return false; } - ioContext.Reader.Position = fatChainEnumerator.CurrentSector.Position + (entryIndex * DirectoryEntry.Length); - entry = ioContext.Reader.ReadDirectoryEntry(ioContext.Version, streamId); + Context.Reader.Position = fatChainEnumerator.CurrentSector.Position + (entryIndex * DirectoryEntry.Length); + entry = Context.Reader.ReadDirectoryEntry(Context.Version, streamId); return true; } @@ -59,15 +58,15 @@ public DirectoryEntry CreateOrRecycleDirectoryEntry() if (entry is not null) return entry; - CfbBinaryWriter writer = ioContext.Writer; + CfbBinaryWriter writer = Context.Writer; uint id = fatChainEnumerator.Extend(); - Header header = ioContext.Header; + Header header = Context.Header; if (header.FirstDirectorySectorId == SectorType.EndOfChain) header.FirstDirectorySectorId = id; - if (ioContext.Version == Version.V4) + if (Context.Version == Version.V4) header.DirectorySectorCount++; - Sector sector = new(id, ioContext.SectorSize); + Sector sector = new(id, Context.SectorSize); writer.Position = sector.Position; for (int i = 0; i < entriesPerSector; i++) writer.Write(DirectoryEntry.Unallocated); @@ -97,8 +96,9 @@ public void Write(DirectoryEntry entry) if (!fatChainEnumerator.MoveTo(chainIndex)) throw new KeyNotFoundException($"Directory entry {entry.Id} was not found."); - CfbBinaryWriter writer = ioContext.Writer; - writer.Position = fatChainEnumerator.CurrentSector.Position + (entryIndex * DirectoryEntry.Length); + CfbBinaryWriter writer = Context.Writer; + Sector sector = new(fatChainEnumerator.Current.Value, Context.SectorSize); + writer.Position = sector.Position + (entryIndex * DirectoryEntry.Length); writer.Write(entry); } } diff --git a/OpenMcdf/Fat.cs b/OpenMcdf/Fat.cs index 30d75e39..487bd74b 100644 --- a/OpenMcdf/Fat.cs +++ b/OpenMcdf/Fat.cs @@ -7,21 +7,20 @@ namespace OpenMcdf; /// /// Encapsulates getting and setting entries in the FAT. /// -internal sealed class Fat : IEnumerable, IDisposable +internal sealed class Fat : ContextBase, IEnumerable, IDisposable { - private readonly IOContext ioContext; private readonly FatSectorEnumerator fatSectorEnumerator; internal readonly int FatElementsPerSector; private readonly byte[] cachedSectorBuffer; Sector cachedSector = Sector.EndOfChain; private bool isDirty; - public Fat(IOContext ioContext) + public Fat(RootContextSite rootContextSite) + : base(rootContextSite) { - this.ioContext = ioContext; - FatElementsPerSector = ioContext.SectorSize / sizeof(uint); - fatSectorEnumerator = new(ioContext); - cachedSectorBuffer = new byte[ioContext.SectorSize]; + FatElementsPerSector = Context.SectorSize / sizeof(uint); + fatSectorEnumerator = new(rootContextSite); + cachedSectorBuffer = new byte[Context.SectorSize]; } public void Dispose() @@ -61,7 +60,7 @@ void CacheCurrentSector() Flush(); - CfbBinaryReader reader = ioContext.Reader; + CfbBinaryReader reader = Context.Reader; reader.Position = current.Position; reader.Read(cachedSectorBuffer); cachedSector = current; @@ -71,7 +70,7 @@ public void Flush() { if (isDirty) { - CfbBinaryWriter writer = ioContext.Writer; + CfbBinaryWriter writer = Context.Writer; writer.Position = cachedSector.Position; writer.Write(cachedSectorBuffer); isDirty = false; @@ -145,21 +144,21 @@ public uint Add(FatEnumerator fatEnumerator, uint startIndex) } FatEntry entry = fatEnumerator.Current; - Sector sector = new(entry.Index, ioContext.SectorSize); - ioContext.ExtendStreamLength(sector.EndPosition); + Sector sector = new(entry.Index, Context.SectorSize); + Context.ExtendStreamLength(sector.EndPosition); this[entry.Index] = SectorType.EndOfChain; return entry.Index; } - public IEnumerator GetEnumerator() => new FatEnumerator(ioContext); + public IEnumerator GetEnumerator() => new FatEnumerator(Context.Fat); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); internal void WriteTrace(TextWriter writer) { - byte[] data = new byte[ioContext.SectorSize]; + byte[] data = new byte[Context.SectorSize]; - Stream baseStream = ioContext.Reader.BaseStream; + Stream baseStream = Context.Reader.BaseStream; writer.WriteLine("Start of FAT ================="); @@ -168,7 +167,7 @@ internal void WriteTrace(TextWriter writer) foreach (FatEntry entry in this) { - Sector sector = new(entry.Index, ioContext.SectorSize); + Sector sector = new(entry.Index, Context.SectorSize); if (entry.IsFree) { freeCount++; @@ -196,8 +195,8 @@ internal void Validate() long difatSectorCount = 0; foreach (FatEntry entry in this) { - Sector sector = new(entry.Index, ioContext.SectorSize); - if (entry.Value <= SectorType.Maximum && sector.EndPosition > ioContext.Length) + Sector sector = new(entry.Index, Context.SectorSize); + if (entry.Value <= SectorType.Maximum && sector.EndPosition > Context.Length) throw new FormatException($"FAT entry {entry} is beyond the end of the stream."); if (entry.Value == SectorType.Fat) fatSectorCount++; @@ -205,10 +204,10 @@ internal void Validate() difatSectorCount++; } - if (ioContext.Header.FatSectorCount != fatSectorCount) - throw new FormatException($"FAT sector count mismatch. Expected: {ioContext.Header.FatSectorCount} Actual: {fatSectorCount}."); - if (ioContext.Header.DifatSectorCount != difatSectorCount) - throw new FormatException($"DIFAT sector count mismatch: Expected: {ioContext.Header.DifatSectorCount} Actual: {difatSectorCount}."); + if (Context.Header.FatSectorCount != fatSectorCount) + throw new FormatException($"FAT sector count mismatch. Expected: {Context.Header.FatSectorCount} Actual: {fatSectorCount}."); + if (Context.Header.DifatSectorCount != difatSectorCount) + throw new FormatException($"DIFAT sector count mismatch: Expected: {Context.Header.DifatSectorCount} Actual: {difatSectorCount}."); } internal long GetFreeSectorCount() => this.Count(entry => entry.IsFree); diff --git a/OpenMcdf/FatChainEnumerator.cs b/OpenMcdf/FatChainEnumerator.cs index 300fa0b3..a0725859 100644 --- a/OpenMcdf/FatChainEnumerator.cs +++ b/OpenMcdf/FatChainEnumerator.cs @@ -8,7 +8,7 @@ namespace OpenMcdf; /// internal sealed class FatChainEnumerator : IEnumerator { - private readonly IOContext ioContext; + private readonly Fat fat; private readonly FatEnumerator fatEnumerator; private uint startId; private bool start = true; @@ -16,11 +16,11 @@ internal sealed class FatChainEnumerator : IEnumerator private FatChainEntry current = FatChainEntry.Invalid; private long length = -1; - public FatChainEnumerator(IOContext ioContext, uint startSectorId) + public FatChainEnumerator(Fat fat, uint startSectorId) { - this.ioContext = ioContext; + this.fat = fat; startId = startSectorId; - fatEnumerator = new(ioContext); + fatEnumerator = new(fat); } /// @@ -29,7 +29,7 @@ public void Dispose() fatEnumerator.Dispose(); } - public Sector CurrentSector => new(Current.Value, ioContext.SectorSize); + public Sector CurrentSector => new(Current.Value, fat.Context.SectorSize); /// public FatChainEntry Current @@ -72,7 +72,7 @@ public bool MoveNext() return false; } - uint value = ioContext.Fat[current.Value]; + uint value = fat[current.Value]; if (value is SectorType.EndOfChain) { index = uint.MaxValue; @@ -147,7 +147,7 @@ public uint Extend(uint requiredChainLength) if (startId == StreamId.NoStream) { - startId = ioContext.Fat.Add(fatEnumerator, 0); + startId = fat.Add(fatEnumerator, 0); chainLength = 1; } @@ -159,8 +159,8 @@ public uint Extend(uint requiredChainLength) Debug.Assert(ok); while (chainLength < requiredChainLength) { - uint id = ioContext.Fat.Add(fatEnumerator, lastId); - ioContext.Fat[lastId] = id; + uint id = fat.Add(fatEnumerator, lastId); + fat[lastId] = id; lastId = id; chainLength++; } @@ -173,7 +173,7 @@ public uint ExtendFrom(uint hintId) { if (startId == SectorType.EndOfChain) { - startId = ioContext.Fat.Add(fatEnumerator, hintId); + startId = fat.Add(fatEnumerator, hintId); return startId; } @@ -183,8 +183,8 @@ public uint ExtendFrom(uint hintId) lastId = current.Value; } - uint id = ioContext.Fat.Add(fatEnumerator, lastId); - ioContext.Fat[lastId] = id; + uint id = fat.Add(fatEnumerator, lastId); + fat[lastId] = id; return id; } @@ -202,15 +202,15 @@ public uint Shrink(uint requiredChainLength) if (lastId is not SectorType.EndOfChain and not SectorType.Free) { if (index == requiredChainLength) - ioContext.Fat[lastId] = SectorType.EndOfChain; + fat[lastId] = SectorType.EndOfChain; else if (index > requiredChainLength) - ioContext.Fat[lastId] = SectorType.Free; + fat[lastId] = SectorType.Free; } lastId = current.Value; } - ioContext.Fat[lastId] = SectorType.Free; + fat[lastId] = SectorType.Free; if (requiredChainLength == 0) { diff --git a/OpenMcdf/FatEnumerator.cs b/OpenMcdf/FatEnumerator.cs index 4d8eed07..afeb1cd1 100644 --- a/OpenMcdf/FatEnumerator.cs +++ b/OpenMcdf/FatEnumerator.cs @@ -7,14 +7,14 @@ namespace OpenMcdf; /// internal class FatEnumerator : IEnumerator { - readonly IOContext ioContext; + readonly Fat fat; bool start = true; uint index = uint.MaxValue; uint value = uint.MaxValue; - public FatEnumerator(IOContext ioContext) + public FatEnumerator(Fat fat) { - this.ioContext = ioContext; + this.fat = fat; } /// @@ -60,7 +60,7 @@ public bool MoveTo(uint index) if (this.index == index) return true; - if (ioContext.Fat.TryGetValue(index, out value)) + if (fat.TryGetValue(index, out value)) { this.index = index; return true; diff --git a/OpenMcdf/FatSectorEnumerator.cs b/OpenMcdf/FatSectorEnumerator.cs index 1381dc9e..9ab32d26 100644 --- a/OpenMcdf/FatSectorEnumerator.cs +++ b/OpenMcdf/FatSectorEnumerator.cs @@ -6,24 +6,23 @@ namespace OpenMcdf; /// /// Enumerates the FAT sectors of a compound file. /// -internal sealed class FatSectorEnumerator : IEnumerator +internal sealed class FatSectorEnumerator : ContextBase, IEnumerator { - private readonly IOContext ioContext; private readonly DifatSectorEnumerator difatSectorEnumerator; private bool start = true; private uint index = uint.MaxValue; private Sector current = Sector.EndOfChain; - public FatSectorEnumerator(IOContext ioContext) + public FatSectorEnumerator(RootContextSite rootContextSite) + : base(rootContextSite) { - this.ioContext = ioContext; - difatSectorEnumerator = new(ioContext); + difatSectorEnumerator = new(rootContextSite); } /// public void Dispose() { - // IOContext is owned by a parent + // Context is owned by a parent difatSectorEnumerator.Dispose(); } @@ -68,7 +67,7 @@ public bool MoveTo(uint index) if (index == this.index) return true; - if (index >= ioContext.Header.FatSectorCount) + if (index >= Context.Header.FatSectorCount) { this.index = uint.MaxValue; current = Sector.EndOfChain; @@ -78,8 +77,8 @@ public bool MoveTo(uint index) if (index < Header.DifatArrayLength) { this.index = index; - uint difatId = ioContext.Header.Difat[this.index]; - current = new(difatId, ioContext.SectorSize); + uint difatId = Context.Header.Difat[this.index]; + current = new(difatId, Context.SectorSize); return true; } @@ -97,10 +96,10 @@ public bool MoveTo(uint index) } Sector difatSector = difatSectorEnumerator.Current; - ioContext.Reader.Position = difatSector.Position + (difatElementIndex * sizeof(uint)); - uint id = ioContext.Reader.ReadUInt32(); + Context.Reader.Position = difatSector.Position + (difatElementIndex * sizeof(uint)); + uint id = Context.Reader.ReadUInt32(); this.index = index; - current = new Sector(id, ioContext.SectorSize); + current = new Sector(id, Context.SectorSize); return true; } @@ -135,14 +134,14 @@ public void Reset() public uint Add() { // No FAT sectors are free, so add a new one - Header header = ioContext.Header; - uint nextIndex = ioContext.Header.FatSectorCount; - Sector newFatSector = new(ioContext.SectorCount, ioContext.SectorSize); + Header header = Context.Header; + uint nextIndex = Context.Header.FatSectorCount; + Sector newFatSector = new(Context.SectorCount, Context.SectorSize); - CfbBinaryWriter writer = ioContext.Writer; + CfbBinaryWriter writer = Context.Writer; writer.Position = newFatSector.Position; writer.Write(SectorDataCache.GetFatEntryData(newFatSector.Length)); - ioContext.ExtendStreamLength(newFatSector.EndPosition); + Context.ExtendStreamLength(newFatSector.EndPosition); header.FatSectorCount++; @@ -164,7 +163,7 @@ public uint Add() writer.Write(newFatSector.Id); } - ioContext.Fat[newFatSector.Id] = SectorType.Fat; + Context.Fat[newFatSector.Id] = SectorType.Fat; return newFatSector.Id; } } diff --git a/OpenMcdf/FatStream.cs b/OpenMcdf/FatStream.cs index d3ec8f59..bede4309 100644 --- a/OpenMcdf/FatStream.cs +++ b/OpenMcdf/FatStream.cs @@ -5,23 +5,25 @@ /// internal class FatStream : Stream { - readonly IOContext ioContext; + readonly RootContextSite rootContextSite; readonly FatChainEnumerator chain; long position; bool isDirty; bool disposed; - internal FatStream(IOContext ioContext, DirectoryEntry directoryEntry) + private RootContext Context => rootContextSite.Context; + + internal FatStream(RootContextSite rootContextSite, DirectoryEntry directoryEntry) { - this.ioContext = ioContext; + this.rootContextSite = rootContextSite; DirectoryEntry = directoryEntry; - chain = new(ioContext, directoryEntry.StartSectorId); + chain = new(Context.Fat, directoryEntry.StartSectorId); } /// internal DirectoryEntry DirectoryEntry { get; private set; } - internal long ChainCapacity => ((Length + ioContext.SectorSize - 1) / ioContext.SectorSize) * ioContext.SectorSize; + internal long ChainCapacity => ((Length + Context.SectorSize - 1) / Context.SectorSize) * Context.SectorSize; /// public override bool CanRead => true; @@ -30,7 +32,7 @@ internal FatStream(IOContext ioContext, DirectoryEntry directoryEntry) public override bool CanSeek => true; /// - public override bool CanWrite => ioContext.CanWrite; + public override bool CanWrite => Context.CanWrite; /// public override long Length => DirectoryEntry.StreamLength; @@ -63,12 +65,12 @@ public override void Flush() if (isDirty) { - ioContext.DirectoryEntries.Write(DirectoryEntry); + Context.DirectoryEntries.Write(DirectoryEntry); isDirty = false; } if (CanWrite) - ioContext.Writer!.Flush(); + Context.Writer!.Flush(); } /// @@ -85,7 +87,7 @@ public override int Read(byte[] buffer, int offset, int count) if (maxCount == 0) return 0; - uint chainIndex = (uint)Math.DivRem(position, ioContext.SectorSize, out long sectorOffset); + uint chainIndex = (uint)Math.DivRem(position, Context.SectorSize, out long sectorOffset); if (!chain.MoveTo(chainIndex)) return 0; @@ -93,12 +95,12 @@ public override int Read(byte[] buffer, int offset, int count) int readCount = 0; do { - Sector sector = chain.CurrentSector; + Sector sector = new(chain.Current.Value, Context.SectorSize); int remaining = realCount - readCount; long readLength = Math.Min(remaining, sector.Length - sectorOffset); - ioContext.Reader.Position = sector.Position + sectorOffset; + Context.Reader.Position = sector.Position + sectorOffset; int localOffset = offset + readCount; - int read = ioContext.Reader.Read(buffer, localOffset, (int)readLength); + int read = Context.Reader.Read(buffer, localOffset, (int)readLength); if (read == 0) return readCount; position += read; @@ -148,10 +150,10 @@ public override void SetLength(long value) { this.ThrowIfNotWritable(); - uint requiredChainLength = (uint)((value + ioContext.SectorSize - 1) / ioContext.SectorSize); + uint requiredChainLength = (uint)((value + Context.SectorSize - 1) / Context.SectorSize); if (value > ChainCapacity) DirectoryEntry.StartSectorId = chain.Extend(requiredChainLength); - else if (value <= ChainCapacity - ioContext.SectorSize) + else if (value <= ChainCapacity - Context.SectorSize) DirectoryEntry.StartSectorId = chain.Shrink(requiredChainLength); DirectoryEntry.StreamLength = value; @@ -169,9 +171,9 @@ public override void Write(byte[] buffer, int offset, int count) if (count == 0) return; - uint chainIndex = (uint)Math.DivRem(position, ioContext.SectorSize, out long sectorOffset); + uint chainIndex = (uint)Math.DivRem(position, Context.SectorSize, out long sectorOffset); - CfbBinaryWriter writer = ioContext.Writer; + CfbBinaryWriter writer = Context.Writer; int writeCount = 0; uint lastIndex = 0; for (; ; ) @@ -185,7 +187,7 @@ public override void Write(byte[] buffer, int offset, int count) int localOffset = offset + writeCount; long writeLength = Math.Min(remaining, sector.Length - sectorOffset); writer.Write(buffer, localOffset, (int)writeLength); - ioContext.ExtendStreamLength(sector.EndPosition); + Context.ExtendStreamLength(sector.EndPosition); position += writeLength; writeCount += (int)writeLength; if (position > Length) @@ -218,7 +220,7 @@ public override int Read(Span buffer) if (maxCount == 0) return 0; - uint chainIndex = (uint)Math.DivRem(position, ioContext.SectorSize, out long sectorOffset); + uint chainIndex = (uint)Math.DivRem(position, Context.SectorSize, out long sectorOffset); if (!chain.MoveTo(chainIndex)) return 0; @@ -229,10 +231,10 @@ public override int Read(Span buffer) Sector sector = chain.CurrentSector; int remaining = realCount - readCount; long readLength = Math.Min(remaining, sector.Length - sectorOffset); - ioContext.Reader.Position = sector.Position + sectorOffset; + Context.Reader.Position = sector.Position + sectorOffset; int localOffset = readCount; Span slice = buffer.Slice(localOffset, (int)readLength); - int read = ioContext.Reader.Read(slice); + int read = Context.Reader.Read(slice); if (read == 0) return readCount; position += read; @@ -255,9 +257,9 @@ public override void Write(ReadOnlySpan buffer) if (buffer.Length == 0) return; - uint chainIndex = (uint)Math.DivRem(position, ioContext.SectorSize, out long sectorOffset); + uint chainIndex = (uint)Math.DivRem(position, Context.SectorSize, out long sectorOffset); - CfbBinaryWriter writer = ioContext.Writer; + CfbBinaryWriter writer = Context.Writer; int writeCount = 0; uint lastIndex = 0; for (; ; ) @@ -272,7 +274,7 @@ public override void Write(ReadOnlySpan buffer) long writeLength = Math.Min(remaining, sector.Length - sectorOffset); ReadOnlySpan slice = buffer.Slice(localOffset, (int)writeLength); writer.Write(slice); - ioContext.ExtendStreamLength(sector.EndPosition); + Context.ExtendStreamLength(sector.EndPosition); position += writeLength; writeCount += (int)writeLength; if (position > Length) diff --git a/OpenMcdf/MiniFat.cs b/OpenMcdf/MiniFat.cs index 1ed7bc71..8faa235c 100644 --- a/OpenMcdf/MiniFat.cs +++ b/OpenMcdf/MiniFat.cs @@ -7,20 +7,19 @@ namespace OpenMcdf; /// /// Encapsulates getting and setting entries in the mini FAT. /// -internal sealed class MiniFat : IEnumerable, IDisposable +internal sealed class MiniFat : ContextBase, IEnumerable, IDisposable { - private readonly IOContext ioContext; private readonly FatChainEnumerator fatChainEnumerator; private readonly int ElementsPerSector; private readonly byte[] sector; private bool isDirty; - public MiniFat(IOContext ioContext) + public MiniFat(RootContextSite rootContextSite) + : base(rootContextSite) { - this.ioContext = ioContext; - ElementsPerSector = ioContext.SectorSize / sizeof(uint); - fatChainEnumerator = new(ioContext, ioContext.Header.FirstMiniFatSectorId); - sector = new byte[ioContext.SectorSize]; + ElementsPerSector = Context.SectorSize / sizeof(uint); + fatChainEnumerator = new(Context.Fat, Context.Header.FirstMiniFatSectorId); + sector = new byte[Context.SectorSize]; } public void Dispose() @@ -34,14 +33,14 @@ public void Flush() { if (isDirty) { - CfbBinaryWriter writer = ioContext.Writer; + CfbBinaryWriter writer = Context.Writer; writer.Position = fatChainEnumerator.CurrentSector.Position; writer.Write(sector); isDirty = false; } } - public IEnumerator GetEnumerator() => new MiniFatEnumerator(ioContext); + public IEnumerator GetEnumerator() => new MiniFatEnumerator(ContextSite); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -72,7 +71,7 @@ bool TryMoveToSectorForKey(uint key, out long elementIndex) if (!ok) return false; - CfbBinaryReader reader = ioContext.Reader; + CfbBinaryReader reader = Context.Reader; reader.Position = fatChainEnumerator.CurrentSector.Position; reader.Read(sector); return true; @@ -114,12 +113,12 @@ public uint Add(MiniFatEnumerator miniFatEnumerator, uint startIndex) if (!movedToFreeEntry) { uint newSectorIndex = fatChainEnumerator.Extend(); - Sector sector = new(newSectorIndex, ioContext.SectorSize); - CfbBinaryWriter writer = ioContext.Writer; + Sector sector = new(newSectorIndex, Context.SectorSize); + CfbBinaryWriter writer = Context.Writer; writer.Position = sector.Position; writer.Write(SectorDataCache.GetFatEntryData(sector.Length)); - Header header = ioContext.Header; + Header header = Context.Header; if (header.FirstMiniFatSectorId == SectorType.EndOfChain) header.FirstMiniFatSectorId = newSectorIndex; header.MiniFatSectorCount++; @@ -134,16 +133,16 @@ public uint Add(MiniFatEnumerator miniFatEnumerator, uint startIndex) this[entry.Index] = SectorType.EndOfChain; Debug.Assert(entry.IsFree); - MiniSector miniSector = new(entry.Index, ioContext.MiniSectorSize); - if (ioContext.MiniStream.Length < miniSector.EndPosition) - ioContext.MiniStream.SetLength(miniSector.EndPosition); + MiniSector miniSector = new(entry.Index, Context.MiniSectorSize); + if (Context.MiniStream.Length < miniSector.EndPosition) + Context.MiniStream.SetLength(miniSector.EndPosition); return entry.Index; } internal void Trace(TextWriter writer) { - using MiniFatEnumerator miniFatEnumerator = new(ioContext); + using MiniFatEnumerator miniFatEnumerator = new(ContextSite); writer.WriteLine("Start of Mini FAT ============"); while (miniFatEnumerator.MoveNext()) @@ -153,12 +152,12 @@ internal void Trace(TextWriter writer) internal void Validate() { - using MiniFatEnumerator miniFatEnumerator = new(ioContext); + using MiniFatEnumerator miniFatEnumerator = new(ContextSite); while (miniFatEnumerator.MoveNext()) { FatEntry current = miniFatEnumerator.Current; - if (current.Value <= SectorType.Maximum && miniFatEnumerator.CurrentSector.EndPosition > ioContext.MiniStream.Length) + if (current.Value <= SectorType.Maximum && miniFatEnumerator.CurrentSector.EndPosition > Context.MiniStream.Length) { throw new FormatException($"Mini FAT entry {current} is beyond the end of the mini stream."); } diff --git a/OpenMcdf/MiniFatChainEnumerator.cs b/OpenMcdf/MiniFatChainEnumerator.cs index 1b02e56b..d6ff4cfc 100644 --- a/OpenMcdf/MiniFatChainEnumerator.cs +++ b/OpenMcdf/MiniFatChainEnumerator.cs @@ -6,9 +6,8 @@ namespace OpenMcdf; /// /// Enumerates the s in a Mini FAT sector chain. /// -internal sealed class MiniFatChainEnumerator : IEnumerator +internal sealed class MiniFatChainEnumerator : ContextBase, IEnumerator { - private readonly IOContext ioContext; private readonly MiniFatEnumerator miniFatEnumerator; private uint startId; private bool start = true; @@ -16,11 +15,11 @@ internal sealed class MiniFatChainEnumerator : IEnumerator private FatChainEntry current = FatChainEntry.Invalid; private long length = -1; - public MiniFatChainEnumerator(IOContext ioContext, uint startSectorId) + public MiniFatChainEnumerator(RootContextSite rootContextSite, uint startSectorId) + : base(rootContextSite) { - this.ioContext = ioContext; startId = startSectorId; - miniFatEnumerator = new(ioContext); + miniFatEnumerator = new(rootContextSite); } /// @@ -36,7 +35,7 @@ public void Dispose() public uint Index => index; - public MiniSector CurrentSector => new(Current.Value, ioContext.MiniSectorSize); + public MiniSector CurrentSector => new(Current.Value, Context.MiniSectorSize); /// public FatChainEntry Current @@ -63,7 +62,7 @@ public bool MoveNext() } else if (!current.IsFreeOrEndOfChain) { - uint sectorId = ioContext.MiniFat[current.Value]; + uint sectorId = Context.MiniFat[current.Value]; if (sectorId == SectorType.EndOfChain) { index = uint.MaxValue; @@ -132,7 +131,7 @@ public void Extend(uint requiredChainLength) if (startId == StreamId.NoStream) { - startId = ioContext.MiniFat.Add(miniFatEnumerator, 0); + startId = Context.MiniFat.Add(miniFatEnumerator, 0); chainLength = 1; } @@ -144,8 +143,8 @@ public void Extend(uint requiredChainLength) Debug.Assert(ok); while (chainLength < requiredChainLength) { - uint id = ioContext.MiniFat.Add(miniFatEnumerator, lastId); - ioContext.MiniFat[lastId] = id; + uint id = Context.MiniFat.Add(miniFatEnumerator, lastId); + Context.MiniFat[lastId] = id; lastId = id; chainLength++; } @@ -173,16 +172,16 @@ public void Shrink(uint requiredChainLength) if (lastId <= SectorType.Maximum) { if (index == requiredChainLength) - ioContext.MiniFat[lastId] = SectorType.EndOfChain; + Context.MiniFat[lastId] = SectorType.EndOfChain; else if (index > requiredChainLength) - ioContext.MiniFat[lastId] = SectorType.Free; + Context.MiniFat[lastId] = SectorType.Free; } lastId = current.Value; } if (lastId <= SectorType.Maximum) - ioContext.MiniFat[lastId] = SectorType.Free; + Context.MiniFat[lastId] = SectorType.Free; if (requiredChainLength == 0) { diff --git a/OpenMcdf/MiniFatEnumerator.cs b/OpenMcdf/MiniFatEnumerator.cs index d7cd55a0..e5ad35f3 100644 --- a/OpenMcdf/MiniFatEnumerator.cs +++ b/OpenMcdf/MiniFatEnumerator.cs @@ -5,18 +5,17 @@ namespace OpenMcdf; /// /// Enumerates the s from the Mini FAT. /// -internal sealed class MiniFatEnumerator : IEnumerator +internal sealed class MiniFatEnumerator : ContextBase, IEnumerator { - private readonly IOContext ioContext; private readonly FatChainEnumerator fatChainEnumerator; private bool start = true; private uint index = uint.MaxValue; private uint value = uint.MaxValue; - public MiniFatEnumerator(IOContext ioContext) + public MiniFatEnumerator(RootContextSite rootContextSite) + : base(rootContextSite) { - fatChainEnumerator = new(ioContext, ioContext.Header.FirstMiniFatSectorId); - this.ioContext = ioContext; + fatChainEnumerator = new(Context.Fat, Context.Header.FirstMiniFatSectorId); } /// @@ -31,7 +30,7 @@ public MiniSector CurrentSector { if (index == uint.MaxValue) throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); - return new(value, ioContext.MiniSectorSize); + return new(value, Context.MiniSectorSize); } } @@ -72,7 +71,7 @@ public bool MoveTo(uint index) if (this.index == index) return true; - if (ioContext.MiniFat.TryGetValue(index, out value)) + if (Context.MiniFat.TryGetValue(index, out value)) { this.index = index; return true; @@ -98,7 +97,7 @@ public bool MoveNextFreeEntry() /// public void Reset() { - fatChainEnumerator.Reset(ioContext.Header.FirstMiniFatSectorId); + fatChainEnumerator.Reset(Context.Header.FirstMiniFatSectorId); start = true; index = uint.MaxValue; value = uint.MaxValue; diff --git a/OpenMcdf/MiniFatStream.cs b/OpenMcdf/MiniFatStream.cs index 6afaf9f5..69699e02 100644 --- a/OpenMcdf/MiniFatStream.cs +++ b/OpenMcdf/MiniFatStream.cs @@ -5,28 +5,30 @@ /// internal sealed class MiniFatStream : Stream { - readonly IOContext ioContext; + readonly RootContextSite rootContextSite; readonly MiniFatChainEnumerator miniChain; long position; bool disposed; bool isDirty; - internal MiniFatStream(IOContext ioContext, DirectoryEntry directoryEntry) + internal MiniFatStream(RootContextSite rootContextSite, DirectoryEntry directoryEntry) { - this.ioContext = ioContext; + this.rootContextSite = rootContextSite; DirectoryEntry = directoryEntry; - miniChain = new(ioContext, directoryEntry.StartSectorId); + miniChain = new(rootContextSite, directoryEntry.StartSectorId); } + RootContext Context => rootContextSite.Context; + internal DirectoryEntry DirectoryEntry { get; private set; } - internal long ChainCapacity => ((Length + ioContext.MiniSectorSize - 1) / ioContext.MiniSectorSize) * ioContext.MiniSectorSize; + internal long ChainCapacity => ((Length + Context.MiniSectorSize - 1) / Context.MiniSectorSize) * Context.MiniSectorSize; public override bool CanRead => true; public override bool CanSeek => true; - public override bool CanWrite => ioContext.CanWrite; + public override bool CanWrite => Context.CanWrite; public override long Length => DirectoryEntry.StreamLength; @@ -55,11 +57,11 @@ public override void Flush() if (isDirty) { - ioContext.DirectoryEntries.Write(DirectoryEntry); + Context.DirectoryEntries.Write(DirectoryEntry); isDirty = false; } - ioContext.MiniStream.Flush(); + Context.MiniStream.Flush(); } public override int Read(byte[] buffer, int offset, int count) @@ -75,11 +77,11 @@ public override int Read(byte[] buffer, int offset, int count) if (maxCount == 0) return 0; - uint chainIndex = (uint)Math.DivRem(position, ioContext.MiniSectorSize, out long sectorOffset); + uint chainIndex = (uint)Math.DivRem(position, Context.MiniSectorSize, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) return 0; - FatStream miniStream = ioContext.MiniStream; + FatStream miniStream = Context.MiniStream; int realCount = Math.Min(count, maxCount); int readCount = 0; do @@ -141,10 +143,10 @@ public override void SetLength(long value) this.ThrowIfDisposed(disposed); this.ThrowIfNotWritable(); - uint requiredChainLength = (uint)((value + ioContext.MiniSectorSize - 1) / ioContext.MiniSectorSize); + uint requiredChainLength = (uint)((value + Context.MiniSectorSize - 1) / Context.MiniSectorSize); if (value > ChainCapacity) miniChain.Extend(requiredChainLength); - else if (value <= ChainCapacity - ioContext.MiniSectorSize) + else if (value <= ChainCapacity - Context.MiniSectorSize) miniChain.Shrink(requiredChainLength); DirectoryEntry.StartSectorId = miniChain.StartId; @@ -165,11 +167,11 @@ public override void Write(byte[] buffer, int offset, int count) if (position + count > ChainCapacity) SetLength(position + count); - uint chainIndex = (uint)Math.DivRem(position, ioContext.MiniSectorSize, out long sectorOffset); + uint chainIndex = (uint)Math.DivRem(position, Context.MiniSectorSize, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) throw new InvalidOperationException($"Failed to move to mini FAT chain index: {chainIndex}."); - FatStream miniStream = ioContext.MiniStream; + FatStream miniStream = Context.MiniStream; int writeCount = 0; do { @@ -178,7 +180,7 @@ public override void Write(byte[] buffer, int offset, int count) miniStream.Seek(basePosition, SeekOrigin.Begin); int remaining = count - writeCount; int localOffset = offset + writeCount; - long writeLength = Math.Min(remaining, ioContext.MiniSectorSize - sectorOffset); + long writeLength = Math.Min(remaining, Context.MiniSectorSize - sectorOffset); miniStream.Write(buffer, localOffset, (int)writeLength); position += writeLength; writeCount += (int)writeLength; @@ -210,11 +212,11 @@ public override int Read(Span buffer) if (maxCount == 0) return 0; - uint chainIndex = (uint)Math.DivRem(position, ioContext.MiniSectorSize, out long sectorOffset); + uint chainIndex = (uint)Math.DivRem(position, Context.MiniSectorSize, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) return 0; - FatStream miniStream = ioContext.MiniStream; + FatStream miniStream = Context.MiniStream; int realCount = Math.Min(buffer.Length, maxCount); int readCount = 0; do @@ -251,11 +253,11 @@ public override void Write(ReadOnlySpan buffer) if (position + buffer.Length > ChainCapacity) SetLength(position + buffer.Length); - uint chainIndex = (uint)Math.DivRem(position, ioContext.MiniSectorSize, out long sectorOffset); + uint chainIndex = (uint)Math.DivRem(position, Context.MiniSectorSize, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) throw new InvalidOperationException($"Failed to move to mini FAT chain index: {chainIndex}."); - FatStream miniStream = ioContext.MiniStream; + FatStream miniStream = Context.MiniStream; int writeCount = 0; do { @@ -264,7 +266,7 @@ public override void Write(ReadOnlySpan buffer) miniStream.Seek(basePosition, SeekOrigin.Begin); int remaining = buffer.Length - writeCount; int localOffset = writeCount; - long writeLength = Math.Min(remaining, ioContext.MiniSectorSize - sectorOffset); + long writeLength = Math.Min(remaining, Context.MiniSectorSize - sectorOffset); ReadOnlySpan slice = buffer.Slice(localOffset, (int)writeLength); miniStream.Write(slice); position += writeLength; diff --git a/OpenMcdf/IOContext.cs b/OpenMcdf/RootContext.cs similarity index 90% rename from OpenMcdf/IOContext.cs rename to OpenMcdf/RootContext.cs index da0cefd4..df72b3d0 100644 --- a/OpenMcdf/IOContext.cs +++ b/OpenMcdf/RootContext.cs @@ -11,7 +11,7 @@ enum IOContextFlags /// /// Encapsulates the objects required to read and write data to and from a compound file. /// -internal sealed class IOContext : IDisposable +internal sealed class RootContext : ContextBase, IDisposable { readonly IOContextFlags contextFlags; readonly CfbBinaryWriter? writer; @@ -45,7 +45,7 @@ public MiniFat MiniFat { get { - miniFat ??= new(this); + miniFat ??= new(ContextSite); return miniFat; } } @@ -54,7 +54,7 @@ public FatStream MiniStream { get { - miniStream ??= new(this, RootEntry); + miniStream ??= new(ContextSite, RootEntry); return miniStream; } } @@ -78,8 +78,11 @@ public FatStream MiniStream bool isDirty; - public IOContext(Stream stream, Version version, IOContextFlags contextFlags = IOContextFlags.None) + public RootContext(RootContextSite rootContextSite, Stream stream, Version version, IOContextFlags contextFlags = IOContextFlags.None) + : base(rootContextSite) { + rootContextSite.Switch(this); + BaseStream = stream; this.contextFlags = contextFlags; @@ -101,8 +104,8 @@ public IOContext(Stream stream, Version version, IOContextFlags contextFlags = I if (stream.CanWrite) writer = new(actualStream); - Fat = new(this); - DirectoryEntries = new(this); + Fat = new(ContextSite); + DirectoryEntries = new(ContextSite); if (contextFlags.HasFlag(IOContextFlags.Create)) { @@ -142,6 +145,8 @@ public void Dispose() public void Flush() { + Fat.Flush(); + if (isDirty && writer is not null && transactedStream is null) { // Ensure the stream is as long as expected diff --git a/OpenMcdf/RootContextSite.cs b/OpenMcdf/RootContextSite.cs new file mode 100644 index 00000000..e4c60455 --- /dev/null +++ b/OpenMcdf/RootContextSite.cs @@ -0,0 +1,16 @@ +namespace OpenMcdf; + +/// +/// A site for the object, to allow switching streams. +/// +internal class RootContextSite +{ + RootContext? context; + + internal RootContext Context => context!; + + internal void Switch(RootContext context) + { + this.context = context; + } +} diff --git a/OpenMcdf/RootStorage.cs b/OpenMcdf/RootStorage.cs index 1120a8a4..2f652008 100644 --- a/OpenMcdf/RootStorage.cs +++ b/OpenMcdf/RootStorage.cs @@ -1,4 +1,6 @@ -namespace OpenMcdf; +using System.Runtime.CompilerServices; + +namespace OpenMcdf; public enum Version : ushort { @@ -28,6 +30,16 @@ private static void ThrowIfLeaveOpen(StorageModeFlags flags) throw new ArgumentException($"{StorageModeFlags.LeaveOpen} is not valid for files"); } + private static IOContextFlags ToIOContextFlags(StorageModeFlags flags) + { + IOContextFlags contextFlags = IOContextFlags.None; + if (flags.HasFlag(StorageModeFlags.LeaveOpen)) + contextFlags |= IOContextFlags.LeaveOpen; + if (flags.HasFlag(StorageModeFlags.Transacted)) + contextFlags |= IOContextFlags.Transacted; + return contextFlags; + } + public static RootStorage Create(string fileName, Version version = Version.V3, StorageModeFlags flags = StorageModeFlags.None) { ThrowIfLeaveOpen(flags); @@ -42,14 +54,10 @@ public static RootStorage Create(Stream stream, Version version = Version.V3, St stream.SetLength(0); stream.Position = 0; - IOContextFlags contextFlags = IOContextFlags.Create; - if (flags.HasFlag(StorageModeFlags.LeaveOpen)) - contextFlags |= IOContextFlags.LeaveOpen; - if (flags.HasFlag(StorageModeFlags.Transacted)) - contextFlags |= IOContextFlags.Transacted; - - IOContext ioContext = new(stream, version, contextFlags); - return new RootStorage(ioContext, flags); + IOContextFlags contextFlags = ToIOContextFlags(flags) | IOContextFlags.Create; + RootContextSite rootContextSite = new(); + _ = new RootContext(rootContextSite, stream, version, contextFlags); + return new RootStorage(rootContextSite, flags); } public static RootStorage Open(string fileName, FileMode mode, StorageModeFlags flags = StorageModeFlags.None) @@ -73,29 +81,25 @@ public static RootStorage Open(Stream stream, StorageModeFlags flags = StorageMo stream.ThrowIfNotSeekable(); stream.Position = 0; - IOContextFlags contextFlags = IOContextFlags.None; - if (flags.HasFlag(StorageModeFlags.LeaveOpen)) - contextFlags |= IOContextFlags.LeaveOpen; - if (flags.HasFlag(StorageModeFlags.Transacted)) - contextFlags |= IOContextFlags.Transacted; - - IOContext ioContext = new(stream, Version.Unknown, contextFlags); - return new RootStorage(ioContext, flags); + IOContextFlags contextFlags = ToIOContextFlags(flags); + RootContextSite rootContextSite = new(); + _ = new RootContext(rootContextSite, stream, Version.Unknown, contextFlags); + return new RootStorage(rootContextSite, flags); } - RootStorage(IOContext ioContext, StorageModeFlags storageModeFlags) - : base(ioContext, ioContext.RootEntry) + RootStorage(RootContextSite rootContextSite, StorageModeFlags storageModeFlags) + : base(rootContextSite, rootContextSite.Context.RootEntry) { this.storageModeFlags = storageModeFlags; } - public void Dispose() => ioContext?.Dispose(); + public void Dispose() => Context?.Dispose(); public void Flush(bool consolidate = false) { - this.ThrowIfDisposed(ioContext.IsDisposed); + this.ThrowIfDisposed(Context.IsDisposed); - ioContext.Flush(); + Context.Flush(); if (consolidate) Consolidate(); @@ -109,21 +113,21 @@ void Consolidate() try { - if (ioContext.BaseStream is MemoryStream) - destinationStream = new MemoryStream((int)ioContext.BaseStream.Length); - else if (ioContext.BaseStream is FileStream) + if (Context.BaseStream is MemoryStream) + destinationStream = new MemoryStream((int)Context.BaseStream.Length); + else if (Context.BaseStream is FileStream) destinationStream = File.Create(Path.GetTempFileName()); else throw new NotSupportedException("Unsupported stream type for consolidation."); - using (RootStorage destinationStorage = Create(destinationStream, ioContext.Version, storageModeFlags)) + using (RootStorage destinationStorage = Create(destinationStream, Context.Version, storageModeFlags)) CopyTo(destinationStorage); - ioContext.BaseStream.Position = 0; + Context.BaseStream.Position = 0; destinationStream.Position = 0; - destinationStream.CopyTo(ioContext.BaseStream); - ioContext.BaseStream.SetLength(destinationStream.Length); + destinationStream.CopyTo(Context.BaseStream); + Context.BaseStream.SetLength(destinationStream.Length); } catch { @@ -138,28 +142,46 @@ void Consolidate() public void Commit() { - this.ThrowIfDisposed(ioContext.IsDisposed); + this.ThrowIfDisposed(Context.IsDisposed); - ioContext.Commit(); + Context.Commit(); } public void Revert() { - this.ThrowIfDisposed(ioContext.IsDisposed); + this.ThrowIfDisposed(Context.IsDisposed); + + Context.Revert(); + } + + public void SwitchTo(Stream stream) + { + Flush(); + + stream.SetLength(Context.BaseStream.Length); + + stream.Position = 0; + Context.BaseStream.Position = 0; + + Context.BaseStream.CopyTo(stream); + stream.Position = 0; + + Context.Dispose(); - ioContext.Revert(); + IOContextFlags contextFlags = ToIOContextFlags(storageModeFlags); + _ = new RootContext(ContextSite, stream, Version.Unknown, contextFlags); } internal void Trace(TextWriter writer) { - writer.WriteLine(ioContext.Header); - ioContext.Fat.WriteTrace(writer); - ioContext.MiniFat.Trace(writer); + writer.WriteLine(Context.Header); + Context.Fat.WriteTrace(writer); + Context.MiniFat.Trace(writer); } internal void Validate() { - ioContext.Fat.Validate(); - ioContext.MiniFat.Validate(); + Context.Fat.Validate(); + Context.MiniFat.Validate(); } } diff --git a/OpenMcdf/Storage.cs b/OpenMcdf/Storage.cs index 27b01609..8bc07bd7 100644 --- a/OpenMcdf/Storage.cs +++ b/OpenMcdf/Storage.cs @@ -3,20 +3,19 @@ /// /// An object in a compound file that is analogous to a file system directory. /// -public class Storage +public class Storage : ContextBase { - internal readonly IOContext ioContext; internal readonly DirectoryTree directoryTree; internal DirectoryEntry DirectoryEntry { get; } - internal Storage(IOContext ioContext, DirectoryEntry directoryEntry) + internal Storage(RootContextSite rootContextSite, DirectoryEntry directoryEntry) + : base(rootContextSite) { if (directoryEntry.Type is not StorageType.Storage and not StorageType.Root) throw new ArgumentException("DirectoryEntry must be a Storage or Root.", nameof(directoryEntry)); - this.ioContext = ioContext; - directoryTree = new(ioContext.DirectoryEntries, directoryEntry); + directoryTree = new(Context.DirectoryEntries, directoryEntry); DirectoryEntry = directoryEntry; } @@ -24,7 +23,7 @@ internal Storage(IOContext ioContext, DirectoryEntry directoryEntry) public IEnumerable EnumerateEntries() { - this.ThrowIfDisposed(ioContext.IsDisposed); + this.ThrowIfDisposed(Context.IsDisposed); return EnumerateDirectoryEntries() .Select(e => e.ToEntryInfo()); @@ -32,7 +31,7 @@ public IEnumerable EnumerateEntries() IEnumerable EnumerateDirectoryEntries() { - using DirectoryTreeEnumerator treeEnumerator = new(ioContext.DirectoryEntries, DirectoryEntry); + using DirectoryTreeEnumerator treeEnumerator = new(Context.DirectoryEntries, DirectoryEntry); while (treeEnumerator.MoveNext()) { yield return treeEnumerator.Current; @@ -44,7 +43,7 @@ IEnumerable EnumerateDirectoryEntries(StorageType type) => Enume DirectoryEntry AddDirectoryEntry(StorageType storageType, string name) { - DirectoryEntry entry = ioContext.DirectoryEntries.CreateOrRecycleDirectoryEntry(); + DirectoryEntry entry = Context.DirectoryEntries.CreateOrRecycleDirectoryEntry(); entry.Recycle(storageType, name); directoryTree.Add(entry); return entry; @@ -54,47 +53,47 @@ public Storage CreateStorage(string name) { ThrowHelper.ThrowIfNameIsInvalid(name); - this.ThrowIfDisposed(ioContext.IsDisposed); + this.ThrowIfDisposed(Context.IsDisposed); DirectoryEntry entry = AddDirectoryEntry(StorageType.Storage, name); - return new Storage(ioContext, entry); + return new Storage(ContextSite, entry); } public CfbStream CreateStream(string name) { ThrowHelper.ThrowIfNameIsInvalid(name); - this.ThrowIfDisposed(ioContext.IsDisposed); + this.ThrowIfDisposed(Context.IsDisposed); // TODO: Return a Stream that can transition between FAT and mini FAT DirectoryEntry entry = AddDirectoryEntry(StorageType.Stream, name); - return new CfbStream(ioContext, entry); + return new CfbStream(ContextSite, entry); } public Storage OpenStorage(string name) { ThrowHelper.ThrowIfNameIsInvalid(name); - this.ThrowIfDisposed(ioContext.IsDisposed); + this.ThrowIfDisposed(Context.IsDisposed); directoryTree.TryGetDirectoryEntry(name, out DirectoryEntry? entry); if (entry is null || entry.Type is not StorageType.Storage) throw new DirectoryNotFoundException($"Storage not found: {name}."); - return new Storage(ioContext, entry); + return new Storage(ContextSite, entry); } public CfbStream OpenStream(string name) { ThrowHelper.ThrowIfNameIsInvalid(name); - this.ThrowIfDisposed(ioContext.IsDisposed); + this.ThrowIfDisposed(Context.IsDisposed); directoryTree.TryGetDirectoryEntry(name, out DirectoryEntry? entry); if (entry is null || entry.Type is not StorageType.Stream) throw new FileNotFoundException($"Stream not found: {name}.", name); // TODO: Return a Stream that can transition between FAT and mini FAT - return new CfbStream(ioContext, entry); + return new CfbStream(ContextSite, entry); } public void CopyTo(Storage destination) @@ -103,13 +102,13 @@ public void CopyTo(Storage destination) { if (entry.Type is StorageType.Storage) { - Storage subSource = new(ioContext, entry); + Storage subSource = new(ContextSite, entry); Storage subDestination = destination.CreateStorage(entry.NameString); subSource.CopyTo(subDestination); } else if (entry.Type is StorageType.Stream) { - CfbStream stream = new(ioContext, entry); + CfbStream stream = new(ContextSite, entry); CfbStream destinationStream = destination.CreateStream(entry.NameString); stream.CopyTo(destinationStream); } @@ -120,7 +119,7 @@ public void Delete(string name) { ThrowHelper.ThrowIfNameIsInvalid(name); - this.ThrowIfDisposed(ioContext.IsDisposed); + this.ThrowIfDisposed(Context.IsDisposed); directoryTree.TryGetDirectoryEntry(name, out DirectoryEntry? entry); if (entry is null) @@ -128,7 +127,7 @@ public void Delete(string name) if (entry.Type is StorageType.Storage && entry.ChildId is not StreamId.NoStream) { - Storage storage = new(ioContext, entry); + Storage storage = new(ContextSite, entry); foreach (EntryInfo childEntry in storage.EnumerateEntries()) { storage.Delete(childEntry.Name); @@ -139,12 +138,12 @@ public void Delete(string name) { if (entry.StreamLength < Header.MiniStreamCutoffSize) { - using MiniFatChainEnumerator miniFatChainEnumerator = new(ioContext, entry.StartSectorId); + using MiniFatChainEnumerator miniFatChainEnumerator = new(ContextSite, entry.StartSectorId); miniFatChainEnumerator.Shrink(0); } else { - using FatChainEnumerator fatChainEnumerator = new(ioContext, entry.StartSectorId); + using FatChainEnumerator fatChainEnumerator = new(Context.Fat, entry.StartSectorId); fatChainEnumerator.Shrink(0); } } diff --git a/OpenMcdf/TransactedStream.cs b/OpenMcdf/TransactedStream.cs index dd616bee..5c01310a 100644 --- a/OpenMcdf/TransactedStream.cs +++ b/OpenMcdf/TransactedStream.cs @@ -4,12 +4,12 @@ namespace OpenMcdf; internal class TransactedStream : Stream { - readonly IOContext ioContext; + readonly RootContext ioContext; readonly Stream originalStream; readonly Dictionary dirtySectorPositions = new(); readonly byte[] buffer; - public TransactedStream(IOContext ioContext, Stream originalStream, Stream overlayStream) + public TransactedStream(RootContext ioContext, Stream originalStream, Stream overlayStream) { this.ioContext = ioContext; this.originalStream = originalStream; From 7159cd40860a2b771877800e00e6171121128613 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 16:46:41 +1300 Subject: [PATCH 105/134] Improve directory/header validation --- OpenMcdf/CfbBinaryReader.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/OpenMcdf/CfbBinaryReader.cs b/OpenMcdf/CfbBinaryReader.cs index 371ca7db..6079c400 100644 --- a/OpenMcdf/CfbBinaryReader.cs +++ b/OpenMcdf/CfbBinaryReader.cs @@ -45,6 +45,8 @@ public int Read(Span buffer) #endif + void ReadExactly(byte[] buffer, int offset, int count) => BaseStream.ReadExactly(buffer, offset, count); + public Guid ReadGuid() { int bytesRead = 0; @@ -68,8 +70,9 @@ public DateTime ReadFileTime() public Header ReadHeader() { Header header = new(); - Read(buffer, 0, Header.Signature.Length); - if (!buffer.Take(Header.Signature.Length).SequenceEqual(Header.Signature)) + ReadExactly(buffer, 0, Header.Signature.Length); + Span signature = buffer.AsSpan(0, Header.Signature.Length); + if (!signature.SequenceEqual(Header.Signature)) throw new FormatException("Invalid header signature."); header.CLSID = ReadGuid(); if (header.CLSID != Guid.Empty) @@ -127,7 +130,7 @@ public DirectoryEntry ReadDirectoryEntry(Version version, uint sid) if (version is not Version.V3 and not Version.V4) throw new ArgumentException($"Unsupported version: {version}.", nameof(version)); - Read(buffer, 0, DirectoryEntry.NameFieldLength); + ReadExactly(buffer, 0, DirectoryEntry.NameFieldLength); DirectoryEntry entry = new() { From 5d599c028a98fc0a3b0f663cf3db5de14e43a58a Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 20:37:17 +1300 Subject: [PATCH 106/134] Refactor RootContext --- OpenMcdf.Ole/OlePropertiesContainer.cs | 4 +-- OpenMcdf.Tests/StorageTests.cs | 2 -- OpenMcdf/DifatSectorEnumerator.cs | 4 --- OpenMcdf/DirectoryEntries.cs | 10 +++--- OpenMcdf/Fat.cs | 8 +---- OpenMcdf/FatSectorEnumerator.cs | 25 ++++----------- OpenMcdf/FatStream.cs | 10 +++--- OpenMcdf/MiniFatStream.cs | 10 +++--- OpenMcdf/RootContext.cs | 11 ++++++- OpenMcdf/RootStorage.cs | 4 +-- OpenMcdf/TransactedStream.cs | 44 ++++++++++++++------------ 11 files changed, 60 insertions(+), 72 deletions(-) diff --git a/OpenMcdf.Ole/OlePropertiesContainer.cs b/OpenMcdf.Ole/OlePropertiesContainer.cs index e47220f4..371730d0 100644 --- a/OpenMcdf.Ole/OlePropertiesContainer.cs +++ b/OpenMcdf.Ole/OlePropertiesContainer.cs @@ -1,6 +1,4 @@ -using OpenMcdf; - -namespace OpenMcdf.Ole; +namespace OpenMcdf.Ole; public enum ContainerType { diff --git a/OpenMcdf.Tests/StorageTests.cs b/OpenMcdf.Tests/StorageTests.cs index 1043e93c..c8c95488 100644 --- a/OpenMcdf.Tests/StorageTests.cs +++ b/OpenMcdf.Tests/StorageTests.cs @@ -1,7 +1,5 @@ namespace OpenMcdf.Tests; -using Version = OpenMcdf.Version; - [TestClass] public sealed class StorageTests { diff --git a/OpenMcdf/DifatSectorEnumerator.cs b/OpenMcdf/DifatSectorEnumerator.cs index c48baf4d..d4fa2d6d 100644 --- a/OpenMcdf/DifatSectorEnumerator.cs +++ b/OpenMcdf/DifatSectorEnumerator.cs @@ -1,11 +1,9 @@ using System.Collections; -using System.Diagnostics; namespace OpenMcdf; internal class DifatSectorEnumerator : ContextBase, IEnumerator { - public readonly uint DifatElementsPerSector; bool start = true; uint index = uint.MaxValue; Sector current = Sector.EndOfChain; @@ -14,13 +12,11 @@ internal class DifatSectorEnumerator : ContextBase, IEnumerator public DifatSectorEnumerator(RootContextSite rootContextSite) : base(rootContextSite) { - DifatElementsPerSector = (uint)((Context.SectorSize / sizeof(uint)) - 1); } /// public void Dispose() { - // IOContext is owned by parent } /// diff --git a/OpenMcdf/DirectoryEntries.cs b/OpenMcdf/DirectoryEntries.cs index 69ef5927..a510e9c1 100644 --- a/OpenMcdf/DirectoryEntries.cs +++ b/OpenMcdf/DirectoryEntries.cs @@ -4,14 +4,12 @@ internal sealed class DirectoryEntries : ContextBase, IDisposable { private readonly FatChainEnumerator fatChainEnumerator; private readonly DirectoryEntryEnumerator directoryEntryEnumerator; - private readonly int entriesPerSector; public DirectoryEntries(RootContextSite rootContextSite) : base(rootContextSite) { fatChainEnumerator = new FatChainEnumerator(Context.Fat, Context.Header.FirstDirectorySectorId); directoryEntryEnumerator = new DirectoryEntryEnumerator(this); - entriesPerSector = Context.SectorSize / DirectoryEntry.Length; } public void Dispose() @@ -40,7 +38,7 @@ public bool TryGetDictionaryEntry(uint streamId, out DirectoryEntry? entry) if (streamId > StreamId.Maximum) throw new ArgumentException($"Invalid directory entry stream ID: ${streamId:X8}.", nameof(streamId)); - uint chainIndex = (uint)Math.DivRem(streamId, entriesPerSector, out long entryIndex); + uint chainIndex = GetChainIndexAndEntryIndex(streamId, out long entryIndex); if (!fatChainEnumerator.MoveTo(chainIndex)) { entry = null; @@ -52,6 +50,8 @@ public bool TryGetDictionaryEntry(uint streamId, out DirectoryEntry? entry) return true; } + private uint GetChainIndexAndEntryIndex(uint streamId, out long entryIndex) => (uint)Math.DivRem(streamId, Context.DirectoryEntriesPerSector, out entryIndex); + public DirectoryEntry CreateOrRecycleDirectoryEntry() { DirectoryEntry? entry = TryRecycleDirectoryEntry(); @@ -68,7 +68,7 @@ public DirectoryEntry CreateOrRecycleDirectoryEntry() Sector sector = new(id, Context.SectorSize); writer.Position = sector.Position; - for (int i = 0; i < entriesPerSector; i++) + for (int i = 0; i < Context.DirectoryEntriesPerSector; i++) writer.Write(DirectoryEntry.Unallocated); entry = TryRecycleDirectoryEntry() @@ -92,7 +92,7 @@ public DirectoryEntry CreateOrRecycleDirectoryEntry() public void Write(DirectoryEntry entry) { - uint chainIndex = (uint)Math.DivRem(entry.Id, entriesPerSector, out long entryIndex); + uint chainIndex = GetChainIndexAndEntryIndex(entry.Id, out long entryIndex); if (!fatChainEnumerator.MoveTo(chainIndex)) throw new KeyNotFoundException($"Directory entry {entry.Id} was not found."); diff --git a/OpenMcdf/Fat.cs b/OpenMcdf/Fat.cs index 487bd74b..411c9c3f 100644 --- a/OpenMcdf/Fat.cs +++ b/OpenMcdf/Fat.cs @@ -10,7 +10,6 @@ namespace OpenMcdf; internal sealed class Fat : ContextBase, IEnumerable, IDisposable { private readonly FatSectorEnumerator fatSectorEnumerator; - internal readonly int FatElementsPerSector; private readonly byte[] cachedSectorBuffer; Sector cachedSector = Sector.EndOfChain; private bool isDirty; @@ -18,7 +17,6 @@ internal sealed class Fat : ContextBase, IEnumerable, IDisposable public Fat(RootContextSite rootContextSite) : base(rootContextSite) { - FatElementsPerSector = Context.SectorSize / sizeof(uint); fatSectorEnumerator = new(rootContextSite); cachedSectorBuffer = new byte[Context.SectorSize]; } @@ -46,11 +44,7 @@ public uint this[uint key] } } - uint GetSectorIndexAndElementOffset(uint key, out long elementIndex) - { - uint index = (uint)Math.DivRem(key, FatElementsPerSector, out elementIndex); - return index; - } + uint GetSectorIndexAndElementOffset(uint key, out long elementIndex) => (uint)Math.DivRem(key, Context.FatEntriesPerSector, out elementIndex); void CacheCurrentSector() { diff --git a/OpenMcdf/FatSectorEnumerator.cs b/OpenMcdf/FatSectorEnumerator.cs index 9ab32d26..39a2a179 100644 --- a/OpenMcdf/FatSectorEnumerator.cs +++ b/OpenMcdf/FatSectorEnumerator.cs @@ -1,5 +1,4 @@ using System.Collections; -using System.Diagnostics; namespace OpenMcdf; @@ -87,8 +86,8 @@ public bool MoveTo(uint index) this.index = Header.DifatArrayLength; } - uint difatSectorIndex = (uint)Math.DivRem(index - Header.DifatArrayLength, difatSectorEnumerator.DifatElementsPerSector, out long difatElementIndex); - if (!difatSectorEnumerator.MoveTo(difatSectorIndex)) + uint difatChainIndex = GetDifatChainIndexAndDifatEntryIndex(index, out long difatElementIndex); + if (!difatSectorEnumerator.MoveTo(difatChainIndex)) { this.index = uint.MaxValue; current = Sector.EndOfChain; @@ -103,6 +102,9 @@ public bool MoveTo(uint index) return true; } + private uint GetDifatChainIndexAndDifatEntryIndex(uint index, out long difatElementIndex) + => (uint)Math.DivRem(index - Header.DifatArrayLength, Context.DifatEntriesPerSector, out difatElementIndex); + /// public void Reset() { @@ -112,21 +114,6 @@ public void Reset() difatSectorEnumerator.Reset(); } - (uint lastIndex, Sector lastSector) MoveToEnd() - { - Reset(); - - uint lastIndex = uint.MaxValue; - Sector lastSector = Sector.EndOfChain; - while (MoveNext()) - { - lastIndex = index; - lastSector = current; - } - - return (lastIndex, lastSector); - } - /// /// Extends the FAT by adding a new sector. /// @@ -154,7 +141,7 @@ public uint Add() } else { - uint difatSectorIndex = (uint)Math.DivRem(nextIndex - Header.DifatArrayLength, difatSectorEnumerator.DifatElementsPerSector, out long difatElementIndex); + uint difatSectorIndex = GetDifatChainIndexAndDifatEntryIndex(nextIndex, out long difatElementIndex); if (!difatSectorEnumerator.MoveTo(difatSectorIndex)) difatSectorEnumerator.Add(); diff --git a/OpenMcdf/FatStream.cs b/OpenMcdf/FatStream.cs index bede4309..871c164a 100644 --- a/OpenMcdf/FatStream.cs +++ b/OpenMcdf/FatStream.cs @@ -73,6 +73,8 @@ public override void Flush() Context.Writer!.Flush(); } + uint GetFatChainIndexAndSectorOffset(long offset, out long sectorOffset) => (uint)Math.DivRem(offset, Context.SectorSize, out sectorOffset); + /// public override int Read(byte[] buffer, int offset, int count) { @@ -87,7 +89,7 @@ public override int Read(byte[] buffer, int offset, int count) if (maxCount == 0) return 0; - uint chainIndex = (uint)Math.DivRem(position, Context.SectorSize, out long sectorOffset); + uint chainIndex = GetFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!chain.MoveTo(chainIndex)) return 0; @@ -171,7 +173,7 @@ public override void Write(byte[] buffer, int offset, int count) if (count == 0) return; - uint chainIndex = (uint)Math.DivRem(position, Context.SectorSize, out long sectorOffset); + uint chainIndex = GetFatChainIndexAndSectorOffset(position, out long sectorOffset); CfbBinaryWriter writer = Context.Writer; int writeCount = 0; @@ -220,7 +222,7 @@ public override int Read(Span buffer) if (maxCount == 0) return 0; - uint chainIndex = (uint)Math.DivRem(position, Context.SectorSize, out long sectorOffset); + uint chainIndex = GetFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!chain.MoveTo(chainIndex)) return 0; @@ -257,7 +259,7 @@ public override void Write(ReadOnlySpan buffer) if (buffer.Length == 0) return; - uint chainIndex = (uint)Math.DivRem(position, Context.SectorSize, out long sectorOffset); + uint chainIndex = GetFatChainIndexAndSectorOffset(position, out long sectorOffset); CfbBinaryWriter writer = Context.Writer; int writeCount = 0; diff --git a/OpenMcdf/MiniFatStream.cs b/OpenMcdf/MiniFatStream.cs index 69699e02..2411feb4 100644 --- a/OpenMcdf/MiniFatStream.cs +++ b/OpenMcdf/MiniFatStream.cs @@ -64,6 +64,8 @@ public override void Flush() Context.MiniStream.Flush(); } + uint GetMiniFatChainIndexAndSectorOffset(long offset, out long sectorOffset) => (uint)Math.DivRem(offset, Context.MiniSectorSize, out sectorOffset); + public override int Read(byte[] buffer, int offset, int count) { ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); @@ -77,7 +79,7 @@ public override int Read(byte[] buffer, int offset, int count) if (maxCount == 0) return 0; - uint chainIndex = (uint)Math.DivRem(position, Context.MiniSectorSize, out long sectorOffset); + uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) return 0; @@ -167,7 +169,7 @@ public override void Write(byte[] buffer, int offset, int count) if (position + count > ChainCapacity) SetLength(position + count); - uint chainIndex = (uint)Math.DivRem(position, Context.MiniSectorSize, out long sectorOffset); + uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) throw new InvalidOperationException($"Failed to move to mini FAT chain index: {chainIndex}."); @@ -212,7 +214,7 @@ public override int Read(Span buffer) if (maxCount == 0) return 0; - uint chainIndex = (uint)Math.DivRem(position, Context.MiniSectorSize, out long sectorOffset); + uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) return 0; @@ -253,7 +255,7 @@ public override void Write(ReadOnlySpan buffer) if (position + buffer.Length > ChainCapacity) SetLength(position + buffer.Length); - uint chainIndex = (uint)Math.DivRem(position, Context.MiniSectorSize, out long sectorOffset); + uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) throw new InvalidOperationException($"Failed to move to mini FAT chain index: {chainIndex}."); diff --git a/OpenMcdf/RootContext.cs b/OpenMcdf/RootContext.cs index df72b3d0..f95addd8 100644 --- a/OpenMcdf/RootContext.cs +++ b/OpenMcdf/RootContext.cs @@ -70,6 +70,12 @@ public FatStream MiniStream public int MiniSectorSize { get; } + public int FatEntriesPerSector { get; } + + public int DifatEntriesPerSector { get; } + + public int DirectoryEntriesPerSector { get; } + public Version Version => (Version)Header.MajorVersion; public long Length { get; private set; } @@ -90,13 +96,16 @@ public RootContext(RootContextSite rootContextSite, Stream stream, Version versi Header = contextFlags.HasFlag(IOContextFlags.Create) ? new(version) : reader.ReadHeader(); SectorSize = 1 << Header.SectorShift; MiniSectorSize = 1 << Header.MiniSectorShift; + FatEntriesPerSector = SectorSize / sizeof(uint); + DifatEntriesPerSector = FatEntriesPerSector - 1; + DirectoryEntriesPerSector = SectorSize / DirectoryEntry.Length; Length = stream.Length; Stream actualStream = stream; if (contextFlags.HasFlag(IOContextFlags.Transacted)) { Stream overlayStream = stream is MemoryStream ? new MemoryStream() : File.Create(Path.GetTempFileName()); - transactedStream = new TransactedStream(this, stream, overlayStream); + transactedStream = new TransactedStream(ContextSite, stream, overlayStream); actualStream = new BufferedStream(transactedStream, SectorSize); } diff --git a/OpenMcdf/RootStorage.cs b/OpenMcdf/RootStorage.cs index 2f652008..86e2c3fd 100644 --- a/OpenMcdf/RootStorage.cs +++ b/OpenMcdf/RootStorage.cs @@ -1,6 +1,4 @@ -using System.Runtime.CompilerServices; - -namespace OpenMcdf; +namespace OpenMcdf; public enum Version : ushort { diff --git a/OpenMcdf/TransactedStream.cs b/OpenMcdf/TransactedStream.cs index 5c01310a..6b8d2a58 100644 --- a/OpenMcdf/TransactedStream.cs +++ b/OpenMcdf/TransactedStream.cs @@ -4,17 +4,19 @@ namespace OpenMcdf; internal class TransactedStream : Stream { - readonly RootContext ioContext; + readonly RootContextSite rootContextSite; readonly Stream originalStream; readonly Dictionary dirtySectorPositions = new(); readonly byte[] buffer; - public TransactedStream(RootContext ioContext, Stream originalStream, Stream overlayStream) + RootContext Context => rootContextSite.Context; + + public TransactedStream(RootContextSite rootContextSite, Stream originalStream, Stream overlayStream) { - this.ioContext = ioContext; + this.rootContextSite = rootContextSite; this.originalStream = originalStream; OverlayStream = overlayStream; - buffer = new byte[ioContext.SectorSize]; + buffer = new byte[Context.SectorSize]; } protected override void Dispose(bool disposing) @@ -39,6 +41,8 @@ protected override void Dispose(bool disposing) public override void Flush() => OverlayStream.Flush(); + uint GetFatChainIndexAndSectorOffset(long offset, out long sectorOffset) => (uint)Math.DivRem(offset, Context.SectorSize, out sectorOffset); + public override int Read(byte[] buffer, int offset, int count) { ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); @@ -47,8 +51,8 @@ public override int Read(byte[] buffer, int offset, int count) int totalRead = 0; do { - uint sectorId = (uint)Math.DivRem(originalStream.Position, ioContext.SectorSize, out long sectorOffset); - int remainingFromSector = ioContext.SectorSize - (int)sectorOffset; + uint sectorId = GetFatChainIndexAndSectorOffset(originalStream.Position, out long sectorOffset); + int remainingFromSector = Context.SectorSize - (int)sectorOffset; int localCount = Math.Min(count - totalRead, remainingFromSector); if (dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) @@ -79,8 +83,8 @@ public override void Write(byte[] buffer, int offset, int count) { ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); - uint sectorId = (uint)Math.DivRem(originalStream.Position, ioContext.SectorSize, out long sectorOffset); - int remainingFromSector = ioContext.SectorSize - (int)sectorOffset; + uint sectorId = GetFatChainIndexAndSectorOffset(originalStream.Position, out long sectorOffset); + int remainingFromSector = Context.SectorSize - (int)sectorOffset; int localCount = Math.Min(count, remainingFromSector); Debug.Assert(localCount == count); // TODO: Loop through the buffer and write to the overlay stream @@ -94,7 +98,7 @@ public override void Write(byte[] buffer, int offset, int count) } long originalPosition = originalStream.Position; - if (added && localCount != ioContext.SectorSize && originalPosition < originalStream.Length) + if (added && localCount != Context.SectorSize && originalPosition < originalStream.Length) { // Copy the existing sector data originalStream.Position = originalPosition - sectorOffset; @@ -106,8 +110,8 @@ public override void Write(byte[] buffer, int offset, int count) OverlayStream.Position = overlayPosition + sectorOffset; OverlayStream.Write(buffer, offset, localCount); - if (OverlayStream.Length < overlayPosition + ioContext.SectorSize) - OverlayStream.SetLength(overlayPosition + ioContext.SectorSize); + if (OverlayStream.Length < overlayPosition + Context.SectorSize) + OverlayStream.SetLength(overlayPosition + Context.SectorSize); originalStream.Position = originalPosition + localCount; } @@ -118,7 +122,7 @@ public void Commit() OverlayStream.Position = entry.Value; OverlayStream.ReadExactly(buffer); - originalStream.Position = entry.Key * ioContext.SectorSize; + originalStream.Position = entry.Key * Context.SectorSize; originalStream.Write(buffer, 0, buffer.Length); } @@ -137,8 +141,8 @@ public void Revert() public override int Read(Span buffer) { - uint sectorId = (uint)Math.DivRem(originalStream.Position, ioContext.SectorSize, out long sectorOffset); - int remainingFromSector = ioContext.SectorSize - (int)sectorOffset; + uint sectorId = (uint)Math.DivRem(originalStream.Position, Context.SectorSize, out long sectorOffset); + int remainingFromSector = Context.SectorSize - (int)sectorOffset; int localCount = Math.Min(buffer.Length, remainingFromSector); Debug.Assert(localCount == buffer.Length); @@ -162,8 +166,8 @@ public override int Read(Span buffer) public override void Write(ReadOnlySpan buffer) { - uint sectorId = (uint)Math.DivRem(originalStream.Position, ioContext.SectorSize, out long sectorOffset); - int remainingFromSector = ioContext.SectorSize - (int)sectorOffset; + uint sectorId = (uint)Math.DivRem(originalStream.Position, Context.SectorSize, out long sectorOffset); + int remainingFromSector = Context.SectorSize - (int)sectorOffset; int localCount = Math.Min(buffer.Length, remainingFromSector); Debug.Assert(localCount == buffer.Length); // TODO: Loop through the buffer and write to the overlay stream @@ -177,7 +181,7 @@ public override void Write(ReadOnlySpan buffer) } long originalPosition = originalStream.Position; - if (added && localCount != ioContext.SectorSize && originalPosition < originalStream.Length) + if (added && localCount != Context.SectorSize && originalPosition < originalStream.Length) { // Copy the existing sector data originalStream.Position = originalPosition - sectorOffset; @@ -189,10 +193,10 @@ public override void Write(ReadOnlySpan buffer) OverlayStream.Position = overlayPosition + sectorOffset; OverlayStream.Write(buffer); - if (OverlayStream.Length < overlayPosition + ioContext.SectorSize) - OverlayStream.SetLength(overlayPosition + ioContext.SectorSize); + if (OverlayStream.Length < overlayPosition + Context.SectorSize) + OverlayStream.SetLength(overlayPosition + Context.SectorSize); originalStream.Position = originalPosition + localCount; } #endif -} \ No newline at end of file +} From 2eae5e43ae9235b5e8512796a225ea3eeda7cfce Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 20:48:36 +1300 Subject: [PATCH 107/134] Improve comments --- OpenMcdf/ContextBase.cs | 2 +- OpenMcdf/DifatSectorEnumerator.cs | 3 +++ OpenMcdf/DirectoryEntries.cs | 3 +++ OpenMcdf/DirectoryEntryComparer.cs | 3 +++ OpenMcdf/DirectoryEntryEnumerator.cs | 2 +- OpenMcdf/DirectoryTree.cs | 5 ++++- OpenMcdf/FatEnumerator.cs | 2 +- OpenMcdf/FatSectorEnumerator.cs | 2 +- OpenMcdf/MiniFat.cs | 2 +- OpenMcdf/MiniFatChainEnumerator.cs | 2 +- OpenMcdf/MiniFatEnumerator.cs | 2 +- OpenMcdf/StreamExtensions.cs | 3 +++ OpenMcdf/TransactedStream.cs | 3 +++ 13 files changed, 26 insertions(+), 8 deletions(-) diff --git a/OpenMcdf/ContextBase.cs b/OpenMcdf/ContextBase.cs index ccf84c5d..28e6a12e 100644 --- a/OpenMcdf/ContextBase.cs +++ b/OpenMcdf/ContextBase.cs @@ -1,7 +1,7 @@ namespace OpenMcdf; /// -/// Supports switching the object. +/// Supports switching the object. /// public abstract class ContextBase { diff --git a/OpenMcdf/DifatSectorEnumerator.cs b/OpenMcdf/DifatSectorEnumerator.cs index d4fa2d6d..455e30b9 100644 --- a/OpenMcdf/DifatSectorEnumerator.cs +++ b/OpenMcdf/DifatSectorEnumerator.cs @@ -2,6 +2,9 @@ namespace OpenMcdf; +/// +/// Enumerates the s in a DIFAT chain. +/// internal class DifatSectorEnumerator : ContextBase, IEnumerator { bool start = true; diff --git a/OpenMcdf/DirectoryEntries.cs b/OpenMcdf/DirectoryEntries.cs index a510e9c1..da78abc2 100644 --- a/OpenMcdf/DirectoryEntries.cs +++ b/OpenMcdf/DirectoryEntries.cs @@ -1,5 +1,8 @@ namespace OpenMcdf; +/// +/// Encapsulates getting and adding objects." +/// internal sealed class DirectoryEntries : ContextBase, IDisposable { private readonly FatChainEnumerator fatChainEnumerator; diff --git a/OpenMcdf/DirectoryEntryComparer.cs b/OpenMcdf/DirectoryEntryComparer.cs index ae255374..d97a19b5 100644 --- a/OpenMcdf/DirectoryEntryComparer.cs +++ b/OpenMcdf/DirectoryEntryComparer.cs @@ -2,6 +2,9 @@ namespace OpenMcdf; +/// +/// Provides a for objects. +/// internal class DirectoryEntryComparer : IComparer { public static DirectoryEntryComparer Default { get; } = new(); diff --git a/OpenMcdf/DirectoryEntryEnumerator.cs b/OpenMcdf/DirectoryEntryEnumerator.cs index f80f78d1..b17ccffb 100644 --- a/OpenMcdf/DirectoryEntryEnumerator.cs +++ b/OpenMcdf/DirectoryEntryEnumerator.cs @@ -3,7 +3,7 @@ namespace OpenMcdf; /// -/// Enumerates instances from a . +/// Enumerates instances from a . /// internal sealed class DirectoryEntryEnumerator : IEnumerator { diff --git a/OpenMcdf/DirectoryTree.cs b/OpenMcdf/DirectoryTree.cs index 65b69ca5..1f0ba373 100644 --- a/OpenMcdf/DirectoryTree.cs +++ b/OpenMcdf/DirectoryTree.cs @@ -2,7 +2,10 @@ namespace OpenMcdf; -internal class DirectoryTree +/// +/// Encapsulates adding and removing objects to a tree. +/// +internal sealed class DirectoryTree { internal enum RelationType { diff --git a/OpenMcdf/FatEnumerator.cs b/OpenMcdf/FatEnumerator.cs index afeb1cd1..75190003 100644 --- a/OpenMcdf/FatEnumerator.cs +++ b/OpenMcdf/FatEnumerator.cs @@ -3,7 +3,7 @@ namespace OpenMcdf; /// -/// Enumerates the entries in a FAT. +/// Enumerates the records in a . /// internal class FatEnumerator : IEnumerator { diff --git a/OpenMcdf/FatSectorEnumerator.cs b/OpenMcdf/FatSectorEnumerator.cs index 39a2a179..6b00d4b8 100644 --- a/OpenMcdf/FatSectorEnumerator.cs +++ b/OpenMcdf/FatSectorEnumerator.cs @@ -3,7 +3,7 @@ namespace OpenMcdf; /// -/// Enumerates the FAT sectors of a compound file. +/// Enumerates the s of a compound file. /// internal sealed class FatSectorEnumerator : ContextBase, IEnumerator { diff --git a/OpenMcdf/MiniFat.cs b/OpenMcdf/MiniFat.cs index 8faa235c..3579660e 100644 --- a/OpenMcdf/MiniFat.cs +++ b/OpenMcdf/MiniFat.cs @@ -5,7 +5,7 @@ namespace OpenMcdf; /// -/// Encapsulates getting and setting entries in the mini FAT. +/// Encapsulates getting and setting records in the mini FAT. /// internal sealed class MiniFat : ContextBase, IEnumerable, IDisposable { diff --git a/OpenMcdf/MiniFatChainEnumerator.cs b/OpenMcdf/MiniFatChainEnumerator.cs index d6ff4cfc..fbf81e49 100644 --- a/OpenMcdf/MiniFatChainEnumerator.cs +++ b/OpenMcdf/MiniFatChainEnumerator.cs @@ -4,7 +4,7 @@ namespace OpenMcdf; /// -/// Enumerates the s in a Mini FAT sector chain. +/// Enumerates the s in a Mini FAT chain. /// internal sealed class MiniFatChainEnumerator : ContextBase, IEnumerator { diff --git a/OpenMcdf/MiniFatEnumerator.cs b/OpenMcdf/MiniFatEnumerator.cs index e5ad35f3..29d5be30 100644 --- a/OpenMcdf/MiniFatEnumerator.cs +++ b/OpenMcdf/MiniFatEnumerator.cs @@ -3,7 +3,7 @@ namespace OpenMcdf; /// -/// Enumerates the s from the Mini FAT. +/// Enumerates the s from the FAT chain for the mini FAT. /// internal sealed class MiniFatEnumerator : ContextBase, IEnumerator { diff --git a/OpenMcdf/StreamExtensions.cs b/OpenMcdf/StreamExtensions.cs index 5c6e2cb6..3d0941d3 100644 --- a/OpenMcdf/StreamExtensions.cs +++ b/OpenMcdf/StreamExtensions.cs @@ -4,6 +4,9 @@ namespace OpenMcdf; +/// +/// Adds modern extension methods to the class for netstandard2.0. +/// internal static class StreamExtensions { #if !NET7_0_OR_GREATER diff --git a/OpenMcdf/TransactedStream.cs b/OpenMcdf/TransactedStream.cs index 6b8d2a58..1abf31ac 100644 --- a/OpenMcdf/TransactedStream.cs +++ b/OpenMcdf/TransactedStream.cs @@ -2,6 +2,9 @@ namespace OpenMcdf; +/// +/// Stores modifications to a CFB stream that can be committed or reverted. +/// internal class TransactedStream : Stream { readonly RootContextSite rootContextSite; From 4395d2a23475e8512bbacdf75510f2ced01f1ac3 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 20:49:00 +1300 Subject: [PATCH 108/134] Optimize FatChainEnumerator --- OpenMcdf/DirectoryEntries.cs | 3 +-- OpenMcdf/FatChainEnumerator.cs | 36 +++++++++++++++++----------------- OpenMcdf/FatStream.cs | 2 +- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/OpenMcdf/DirectoryEntries.cs b/OpenMcdf/DirectoryEntries.cs index da78abc2..21a1d35b 100644 --- a/OpenMcdf/DirectoryEntries.cs +++ b/OpenMcdf/DirectoryEntries.cs @@ -100,8 +100,7 @@ public void Write(DirectoryEntry entry) throw new KeyNotFoundException($"Directory entry {entry.Id} was not found."); CfbBinaryWriter writer = Context.Writer; - Sector sector = new(fatChainEnumerator.Current.Value, Context.SectorSize); - writer.Position = sector.Position + (entryIndex * DirectoryEntry.Length); + writer.Position = fatChainEnumerator.CurrentSector.Position + (entryIndex * DirectoryEntry.Length); writer.Write(entry); } } diff --git a/OpenMcdf/FatChainEnumerator.cs b/OpenMcdf/FatChainEnumerator.cs index a0725859..b89bf78b 100644 --- a/OpenMcdf/FatChainEnumerator.cs +++ b/OpenMcdf/FatChainEnumerator.cs @@ -6,14 +6,14 @@ namespace OpenMcdf; /// /// Enumerates the s in a FAT sector chain. /// -internal sealed class FatChainEnumerator : IEnumerator +internal sealed class FatChainEnumerator : IEnumerator { private readonly Fat fat; private readonly FatEnumerator fatEnumerator; private uint startId; private bool start = true; private uint index = uint.MaxValue; - private FatChainEntry current = FatChainEntry.Invalid; + private uint current = uint.MaxValue; private long length = -1; public FatChainEnumerator(Fat fat, uint startSectorId) @@ -29,10 +29,10 @@ public void Dispose() fatEnumerator.Dispose(); } - public Sector CurrentSector => new(Current.Value, fat.Context.SectorSize); + public Sector CurrentSector => new(current, fat.Context.SectorSize); /// - public FatChainEntry Current + public uint Current { get { @@ -55,28 +55,28 @@ public bool MoveNext() if (startId is SectorType.EndOfChain or SectorType.Free) { index = uint.MaxValue; - current = FatChainEntry.Invalid; + current = uint.MaxValue; return false; } index = 0; - current = new(index, startId); + current = startId; start = false; return true; } - if (current.IsFreeOrEndOfChain || current == FatChainEntry.Invalid) + if (SectorType.IsFreeOrEndOfChain(current)) { index = uint.MaxValue; - current = FatChainEntry.Invalid; + current = uint.MaxValue; return false; } - uint value = fat[current.Value]; + uint value = fat[current]; if (value is SectorType.EndOfChain) { index = uint.MaxValue; - current = FatChainEntry.Invalid; + current = uint.MaxValue; return false; } @@ -85,11 +85,11 @@ public bool MoveNext() { // If the index is greater than the maximum, then the chain must contain a loop index = uint.MaxValue; - current = FatChainEntry.Invalid; - throw new IOException("FAT sector chain is corrupt"); + current = uint.MaxValue; + throw new IOException("FAT sector chain is corrupt."); } - current = new(index, value); + current = value; return true; } @@ -154,7 +154,7 @@ public uint Extend(uint requiredChainLength) bool ok = MoveTo(chainLength - 1); Debug.Assert(ok); - uint lastId = current.Value; + uint lastId = current; ok = fatEnumerator.MoveTo(lastId); Debug.Assert(ok); while (chainLength < requiredChainLength) @@ -180,7 +180,7 @@ public uint ExtendFrom(uint hintId) uint lastId = startId; while (MoveNext()) { - lastId = current.Value; + lastId = current; } uint id = fat.Add(fatEnumerator, lastId); @@ -196,7 +196,7 @@ public uint Shrink(uint requiredChainLength) Reset(); - uint lastId = current.Value; + uint lastId = current; while (MoveNext()) { if (lastId is not SectorType.EndOfChain and not SectorType.Free) @@ -207,7 +207,7 @@ public uint Shrink(uint requiredChainLength) fat[lastId] = SectorType.Free; } - lastId = current.Value; + lastId = current; } fat[lastId] = SectorType.Free; @@ -235,7 +235,7 @@ public void Reset(uint startSectorId) startId = startSectorId; start = true; index = uint.MaxValue; - current = FatChainEntry.Invalid; + current = uint.MaxValue; } public override string ToString() => $"{current}"; diff --git a/OpenMcdf/FatStream.cs b/OpenMcdf/FatStream.cs index 871c164a..bf4cbd1a 100644 --- a/OpenMcdf/FatStream.cs +++ b/OpenMcdf/FatStream.cs @@ -97,7 +97,7 @@ public override int Read(byte[] buffer, int offset, int count) int readCount = 0; do { - Sector sector = new(chain.Current.Value, Context.SectorSize); + Sector sector = chain.CurrentSector; int remaining = realCount - readCount; long readLength = Math.Min(remaining, sector.Length - sectorOffset); Context.Reader.Position = sector.Position + sectorOffset; From 821cde4a005906494abaf37b0e50fb5c4175933d Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 20:52:40 +1300 Subject: [PATCH 109/134] Optimize MiniFatChainEnumerator --- OpenMcdf/FatChainEntry.cs | 10 ------- OpenMcdf/FatChainEnumerator.cs | 2 +- OpenMcdf/MiniFatChainEnumerator.cs | 44 ++++++++++++++---------------- OpenMcdf/MiniFatStream.cs | 5 ++-- 4 files changed, 24 insertions(+), 37 deletions(-) delete mode 100644 OpenMcdf/FatChainEntry.cs diff --git a/OpenMcdf/FatChainEntry.cs b/OpenMcdf/FatChainEntry.cs deleted file mode 100644 index 0f5fdde8..00000000 --- a/OpenMcdf/FatChainEntry.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace OpenMcdf; - -internal record struct FatChainEntry(uint Index, uint Value) -{ - internal static readonly FatChainEntry Invalid = new(uint.MaxValue, SectorType.EndOfChain); - - public readonly bool IsFreeOrEndOfChain => SectorType.IsFreeOrEndOfChain(Value); - - public override readonly string ToString() => $"#{Index}: {Value}"; -} diff --git a/OpenMcdf/FatChainEnumerator.cs b/OpenMcdf/FatChainEnumerator.cs index b89bf78b..2ca194d3 100644 --- a/OpenMcdf/FatChainEnumerator.cs +++ b/OpenMcdf/FatChainEnumerator.cs @@ -238,5 +238,5 @@ public void Reset(uint startSectorId) current = uint.MaxValue; } - public override string ToString() => $"{current}"; + public override string ToString() => $"Index: {index} Current: {current}"; } diff --git a/OpenMcdf/MiniFatChainEnumerator.cs b/OpenMcdf/MiniFatChainEnumerator.cs index fbf81e49..48e59aa4 100644 --- a/OpenMcdf/MiniFatChainEnumerator.cs +++ b/OpenMcdf/MiniFatChainEnumerator.cs @@ -6,13 +6,13 @@ namespace OpenMcdf; /// /// Enumerates the s in a Mini FAT chain. /// -internal sealed class MiniFatChainEnumerator : ContextBase, IEnumerator +internal sealed class MiniFatChainEnumerator : ContextBase, IEnumerator { private readonly MiniFatEnumerator miniFatEnumerator; private uint startId; private bool start = true; uint index = uint.MaxValue; - private FatChainEntry current = FatChainEntry.Invalid; + private uint current = uint.MaxValue; private long length = -1; public MiniFatChainEnumerator(RootContextSite rootContextSite, uint startSectorId) @@ -31,18 +31,14 @@ public void Dispose() /// The index within the Mini FAT sector chain, or if the enumeration has not started. /// - public uint StartId => startId; - - public uint Index => index; - - public MiniSector CurrentSector => new(Current.Value, Context.MiniSectorSize); + public MiniSector CurrentSector => new(Current, Context.MiniSectorSize); /// - public FatChainEntry Current + public uint Current { get { - if (current.IsFreeOrEndOfChain) + if (index == uint.MaxValue) throw new InvalidOperationException("Enumeration has not started. Call MoveNext."); return current; } @@ -58,15 +54,15 @@ public bool MoveNext() { start = false; index = 0; - current = new(index, startId); + current = startId; } - else if (!current.IsFreeOrEndOfChain) + else if (!SectorType.IsFreeOrEndOfChain(current)) { - uint sectorId = Context.MiniFat[current.Value]; + uint sectorId = Context.MiniFat[current]; if (sectorId == SectorType.EndOfChain) { index = uint.MaxValue; - current = FatChainEntry.Invalid; + current = uint.MaxValue; return false; } @@ -75,14 +71,14 @@ public bool MoveNext() throw new FormatException("Mini FAT chain is corrupt."); index = nextIndex; - current = new(nextIndex, sectorId); + current = sectorId; return true; } - if (current.IsFreeOrEndOfChain) + if (SectorType.IsFreeOrEndOfChain(current)) { index = uint.MaxValue; - current = FatChainEntry.Invalid; + current = uint.MaxValue; return false; } @@ -123,7 +119,7 @@ public long GetLength() return length; } - public void Extend(uint requiredChainLength) + public uint Extend(uint requiredChainLength) { uint chainLength = (uint)GetLength(); if (chainLength >= requiredChainLength) @@ -138,7 +134,7 @@ public void Extend(uint requiredChainLength) bool ok = MoveTo(chainLength - 1); Debug.Assert(ok); - uint lastId = current.Value; + uint lastId = current; ok = miniFatEnumerator.MoveTo(lastId); Debug.Assert(ok); while (chainLength < requiredChainLength) @@ -156,9 +152,10 @@ public void Extend(uint requiredChainLength) #endif length = requiredChainLength; + return startId; } - public void Shrink(uint requiredChainLength) + public uint Shrink(uint requiredChainLength) { uint chainLength = (uint)GetLength(); if (chainLength <= requiredChainLength) @@ -166,7 +163,7 @@ public void Shrink(uint requiredChainLength) Reset(); - uint lastId = current.Value; + uint lastId = current; while (MoveNext()) { if (lastId <= SectorType.Maximum) @@ -177,7 +174,7 @@ public void Shrink(uint requiredChainLength) Context.MiniFat[lastId] = SectorType.Free; } - lastId = current.Value; + lastId = current; } if (lastId <= SectorType.Maximum) @@ -195,6 +192,7 @@ public void Shrink(uint requiredChainLength) #endif length = requiredChainLength; + return startId; } /// @@ -202,8 +200,8 @@ public void Reset() { start = true; index = uint.MaxValue; - current = FatChainEntry.Invalid; + current = uint.MaxValue; } - public override string ToString() => $"Index: {index} Value {current}"; + public override string ToString() => $"Index: {index} Current: {current}"; } diff --git a/OpenMcdf/MiniFatStream.cs b/OpenMcdf/MiniFatStream.cs index 2411feb4..d1c92770 100644 --- a/OpenMcdf/MiniFatStream.cs +++ b/OpenMcdf/MiniFatStream.cs @@ -147,11 +147,10 @@ public override void SetLength(long value) uint requiredChainLength = (uint)((value + Context.MiniSectorSize - 1) / Context.MiniSectorSize); if (value > ChainCapacity) - miniChain.Extend(requiredChainLength); + DirectoryEntry.StartSectorId = miniChain.Extend(requiredChainLength); else if (value <= ChainCapacity - Context.MiniSectorSize) - miniChain.Shrink(requiredChainLength); + DirectoryEntry.StartSectorId = miniChain.Shrink(requiredChainLength); - DirectoryEntry.StartSectorId = miniChain.StartId; DirectoryEntry.StreamLength = value; isDirty = true; } From 94ad220ade54c5449f47e1098f52219415fd3ed0 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 21:38:25 +1300 Subject: [PATCH 110/134] Seal StreamDataProvider --- StructuredStorageExplorer/StreamDataProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StructuredStorageExplorer/StreamDataProvider.cs b/StructuredStorageExplorer/StreamDataProvider.cs index e99c23b7..d2973128 100644 --- a/StructuredStorageExplorer/StreamDataProvider.cs +++ b/StructuredStorageExplorer/StreamDataProvider.cs @@ -3,7 +3,7 @@ namespace StructuredStorageExplorer; -public class StreamDataProvider : IByteProvider +internal sealed class StreamDataProvider : IByteProvider { /// /// Modifying stream From c9b8d2385c2d362857e740e481a27ea224357ec7 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 23:15:32 +1300 Subject: [PATCH 111/134] Fix OLE stack overflow --- OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs b/OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs index 551e55ed..828dbfa6 100644 --- a/OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs +++ b/OpenMcdf.Ole/DocumentSummaryInfoPropertyFactory.cs @@ -11,6 +11,6 @@ protected override ITypedPropertyValue CreateLpstrProperty(VTPropertyType vType, if (propertyIdentifier is 0x0000000C or 0x0000000D) return new VT_Unaligned_LPSTR_Property(vType, codePage, isVariant); - return CreateLpstrProperty(vType, codePage, propertyIdentifier, isVariant); + return base.CreateLpstrProperty(vType, codePage, propertyIdentifier, isVariant); } } From 7ddcf4e484271900e27bfc71305c3c5b34176bf7 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 23:36:06 +1300 Subject: [PATCH 112/134] Fix OLE BinaryReader/Writer usage --- OpenMcdf.Ole/OlePropertiesContainer.cs | 7 ++++--- OpenMcdf.Ole/PropertySetStream.cs | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/OpenMcdf.Ole/OlePropertiesContainer.cs b/OpenMcdf.Ole/OlePropertiesContainer.cs index 371730d0..f21fea91 100644 --- a/OpenMcdf.Ole/OlePropertiesContainer.cs +++ b/OpenMcdf.Ole/OlePropertiesContainer.cs @@ -1,4 +1,6 @@ -namespace OpenMcdf.Ole; +using System.Text; + +namespace OpenMcdf.Ole; public enum ContainerType { @@ -42,8 +44,7 @@ public OlePropertiesContainer(CfbStream cfStream) this.cfStream = cfStream; - cfStream.Position = 0; - using BinaryReader reader = new(cfStream); + using BinaryReader reader = new(cfStream, Encoding.Unicode, true); pStream.Read(reader); if (pStream.FMTID0 == FormatIdentifiers.SummaryInformation) diff --git a/OpenMcdf.Ole/PropertySetStream.cs b/OpenMcdf.Ole/PropertySetStream.cs index 2f3de824..57caf285 100644 --- a/OpenMcdf.Ole/PropertySetStream.cs +++ b/OpenMcdf.Ole/PropertySetStream.cs @@ -28,6 +28,8 @@ public PropertySetStream() public void Read(BinaryReader br) { + br.BaseStream.Position = 0; + ByteOrder = br.ReadUInt16(); Version = br.ReadUInt16(); SystemIdentifier = br.ReadUInt32(); @@ -113,6 +115,8 @@ public void Read(BinaryReader br) public void Write(BinaryWriter bw) { + bw.BaseStream.Position = 0; + OffsetContainer oc0 = new(); OffsetContainer oc1 = new(); From 502926cfeb90e0a103e2e1d1a8054e738618a072 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 23:45:59 +1300 Subject: [PATCH 113/134] Fix encoding provider registration --- OpenMcdf.Ole/CodePages.cs | 4 +++- OpenMcdf.Ole/PropertyFactory.cs | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenMcdf.Ole/CodePages.cs b/OpenMcdf.Ole/CodePages.cs index 1f5a22c7..fdf34886 100644 --- a/OpenMcdf.Ole/CodePages.cs +++ b/OpenMcdf.Ole/CodePages.cs @@ -1,4 +1,6 @@ -namespace OpenMcdf.Ole; +using System.Text; + +namespace OpenMcdf.Ole; internal static class CodePages { diff --git a/OpenMcdf.Ole/PropertyFactory.cs b/OpenMcdf.Ole/PropertyFactory.cs index f8a699f1..9a1df14a 100644 --- a/OpenMcdf.Ole/PropertyFactory.cs +++ b/OpenMcdf.Ole/PropertyFactory.cs @@ -6,9 +6,7 @@ internal abstract class PropertyFactory { static PropertyFactory() { -#if NETSTANDARD2_0_OR_GREATER Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); -#endif } public ITypedPropertyValue CreateProperty(VTPropertyType vType, int codePage, uint propertyIdentifier, bool isVariant = false) From f454dac12c1ce2c1d33a8519bebb3510d4fe3080 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Tue, 12 Nov 2024 23:11:17 +1300 Subject: [PATCH 114/134] Add OLE tests --- OpenMcdf.Ole.Tests/2custom.doc | Bin 0 -> 27136 bytes OpenMcdf.Ole.Tests/CLSIDPropertyTest.file | Bin 0 -> 245760 bytes OpenMcdf.Ole.Tests/Issue134.cfs | Bin 0 -> 2560 bytes .../OlePropertiesExtensionsTests.cs | 440 ++++++++++++++++++ OpenMcdf.Ole.Tests/OpenMcdf.Ole.Tests.csproj | 54 +++ OpenMcdf.Ole.Tests/SampleWorkBook_bug98.xls | Bin 0 -> 19456 bytes OpenMcdf.Ole.Tests/_Test.ppt | Bin 0 -> 174592 bytes OpenMcdf.Ole.Tests/english.presets.doc | Bin 0 -> 9728 bytes OpenMcdf.Ole.Tests/report.xls | Bin 0 -> 16896 bytes OpenMcdf.Ole.Tests/winUnicodeDictionary.doc | Bin 0 -> 26624 bytes OpenMcdf.Ole.Tests/wstr_presets.doc | Bin 0 -> 27136 bytes OpenMcdf.Tests/OpenMcdf.Tests.csproj | 2 +- OpenMcdf.sln | 6 + OpenMcdf/RootStorage.cs | 4 +- 14 files changed, 504 insertions(+), 2 deletions(-) create mode 100644 OpenMcdf.Ole.Tests/2custom.doc create mode 100644 OpenMcdf.Ole.Tests/CLSIDPropertyTest.file create mode 100644 OpenMcdf.Ole.Tests/Issue134.cfs create mode 100644 OpenMcdf.Ole.Tests/OlePropertiesExtensionsTests.cs create mode 100644 OpenMcdf.Ole.Tests/OpenMcdf.Ole.Tests.csproj create mode 100644 OpenMcdf.Ole.Tests/SampleWorkBook_bug98.xls create mode 100644 OpenMcdf.Ole.Tests/_Test.ppt create mode 100644 OpenMcdf.Ole.Tests/english.presets.doc create mode 100644 OpenMcdf.Ole.Tests/report.xls create mode 100644 OpenMcdf.Ole.Tests/winUnicodeDictionary.doc create mode 100644 OpenMcdf.Ole.Tests/wstr_presets.doc diff --git a/OpenMcdf.Ole.Tests/2custom.doc b/OpenMcdf.Ole.Tests/2custom.doc new file mode 100644 index 0000000000000000000000000000000000000000..0d53d3a1ae713c9cf17f3e8cfe094ced38e54ee0 GIT binary patch literal 27136 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 RZGl*zM3RiRYX5WB{{xX)F>C+; literal 0 HcmV?d00001 diff --git a/OpenMcdf.Ole.Tests/CLSIDPropertyTest.file b/OpenMcdf.Ole.Tests/CLSIDPropertyTest.file new file mode 100644 index 0000000000000000000000000000000000000000..27b609bf61b6e6e31b5186e8d59f95ef3b3ff142 GIT binary patch literal 245760 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 CT~pZr literal 0 HcmV?d00001 diff --git a/OpenMcdf.Ole.Tests/Issue134.cfs b/OpenMcdf.Ole.Tests/Issue134.cfs new file mode 100644 index 0000000000000000000000000000000000000000..f53f9b4f799023a10a089ad3361fbc38027fb8d6 GIT binary patch literal 2560 zcmeHH%}N4M7(JsI8U$M4!i_#cQAjIsQDB=0@c}e(QB(t(LA#dXQ%on_m$ z)Z)oTv_&sqF>`y&4tKfcKN1=D&=R~ zqC=SU6zMMe#5Fxl+|i@yMrb(k@BR>4g^0rS0_r>UnEFS364gMs@?YgdG@WU9 z;mp-GURVYGDT{>Ry_h${{p>Eg&X=6j9ZvEVj&XuhKU +/// Summary description for UnitTest1 +/// +[TestClass] +public class OlePropertiesExtensionsTests +{ + [TestMethod] + public void ReadSummaryInformation() + { + using var cf = RootStorage.OpenRead("_Test.ppt"); + using CfbStream stream = cf.OpenStream("\u0005SummaryInformation"); + OlePropertiesContainer co = new(stream); + + foreach (OleProperty p in co.Properties) + { + Debug.WriteLine(p); + } + } + + [TestMethod] + public void ReadDocumentSummaryInformation() + { + using var cf = RootStorage.OpenRead("_Test.ppt"); + using CfbStream stream = cf.OpenStream("\u0005DocumentSummaryInformation"); + OlePropertiesContainer co = new(stream); + + foreach (OleProperty p in co.Properties) + { + Debug.WriteLine(p); + } + } + + [TestMethod] + public void ReadThenWriteDocumentSummaryInformation() + { + using var cf = RootStorage.OpenRead("_Test.ppt"); + using CfbStream stream = cf.OpenStream("\u0005DocumentSummaryInformation"); + OlePropertiesContainer co = new(stream); + + using var cf2 = RootStorage.CreateInMemory(); + using CfbStream stream2 = cf2.CreateStream("\u0005DocumentSummaryInformation"); + co.Save(stream2); + } + + // Modify some document summary information properties, save to a file, and then validate the expected results + [TestMethod] + public void ModifyDocumentSummaryInformation() + { + using MemoryStream modifiedStream = new(); + using (FileStream stream = File.OpenRead("_Test.ppt")) + stream.CopyTo(modifiedStream); + + // Verify initial properties, and then create a modified document + using (var cf = RootStorage.Open(modifiedStream, StorageModeFlags.LeaveOpen)) + { + using CfbStream dsiStream = cf.OpenStream("\u0005DocumentSummaryInformation"); + OlePropertiesContainer co = new(dsiStream); + + // The company property should exist but be empty + OleProperty companyProperty = co.Properties.First(prop => prop.PropertyName == "PIDDSI_COMPANY"); + Assert.AreEqual("", companyProperty.Value); + + // As a sanity check, check that the value of a property that we don't change remains the same + OleProperty formatProperty = co.Properties.First(prop => prop.PropertyName == "PIDDSI_PRESFORMAT"); + Assert.AreEqual("A4 Paper (210x297 mm)", formatProperty.Value); + + // The manager property shouldn't exist, and we'll add it + Assert.IsFalse(co.Properties.Any(prop => prop.PropertyName == "PIDDSI_MANAGER")); + + OleProperty managerProp = co.CreateProperty(VTPropertyType.VT_LPSTR, 0x0000000E, "PIDDSI_MANAGER"); + co.Add(managerProp); + + companyProperty.Value = "My Company"; + managerProp.Value = "The Boss"; + + co.Save(dsiStream); + } + + using (var cf = RootStorage.Open(modifiedStream)) + { + using CfbStream stream = cf.OpenStream("\u0005DocumentSummaryInformation"); + OlePropertiesContainer co = new(stream); + + OleProperty companyProperty = co.Properties.First(prop => prop.PropertyName == "PIDDSI_COMPANY"); + Assert.AreEqual("My Company", companyProperty.Value); + + OleProperty formatProperty = co.Properties.First(prop => prop.PropertyName == "PIDDSI_PRESFORMAT"); + Assert.AreEqual("A4 Paper (210x297 mm)", formatProperty.Value); + + OleProperty managerProperty = co.Properties.First(prop => prop.PropertyName == "PIDDSI_MANAGER"); + Assert.AreEqual("The Boss", managerProperty.Value); + } + } + + [TestMethod] + public void ReadSummaryInformationUtf8() + { + // Regression test for #33 + using var cf = RootStorage.Open("wstr_presets.doc", FileMode.Open); + using CfbStream stream = cf.OpenStream("\u0005SummaryInformation"); + OlePropertiesContainer co = new(stream); + + foreach (OleProperty p in co.Properties) + { + Debug.WriteLine(p); + } + + using CfbStream stream2 = cf.OpenStream("\u0005DocumentSummaryInformation"); + OlePropertiesContainer co2 = new(stream2); + + foreach (OleProperty p in co2.Properties) + { + Debug.WriteLine(p); + } + } + + [TestMethod] + public void ReadSummaryInformationUtf8Part2() + { + // Regression test for #34 + using var cf = RootStorage.OpenRead("2custom.doc"); + using CfbStream stream = cf.OpenStream("\u0005SummaryInformation"); + OlePropertiesContainer co = new(stream); + + foreach (OleProperty p in co.Properties) + { + Debug.WriteLine(p); + } + + using CfbStream stream2 = cf.OpenStream("\u0005DocumentSummaryInformation"); + OlePropertiesContainer co2 = new(stream2); + + foreach (OleProperty p in co2.Properties) + { + Debug.WriteLine(p); + } + + if (co2.UserDefinedProperties is not null) + { + foreach (OleProperty p in co2.UserDefinedProperties.Properties) + { + Debug.WriteLine(p); + } + } + } + + [TestMethod] + public void SummaryInformationReadLpwstring() + { + using var cf = RootStorage.OpenRead("english.presets.doc"); + using CfbStream stream = cf.OpenStream("\u0005SummaryInformation"); + OlePropertiesContainer co = new(stream); + + foreach (OleProperty p in co.Properties) + { + Debug.WriteLine(p); + } + } + + // Test that we can modify an LPWSTR property, and the value is null terminated as required + [TestMethod] + public void SummaryInformationModifyLpwstring() + { + using MemoryStream modifiedStream = new(); + using (FileStream stream = File.OpenRead("wstr_presets.doc")) + stream.CopyTo(modifiedStream); + + // Modify some LPWSTR properties, and save to a new file + using (var cf = RootStorage.Open(modifiedStream, StorageModeFlags.LeaveOpen)) + { + using CfbStream dsiStream = cf.OpenStream("\u0005SummaryInformation"); + OlePropertiesContainer co = new(dsiStream); + + OleProperty authorProperty = co.Properties.First(prop => prop.PropertyName == "PIDSI_AUTHOR"); + Assert.AreEqual(VTPropertyType.VT_LPWSTR, authorProperty.VTType); + Assert.AreEqual("zkyiqpqoroxnbdwhnjfqroxlgylpbgcwuhjfifpkvycugvuecoputqgknnbs", authorProperty.Value); + + OleProperty keyWordsProperty = co.Properties.First(prop => prop.PropertyName == "PIDSI_KEYWORDS"); + Assert.AreEqual(VTPropertyType.VT_LPWSTR, keyWordsProperty.VTType); + Assert.AreEqual("abcdefghijk", keyWordsProperty.Value); + + authorProperty.Value = "ABC"; + keyWordsProperty.Value = ""; + co.Save(dsiStream); + } + + // Open the new file and check for the expected values + using (var cf = RootStorage.Open(modifiedStream)) + { + using CfbStream stream = cf.OpenStream("\u0005SummaryInformation"); + OlePropertiesContainer co = new(stream); + + OleProperty authorProperty = co.Properties.First(prop => prop.PropertyName == "PIDSI_AUTHOR"); + Assert.AreEqual(VTPropertyType.VT_LPWSTR, authorProperty.VTType); + Assert.AreEqual("ABC", authorProperty.Value); + + OleProperty keyWordsProperty = co.Properties.First(prop => prop.PropertyName == "PIDSI_KEYWORDS"); + Assert.AreEqual(VTPropertyType.VT_LPWSTR, keyWordsProperty.VTType); + Assert.AreEqual("", keyWordsProperty.Value); + } + } + + // winUnicodeDictionary.doc contains a UserProperties section with the CP_WINUNICODE codepage, and LPWSTR string properties + [TestMethod] + public void TestReadUnicodeUserPropertiesDictionary() + { + using var cf = RootStorage.OpenRead("winUnicodeDictionary.doc"); + CfbStream dsiStream = cf.OpenStream("\u0005DocumentSummaryInformation"); + OlePropertiesContainer co = new(dsiStream); + OlePropertiesContainer? userProps = co.UserDefinedProperties; + + // CodePage should be CP_WINUNICODE (1200) + Assert.AreEqual(1200, userProps.Context.CodePage); + + // There should be 5 property names present, and 6 properties (the properties include the code page) + Assert.AreEqual(5, userProps.PropertyNames.Count); + Assert.AreEqual(6, userProps.Properties.Count); + + // Check for expected names and values + OleProperty[] propArray = userProps.Properties.ToArray(); + + // CodePage prop + Assert.AreEqual(1u, propArray[0].PropertyIdentifier); + Assert.AreEqual("0x00000001", propArray[0].PropertyName); + Assert.AreEqual((short)1200, propArray[0].Value); + + // String properties + Assert.AreEqual("A", propArray[1].PropertyName); + Assert.AreEqual("", propArray[1].Value); + Assert.AreEqual("AB", propArray[2].PropertyName); + Assert.AreEqual("X", propArray[2].Value); + Assert.AreEqual("ABC", propArray[3].PropertyName); + Assert.AreEqual("XY", propArray[3].Value); + Assert.AreEqual("ABCD", propArray[4].PropertyName); + Assert.AreEqual("XYZ", propArray[4].Value); + Assert.AreEqual("ABCDE", propArray[5].PropertyName); + Assert.AreEqual("XYZ!", propArray[5].Value); + } + + // Test that we can add user properties of various types and then read them back + [TestMethod] + public void AddDocumentSummaryInformationCustomInfo() + { + using MemoryStream modifiedStream = new(); + using (FileStream stream = File.OpenRead("english.presets.doc")) + stream.CopyTo(modifiedStream); + + // Test value for a VT_FILETIME property + DateTime testNow = DateTime.UtcNow; + + // english.presets.doc has a user defined property section, but no properties other than the codepage + using (var cf = RootStorage.Open(modifiedStream, StorageModeFlags.LeaveOpen)) + { + using CfbStream dsiStream = cf.OpenStream("\u0005DocumentSummaryInformation"); + OlePropertiesContainer co = new(dsiStream); + OlePropertiesContainer? userProperties = co.UserDefinedProperties; + + userProperties.PropertyNames[2] = "StringProperty"; + userProperties.PropertyNames[3] = "BooleanProperty"; + userProperties.PropertyNames[4] = "IntegerProperty"; + userProperties.PropertyNames[5] = "DateProperty"; + userProperties.PropertyNames[6] = "DoubleProperty"; + + OleProperty stringProperty = co.CreateProperty(VTPropertyType.VT_LPSTR, 2); + stringProperty.Value = "Hello"; + userProperties.Add(stringProperty); + + OleProperty booleanProperty = co.CreateProperty(VTPropertyType.VT_BOOL, 3); + booleanProperty.Value = true; + userProperties.Add(booleanProperty); + + OleProperty integerProperty = co.CreateProperty(VTPropertyType.VT_I4, 4); + integerProperty.Value = 3456; + userProperties.Add(integerProperty); + + OleProperty timeProperty = co.CreateProperty(VTPropertyType.VT_FILETIME, 5); + timeProperty.Value = testNow; + userProperties.Add(timeProperty); + + OleProperty doubleProperty = co.CreateProperty(VTPropertyType.VT_R8, 6); + doubleProperty.Value = 1.234567d; + userProperties.Add(doubleProperty); + + co.Save(dsiStream); + } + + using (var cf = RootStorage.Open(modifiedStream)) + { + using CfbStream stream = cf.OpenStream("\u0005DocumentSummaryInformation"); + OlePropertiesContainer co = new(stream); + OleProperty[] propArray = co.UserDefinedProperties.Properties.ToArray(); + Assert.AreEqual(6, propArray.Length); + + // CodePage prop + Assert.AreEqual(1u, propArray[0].PropertyIdentifier); + Assert.AreEqual("0x00000001", propArray[0].PropertyName); + Assert.AreEqual((short)-535, propArray[0].Value); + + // User properties + Assert.AreEqual("StringProperty", propArray[1].PropertyName); + Assert.AreEqual("Hello", propArray[1].Value); + Assert.AreEqual(VTPropertyType.VT_LPSTR, propArray[1].VTType); + Assert.AreEqual("BooleanProperty", propArray[2].PropertyName); + Assert.AreEqual(true, propArray[2].Value); + Assert.AreEqual(VTPropertyType.VT_BOOL, propArray[2].VTType); + Assert.AreEqual("IntegerProperty", propArray[3].PropertyName); + Assert.AreEqual(3456, propArray[3].Value); + Assert.AreEqual(VTPropertyType.VT_I4, propArray[3].VTType); + Assert.AreEqual("DateProperty", propArray[4].PropertyName); + Assert.AreEqual(testNow, propArray[4].Value); + Assert.AreEqual(VTPropertyType.VT_FILETIME, propArray[4].VTType); + Assert.AreEqual("DoubleProperty", propArray[5].PropertyName); + Assert.AreEqual(1.234567d, propArray[5].Value); + Assert.AreEqual(VTPropertyType.VT_R8, propArray[5].VTType); + } + } + + // Try to read a document which contains Vector/String properties + // refs https://github.com/ironfede/openmcdf/issues/98 + [TestMethod] + public void ReadLpwstringVector() + { + using var cf = RootStorage.OpenRead("SampleWorkBook_bug98.xls"); + using CfbStream stream = cf.OpenStream("\u0005DocumentSummaryInformation"); + OlePropertiesContainer co = new(stream); + + OleProperty docPartsProperty = co.Properties.FirstOrDefault(property => property.PropertyIdentifier == 13); //13 == PIDDSI_DOCPARTS + + var docPartsValues = docPartsProperty.Value as IList; + Assert.AreEqual(3, docPartsValues.Count); + Assert.AreEqual("Sheet1", docPartsValues[0]); + Assert.AreEqual("Sheet2", docPartsValues[1]); + Assert.AreEqual("Sheet3", docPartsValues[2]); + } + + [TestMethod] + public void ReadClsidProperty() + { + Guid guid = new("15891a95-bf6e-4409-b7d0-3a31c391fa31"); + using var cf = RootStorage.OpenRead("CLSIDPropertyTest.file"); + using CfbStream stream = cf.OpenStream("\u0005C3teagxwOttdbfkuIaamtae3Ie"); + OlePropertiesContainer co = new(stream); + OleProperty clsidProp = co.Properties.First(x => x.PropertyName == "DocumentID"); + Assert.AreEqual(guid, clsidProp.Value); + } + + // The test file 'report.xls' contains a DocumentSummaryInfo section, but no user defined properties. + // This tests adding a new user defined properties section to the existing DocumentSummaryInfo. + [TestMethod] + public void AddUserDefinedPropertiesSection() + { + using MemoryStream modifiedStream = new(); + using (FileStream stream = File.OpenRead("report.xls")) + stream.CopyTo(modifiedStream); + + using (var cf = RootStorage.Open(modifiedStream, StorageModeFlags.LeaveOpen)) + { + using CfbStream dsiStream = cf.OpenStream("\u0005DocumentSummaryInformation"); + OlePropertiesContainer co = new(dsiStream); + + Assert.IsNull(co.UserDefinedProperties); + + OlePropertiesContainer newUserDefinedProperties = co.CreateUserDefinedProperties(65001); // 65001 - UTF-8 + + newUserDefinedProperties.PropertyNames[2] = "MyCustomProperty"; + + OleProperty CreateProperty = co.CreateProperty(VTPropertyType.VT_LPSTR, 2); + CreateProperty.Value = "Testing"; + newUserDefinedProperties.Add(CreateProperty); + + co.Save(dsiStream); + } + + using (var cf = RootStorage.Open(modifiedStream)) + { + using CfbStream stream = cf.OpenStream("\u0005DocumentSummaryInformation"); + OlePropertiesContainer co = new(stream); + + // User defined properties should be present now + Assert.IsNotNull(co.UserDefinedProperties); + Assert.AreEqual(65001, co.UserDefinedProperties.Context.CodePage); + + // And the expected properties should the there + OleProperty[] propArray = co.UserDefinedProperties.Properties.ToArray(); + Assert.AreEqual(propArray.Length, 2); + + // CodePage prop + Assert.AreEqual(1u, propArray[0].PropertyIdentifier); + Assert.AreEqual("0x00000001", propArray[0].PropertyName); + Assert.AreEqual((short)-535, propArray[0].Value); + + // User properties + Assert.AreEqual("MyCustomProperty", propArray[1].PropertyName); + Assert.AreEqual("Testing", propArray[1].Value); + Assert.AreEqual(VTPropertyType.VT_LPSTR, propArray[1].VTType); + } + } + + // A test for the issue described in https://github.com/ironfede/openmcdf/issues/134 where modifying an AppSpecific stream + // removes any already-existing Dictionary property + [TestMethod] + public void TestRetainDictionaryPropertyInAppSpecificStreams() + { + using MemoryStream modifiedStream = new(); + using (FileStream stream = File.OpenRead("Issue134.cfs")) + stream.CopyTo(modifiedStream); + + Dictionary expectedPropertyNames = new() + { + [2] = "Document Number", + [3] = "Revision", + [4] = "Project Name" + }; + + using (var cf = RootStorage.Open(modifiedStream, StorageModeFlags.LeaveOpen)) + { + using CfbStream testStream = cf.OpenStream("Issue134"); + OlePropertiesContainer co = new(testStream); + + CollectionAssert.AreEqual(expectedPropertyNames, co.PropertyNames); + + // Write test file + co.Save(testStream); + } + + // Open test file, and check that the property names are still as expected. + using (var cf = RootStorage.Open(modifiedStream)) + { + using CfbStream testStream = cf.OpenStream("Issue134"); + OlePropertiesContainer co = new(testStream); + + CollectionAssert.AreEqual(expectedPropertyNames, co.PropertyNames); + } + } +} diff --git a/OpenMcdf.Ole.Tests/OpenMcdf.Ole.Tests.csproj b/OpenMcdf.Ole.Tests/OpenMcdf.Ole.Tests.csproj new file mode 100644 index 00000000..59b58f11 --- /dev/null +++ b/OpenMcdf.Ole.Tests/OpenMcdf.Ole.Tests.csproj @@ -0,0 +1,54 @@ + + + + net48;net8.0 + Exe + 12.0 + enable + enable + + false + true + true + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + Always + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + Always + + + PreserveNewest + + + + diff --git a/OpenMcdf.Ole.Tests/SampleWorkBook_bug98.xls b/OpenMcdf.Ole.Tests/SampleWorkBook_bug98.xls new file mode 100644 index 0000000000000000000000000000000000000000..063c04f5b831dbef016db7bae0fb5047488121a0 GIT binary patch literal 19456 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 literal 0 HcmV?d00001 diff --git a/OpenMcdf.Ole.Tests/_Test.ppt b/OpenMcdf.Ole.Tests/_Test.ppt new file mode 100644 index 0000000000000000000000000000000000000000..4b5a7692c487ed3675640407315cdb4240fdb68a GIT binary patch literal 174592 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 sTd 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 literal 0 HcmV?d00001 diff --git a/OpenMcdf.Ole.Tests/english.presets.doc b/OpenMcdf.Ole.Tests/english.presets.doc new file mode 100644 index 0000000000000000000000000000000000000000..3fdf7297b22e4ab8f3bbb14f380655f6ad1d24a0 GIT binary patch literal 9728 zcmeI1Pi$009LMLCZMy}8Qc6WYSxN;&ETxDbpq2tHR7wj)@h`I5UFhoWzPh_j!4vWS zK_mW2yqJiJClgaOp4E%tAd!O}ys0-650w}b67Bl=&f6zTk?pnu0kgCDyqPyU^PAuN z=FgiyXTF{D<9nYh_{ofOo6Iybma8#U1@R8yTK}Cu1!2XF<#IVcnqb*oM;7=iudCWH zKqFRy+PoTqPy^Fp2F!#TU>3{<<(ZTBucIoLRV}cVM_(&s(4rn-$vMpTDOv{`D=;_^*SxP!IE<0d9o(a1$(mg|G;2 zhFf4UgkT9Yg352gzZI6ja##V1Ux|M^tb#kB8CF9JtbtZo3wOdgxC_?92DlqG!Y0@Z z_rSeyAGE<1Xosz^4YorEbixkU30-hM?1FCC4L#5cdtfj0!9I8Z9)$hy5IhWzz@zXO zJPrro2{;I0P#yd6qY%r#pKsAC8*(Up8WX%qsjATz-Iy=!({!l)y5s$6H{%}4hW5MZ z=pEbKr-qWTRMvhIy89Hj&F#0V0)?A-vHv%3Fu&jZ>C>K|Vo%3gMifg@^uIcK+H_VS zkJ>Mimvn$-%5S*}gU`2YH}hNCB(ZdYclF1*Xuac@HBHXklXTjXr_7cnv$DydPqnW! zJzW8_2ex)KnBVVCy5!cgIfw~o@7xg?9 z>q$Mf*fEWc`f)YAI7aHIi6i?kF*h(~=bjv;;zvvswVscRN@P@0*}FlOQ=LoPM*T;30K z1sQjZ$ZdDph^?kJVKYdIxUIePt4G=qtKUrYt3$8(=B}WltYLzW2c2cMhI{d~V%bT} zqEH=CZ z`Qa*pxi&?3BMD!Vq?{s{Fl?XEqN=muxTE-q+&&DGq;eSyyBmKPUmtCC=oFdPA;&MQ zn5;e;ENLR-)G%1PthCa%(Ba~gq&#tHtoeQdrzVe}sk*n{tRW^y#l#r|nkb9cX?=;> zsp}+cX_Cw&tbD$~i)eW%4Q0^``3h^@Syh^)y3wGY8oEeusig+_!|12sC*9VSEJs=# z&f9!l;5dV8V%JUj^SRpT{!Z{!=I=9fjR7lQhH;~xb_$w5|C%t{q%}EKATpQJqOZ^enZ$?b>k&KM zYph=!Q<(lTg~!-&IRDJH=Y^`H{`kVXXV3Kv)PD4O(A>WKvmaFB&HOPFU;ht)j5i3= z;CWz%H}8OE^%2mFehRALSJ2e0%q*GAOnmK&=jUbe*|GcM7JSM5g+|a?S84Xevx!(o zJeG)haoR`s4Mm=e^=I?3UcBTF4#y&yY%ERC=GT7v{@BrB7Fd~1cY9iEM9I~fx^F0% z3}-_-Q>2e&vhmd6RUtPWO1XS;l}2^l?WU9AM03>5CT*N_(|WlLjvYPw8tU3Go^@?{ zuJ|DF?&vr68(o|3`8l)lJ0-iw{raKfbcbp%+i2Yv-oIYQ;zOse<1Di^>?Xpg4PNX8 z`D+3Vrd5^8JLy%-<#MxayGRj*)i!>3e7=`$#G$-M-yff<@Lg%@3nL~7KSO$1_r<*L zC;y!HgYaM78_1Z7n0a*F2pNazvMdVC3BuZymEHf-0&mg2>W5d`RhS>Hc9G@s$`&YF z;F_{P*sgf4DV55~Tulq)*Lhd7$Z}cNj0GJ3GIFA_5tbf5QP~8oz1+?mA8=X*rO#U4R?b-`UzVhnsM_bEXpu6gW;5+^A@6EMx-p9r{mX2rGJlp-T z_Um2lKq_^#-OX?wsC~4~2inu3SBf!bepSfyt5OL8bT!Lij;-E>MZex(B~TqOtdL)c%}3A+2&UCJR^#Je%`kMF&-|Mpx# z`Ai7*ET+`*my$lA&Vt)-%ZhS8=5l;4O=4)E`=Zj^f+^JhHbnd9PL&sxJUM%8m_qpx X%0E?F^-4-*8$SOiC4=sp{d9i=@PHCg literal 0 HcmV?d00001 diff --git a/OpenMcdf.Ole.Tests/report.xls b/OpenMcdf.Ole.Tests/report.xls new file mode 100644 index 0000000000000000000000000000000000000000..7b667a5077161aeba1d12f02944707b21581b2f7 GIT binary patch literal 16896 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 izF0CE{{zz*6^j92|LHBJV3B1?{{I1vYc3lA literal 0 HcmV?d00001 diff --git a/OpenMcdf.Ole.Tests/winUnicodeDictionary.doc b/OpenMcdf.Ole.Tests/winUnicodeDictionary.doc new file mode 100644 index 0000000000000000000000000000000000000000..2ba5153c78913c14c548d70d188ceae510d70845 GIT binary patch literal 26624 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 literal 0 HcmV?d00001 diff --git a/OpenMcdf.Ole.Tests/wstr_presets.doc b/OpenMcdf.Ole.Tests/wstr_presets.doc new file mode 100644 index 0000000000000000000000000000000000000000..884da6af5fb42bd42f8f7b62213d15d1c4008337 GIT binary patch literal 27136 zcmeHQ2|Sg{`=4{{yCezK2}O%7g}MnvS+azbRtE=1oMTI?QqjWgrp;|pN!k@60>z%)B#4+0{-} z+YV|yAYyJfks$9|NkRl_ zr?C?4$Za*>nNAcK?%xjl>_B<3fvp9fl6R6Ynh@>2

J_Lj4| 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 QxS(;mFa>@2|3=_{03O{PXaE2J literal 0 HcmV?d00001 diff --git a/OpenMcdf.Tests/OpenMcdf.Tests.csproj b/OpenMcdf.Tests/OpenMcdf.Tests.csproj index 2736a5b8..dcba798f 100644 --- a/OpenMcdf.Tests/OpenMcdf.Tests.csproj +++ b/OpenMcdf.Tests/OpenMcdf.Tests.csproj @@ -3,7 +3,7 @@ net48;net8.0-windows Exe - 11.0 + 12.0 enable enable diff --git a/OpenMcdf.sln b/OpenMcdf.sln index 0e7f8a06..d8639c4d 100644 --- a/OpenMcdf.sln +++ b/OpenMcdf.sln @@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Ole", "OpenMcdf.Ol EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructuredStorageExplorer", "StructuredStorageExplorer\StructuredStorageExplorer.csproj", "{D5DDCC19-80C4-40D2-AEBF-2DA1CCB1D543}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenMcdf.Ole.Tests", "OpenMcdf.Ole.Tests\OpenMcdf.Ole.Tests.csproj", "{34F153C4-3EFA-4D6E-B860-AEE300CCCF98}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -58,6 +60,10 @@ Global {D5DDCC19-80C4-40D2-AEBF-2DA1CCB1D543}.Debug|Any CPU.Build.0 = Debug|Any CPU {D5DDCC19-80C4-40D2-AEBF-2DA1CCB1D543}.Release|Any CPU.ActiveCfg = Release|Any CPU {D5DDCC19-80C4-40D2-AEBF-2DA1CCB1D543}.Release|Any CPU.Build.0 = Release|Any CPU + {34F153C4-3EFA-4D6E-B860-AEE300CCCF98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34F153C4-3EFA-4D6E-B860-AEE300CCCF98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34F153C4-3EFA-4D6E-B860-AEE300CCCF98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34F153C4-3EFA-4D6E-B860-AEE300CCCF98}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/OpenMcdf/RootStorage.cs b/OpenMcdf/RootStorage.cs index 86e2c3fd..e6a45cd4 100644 --- a/OpenMcdf/RootStorage.cs +++ b/OpenMcdf/RootStorage.cs @@ -58,6 +58,8 @@ public static RootStorage Create(Stream stream, Version version = Version.V3, St return new RootStorage(rootContextSite, flags); } + public static RootStorage CreateInMemory(Version version = Version.V3) => Create(new MemoryStream(), version); + public static RootStorage Open(string fileName, FileMode mode, StorageModeFlags flags = StorageModeFlags.None) { ThrowIfLeaveOpen(flags); @@ -91,7 +93,7 @@ public static RootStorage Open(Stream stream, StorageModeFlags flags = StorageMo this.storageModeFlags = storageModeFlags; } - public void Dispose() => Context?.Dispose(); + public void Dispose() => Context.Dispose(); public void Flush(bool consolidate = false) { From f0069e828543f0acb12573610dc72b129a03bfda Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 13 Nov 2024 21:28:06 +1300 Subject: [PATCH 115/134] Fix NRE in Structured Storage Explorer --- StructuredStorageExplorer/MainForm.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/StructuredStorageExplorer/MainForm.cs b/StructuredStorageExplorer/MainForm.cs index 3ba8ec50..1532099a 100644 --- a/StructuredStorageExplorer/MainForm.cs +++ b/StructuredStorageExplorer/MainForm.cs @@ -321,8 +321,8 @@ private void openFileMenuItem_Click(object sender, EventArgs e) private void treeView1_MouseUp(object sender, MouseEventArgs e) { - TreeNode n = treeView1.GetNodeAt(e.X, e.Y); - if (n.Tag is not NodeSelection nodeSelection) + TreeNode? n = treeView1.GetNodeAt(e.X, e.Y); + if (n?.Tag is not NodeSelection nodeSelection) { addStorageStripMenuItem1.Enabled = true; addStreamToolStripMenuItem.Enabled = true; From ecd15e96ffb29db96a15587930855c716b07f954 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Wed, 13 Nov 2024 21:28:27 +1300 Subject: [PATCH 116/134] Add path to EntryInfo --- OpenMcdf/CfbStream.cs | 6 ++++-- OpenMcdf/DirectoryEntry.cs | 14 +++++++++++++- OpenMcdf/EntryInfo.cs | 1 + OpenMcdf/RootStorage.cs | 2 +- OpenMcdf/Storage.cs | 23 +++++++++++++---------- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/OpenMcdf/CfbStream.cs b/OpenMcdf/CfbStream.cs index e4159fc8..e355aec4 100644 --- a/OpenMcdf/CfbStream.cs +++ b/OpenMcdf/CfbStream.cs @@ -9,10 +9,11 @@ public sealed class CfbStream : Stream private readonly DirectoryEntry directoryEntry; private Stream stream; - internal CfbStream(RootContextSite rootContextSite, DirectoryEntry directoryEntry) + internal CfbStream(RootContextSite rootContextSite, DirectoryEntry directoryEntry, Storage parent) { this.rootContextSite = rootContextSite; this.directoryEntry = directoryEntry; + Parent = parent; stream = directoryEntry.StreamLength < Header.MiniStreamCutoffSize ? new MiniFatStream(rootContextSite, directoryEntry) : new FatStream(rootContextSite, directoryEntry); @@ -24,8 +25,9 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + public Storage Parent { get; } - public EntryInfo EntryInfo => directoryEntry.ToEntryInfo(); + public EntryInfo EntryInfo => directoryEntry.ToEntryInfo(Parent); public override bool CanRead => stream.CanRead; diff --git a/OpenMcdf/DirectoryEntry.cs b/OpenMcdf/DirectoryEntry.cs index 6eac4af8..65dd92fb 100644 --- a/OpenMcdf/DirectoryEntry.cs +++ b/OpenMcdf/DirectoryEntry.cs @@ -226,7 +226,19 @@ public void Recycle(StorageType storageType, string name) _ => throw new InvalidOperationException("Invalid storage type.") }; - public EntryInfo ToEntryInfo() => new(EntryType, NameString, StreamLength, CLSID, CreationTime, ModifiedTime); + public EntryInfo ToEntryInfo(Storage? parent) + { + StringBuilder path = new(); + path.Append('/'); + while (parent is not null) + { + path.Insert(0, parent.DirectoryEntry.NameString); + path.Insert(0, '/'); + parent = parent.Parent; + } + + return new(EntryType, path.ToString(), NameString, StreamLength, CLSID, CreationTime, ModifiedTime); + } public override string ToString() => $"{Id}: \"{NameString}\""; diff --git a/OpenMcdf/EntryInfo.cs b/OpenMcdf/EntryInfo.cs index d88809d4..cad3c451 100644 --- a/OpenMcdf/EntryInfo.cs +++ b/OpenMcdf/EntryInfo.cs @@ -11,6 +11,7 @@ public enum EntryType ///

public readonly record struct EntryInfo( EntryType Type, + string Path, string Name, long Length, Guid CLSID, diff --git a/OpenMcdf/RootStorage.cs b/OpenMcdf/RootStorage.cs index e6a45cd4..a7a32996 100644 --- a/OpenMcdf/RootStorage.cs +++ b/OpenMcdf/RootStorage.cs @@ -88,7 +88,7 @@ public static RootStorage Open(Stream stream, StorageModeFlags flags = StorageMo } RootStorage(RootContextSite rootContextSite, StorageModeFlags storageModeFlags) - : base(rootContextSite, rootContextSite.Context.RootEntry) + : base(rootContextSite, rootContextSite.Context.RootEntry, null) { this.storageModeFlags = storageModeFlags; } diff --git a/OpenMcdf/Storage.cs b/OpenMcdf/Storage.cs index 8bc07bd7..67b76dbd 100644 --- a/OpenMcdf/Storage.cs +++ b/OpenMcdf/Storage.cs @@ -9,7 +9,9 @@ public class Storage : ContextBase internal DirectoryEntry DirectoryEntry { get; } - internal Storage(RootContextSite rootContextSite, DirectoryEntry directoryEntry) + public Storage? Parent { get; } + + internal Storage(RootContextSite rootContextSite, DirectoryEntry directoryEntry, Storage? parent) : base(rootContextSite) { if (directoryEntry.Type is not StorageType.Storage and not StorageType.Root) @@ -17,16 +19,17 @@ internal Storage(RootContextSite rootContextSite, DirectoryEntry directoryEntry) directoryTree = new(Context.DirectoryEntries, directoryEntry); DirectoryEntry = directoryEntry; + Parent = parent; } - public EntryInfo EntryInfo => DirectoryEntry.ToEntryInfo(); + public EntryInfo EntryInfo => DirectoryEntry.ToEntryInfo(Parent); public IEnumerable EnumerateEntries() { this.ThrowIfDisposed(Context.IsDisposed); return EnumerateDirectoryEntries() - .Select(e => e.ToEntryInfo()); + .Select(e => e.ToEntryInfo(this)); } IEnumerable EnumerateDirectoryEntries() @@ -56,7 +59,7 @@ public Storage CreateStorage(string name) this.ThrowIfDisposed(Context.IsDisposed); DirectoryEntry entry = AddDirectoryEntry(StorageType.Storage, name); - return new Storage(ContextSite, entry); + return new Storage(ContextSite, entry, this); } public CfbStream CreateStream(string name) @@ -67,7 +70,7 @@ public CfbStream CreateStream(string name) // TODO: Return a Stream that can transition between FAT and mini FAT DirectoryEntry entry = AddDirectoryEntry(StorageType.Stream, name); - return new CfbStream(ContextSite, entry); + return new CfbStream(ContextSite, entry, this); } public Storage OpenStorage(string name) @@ -79,7 +82,7 @@ public Storage OpenStorage(string name) directoryTree.TryGetDirectoryEntry(name, out DirectoryEntry? entry); if (entry is null || entry.Type is not StorageType.Storage) throw new DirectoryNotFoundException($"Storage not found: {name}."); - return new Storage(ContextSite, entry); + return new Storage(ContextSite, entry, this); } public CfbStream OpenStream(string name) @@ -93,7 +96,7 @@ public CfbStream OpenStream(string name) throw new FileNotFoundException($"Stream not found: {name}.", name); // TODO: Return a Stream that can transition between FAT and mini FAT - return new CfbStream(ContextSite, entry); + return new CfbStream(ContextSite, entry, this); } public void CopyTo(Storage destination) @@ -102,13 +105,13 @@ public void CopyTo(Storage destination) { if (entry.Type is StorageType.Storage) { - Storage subSource = new(ContextSite, entry); + Storage subSource = new(ContextSite, entry, this); Storage subDestination = destination.CreateStorage(entry.NameString); subSource.CopyTo(subDestination); } else if (entry.Type is StorageType.Stream) { - CfbStream stream = new(ContextSite, entry); + CfbStream stream = new(ContextSite, entry, this); CfbStream destinationStream = destination.CreateStream(entry.NameString); stream.CopyTo(destinationStream); } @@ -127,7 +130,7 @@ public void Delete(string name) if (entry.Type is StorageType.Storage && entry.ChildId is not StreamId.NoStream) { - Storage storage = new(ContextSite, entry); + Storage storage = new(ContextSite, entry, this); foreach (EntryInfo childEntry in storage.EnumerateEntries()) { storage.Delete(childEntry.Name); From f33571476de239f248e810c584f73c4acc967b9e Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 09:19:10 +1300 Subject: [PATCH 117/134] Expose base Stream from RootStorage --- OpenMcdf/RootStorage.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OpenMcdf/RootStorage.cs b/OpenMcdf/RootStorage.cs index a7a32996..08a0bc20 100644 --- a/OpenMcdf/RootStorage.cs +++ b/OpenMcdf/RootStorage.cs @@ -95,6 +95,8 @@ public static RootStorage Open(Stream stream, StorageModeFlags flags = StorageMo public void Dispose() => Context.Dispose(); + public Stream BaseStream => Context.BaseStream; + public void Flush(bool consolidate = false) { this.ThrowIfDisposed(Context.IsDisposed); From e0d9e8f7ac54cf05ff5e88a950a0410cf9ebead6 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 09:39:50 +1300 Subject: [PATCH 118/134] Remove redundant code --- OpenMcdf/Storage.cs | 5 ----- OpenMcdf/TransactedStream.cs | 2 -- 2 files changed, 7 deletions(-) diff --git a/OpenMcdf/Storage.cs b/OpenMcdf/Storage.cs index 67b76dbd..244b1b02 100644 --- a/OpenMcdf/Storage.cs +++ b/OpenMcdf/Storage.cs @@ -41,9 +41,6 @@ IEnumerable EnumerateDirectoryEntries() } } - IEnumerable EnumerateDirectoryEntries(StorageType type) => EnumerateDirectoryEntries() - .Where(e => e.Type == type); - DirectoryEntry AddDirectoryEntry(StorageType storageType, string name) { DirectoryEntry entry = Context.DirectoryEntries.CreateOrRecycleDirectoryEntry(); @@ -68,7 +65,6 @@ public CfbStream CreateStream(string name) this.ThrowIfDisposed(Context.IsDisposed); - // TODO: Return a Stream that can transition between FAT and mini FAT DirectoryEntry entry = AddDirectoryEntry(StorageType.Stream, name); return new CfbStream(ContextSite, entry, this); } @@ -95,7 +91,6 @@ public CfbStream OpenStream(string name) if (entry is null || entry.Type is not StorageType.Stream) throw new FileNotFoundException($"Stream not found: {name}.", name); - // TODO: Return a Stream that can transition between FAT and mini FAT return new CfbStream(ContextSite, entry, this); } diff --git a/OpenMcdf/TransactedStream.cs b/OpenMcdf/TransactedStream.cs index 1abf31ac..29e948d0 100644 --- a/OpenMcdf/TransactedStream.cs +++ b/OpenMcdf/TransactedStream.cs @@ -90,7 +90,6 @@ public override void Write(byte[] buffer, int offset, int count) int remainingFromSector = Context.SectorSize - (int)sectorOffset; int localCount = Math.Min(count, remainingFromSector); Debug.Assert(localCount == count); - // TODO: Loop through the buffer and write to the overlay stream bool added = false; if (!dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) @@ -173,7 +172,6 @@ public override void Write(ReadOnlySpan buffer) int remainingFromSector = Context.SectorSize - (int)sectorOffset; int localCount = Math.Min(buffer.Length, remainingFromSector); Debug.Assert(localCount == buffer.Length); - // TODO: Loop through the buffer and write to the overlay stream bool added = false; if (!dirtySectorPositions.TryGetValue(sectorId, out long overlayPosition)) From 6a9e19cf37a480a5621cbd2c1eb7646666aec20d Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 09:54:55 +1300 Subject: [PATCH 119/134] Improve path handling --- OpenMcdf/CfbStream.cs | 11 ++++++++++- OpenMcdf/DirectoryEntry.cs | 14 +------------- OpenMcdf/RootStorage.cs | 2 +- OpenMcdf/Storage.cs | 10 +++++++--- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/OpenMcdf/CfbStream.cs b/OpenMcdf/CfbStream.cs index e355aec4..8fdaef17 100644 --- a/OpenMcdf/CfbStream.cs +++ b/OpenMcdf/CfbStream.cs @@ -25,9 +25,18 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + public Storage Parent { get; } - public EntryInfo EntryInfo => directoryEntry.ToEntryInfo(Parent); + public EntryInfo EntryInfo + { + get + { + EntryInfo parentEntryInfo = Parent.EntryInfo; + string path = $"{parentEntryInfo.Path}{parentEntryInfo.Name}"; + return directoryEntry.ToEntryInfo(path); + } + } public override bool CanRead => stream.CanRead; diff --git a/OpenMcdf/DirectoryEntry.cs b/OpenMcdf/DirectoryEntry.cs index 65dd92fb..1e67470c 100644 --- a/OpenMcdf/DirectoryEntry.cs +++ b/OpenMcdf/DirectoryEntry.cs @@ -226,19 +226,7 @@ public void Recycle(StorageType storageType, string name) _ => throw new InvalidOperationException("Invalid storage type.") }; - public EntryInfo ToEntryInfo(Storage? parent) - { - StringBuilder path = new(); - path.Append('/'); - while (parent is not null) - { - path.Insert(0, parent.DirectoryEntry.NameString); - path.Insert(0, '/'); - parent = parent.Parent; - } - - return new(EntryType, path.ToString(), NameString, StreamLength, CLSID, CreationTime, ModifiedTime); - } + public EntryInfo ToEntryInfo(string path) => new(EntryType, path, NameString, StreamLength, CLSID, CreationTime, ModifiedTime); public override string ToString() => $"{Id}: \"{NameString}\""; diff --git a/OpenMcdf/RootStorage.cs b/OpenMcdf/RootStorage.cs index 08a0bc20..c15cf7f5 100644 --- a/OpenMcdf/RootStorage.cs +++ b/OpenMcdf/RootStorage.cs @@ -118,7 +118,7 @@ void Consolidate() if (Context.BaseStream is MemoryStream) destinationStream = new MemoryStream((int)Context.BaseStream.Length); else if (Context.BaseStream is FileStream) - destinationStream = File.Create(Path.GetTempFileName()); + destinationStream = File.Create(System.IO.Path.GetTempFileName()); else throw new NotSupportedException("Unsupported stream type for consolidation."); diff --git a/OpenMcdf/Storage.cs b/OpenMcdf/Storage.cs index 244b1b02..ac905d17 100644 --- a/OpenMcdf/Storage.cs +++ b/OpenMcdf/Storage.cs @@ -5,7 +5,8 @@ ///
public class Storage : ContextBase { - internal readonly DirectoryTree directoryTree; + readonly DirectoryTree directoryTree; + readonly string path; internal DirectoryEntry DirectoryEntry { get; } @@ -20,16 +21,19 @@ internal Storage(RootContextSite rootContextSite, DirectoryEntry directoryEntry, directoryTree = new(Context.DirectoryEntries, directoryEntry); DirectoryEntry = directoryEntry; Parent = parent; + path = parent is null ? $"/" : $"{parent.path}{parent.EntryInfo.Name}/"; } - public EntryInfo EntryInfo => DirectoryEntry.ToEntryInfo(Parent); + public EntryInfo EntryInfo => DirectoryEntry.ToEntryInfo(path); public IEnumerable EnumerateEntries() { this.ThrowIfDisposed(Context.IsDisposed); + EntryInfo entryInfo = EntryInfo; + string path = $"{entryInfo.Path}{entryInfo.Name}"; return EnumerateDirectoryEntries() - .Select(e => e.ToEntryInfo(this)); + .Select(e => e.ToEntryInfo(path)); } IEnumerable EnumerateDirectoryEntries() From fcc3723d22acadd2dd40ddb3dd9a44a54d90e560 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 10:05:54 +1300 Subject: [PATCH 120/134] Improve argument validation --- OpenMcdf/CfbStream.cs | 31 +++++++++++++++++++++++++++---- OpenMcdf/FatStream.cs | 26 ++++++++++---------------- OpenMcdf/MiniFatStream.cs | 26 ++++++++++---------------- 3 files changed, 47 insertions(+), 36 deletions(-) diff --git a/OpenMcdf/CfbStream.cs b/OpenMcdf/CfbStream.cs index 8fdaef17..8805cbfa 100644 --- a/OpenMcdf/CfbStream.cs +++ b/OpenMcdf/CfbStream.cs @@ -8,6 +8,7 @@ public sealed class CfbStream : Stream private readonly RootContextSite rootContextSite; private readonly DirectoryEntry directoryEntry; private Stream stream; + private bool isDisposed; internal CfbStream(RootContextSite rootContextSite, DirectoryEntry directoryEntry, Storage parent) { @@ -21,7 +22,11 @@ internal CfbStream(RootContextSite rootContextSite, DirectoryEntry directoryEntr protected override void Dispose(bool disposing) { - stream.Dispose(); + if (!isDisposed) + { + stream.Dispose(); + isDisposed = true; + } base.Dispose(disposing); } @@ -48,14 +53,30 @@ public EntryInfo EntryInfo public override long Position { get => stream.Position; set => stream.Position = value; } - public override void Flush() => stream.Flush(); + public override void Flush() + { + this.ThrowIfDisposed(isDisposed); + + stream.Flush(); + } - public override int Read(byte[] buffer, int offset, int count) => stream.Read(buffer, offset, count); + public override int Read(byte[] buffer, int offset, int count) + { + this.ThrowIfDisposed(isDisposed); - public override long Seek(long offset, SeekOrigin origin) => stream.Seek(offset, origin); + return stream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + this.ThrowIfDisposed(isDisposed); + + return stream.Seek(offset, origin); + } public override void SetLength(long value) { + this.ThrowIfDisposed(isDisposed); this.ThrowIfNotWritable(); if (value >= Header.MiniStreamCutoffSize && stream is MiniFatStream miniStream) @@ -98,6 +119,7 @@ public override void Write(byte[] buffer, int offset, int count) { ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); + this.ThrowIfDisposed(isDisposed); this.ThrowIfNotWritable(); long newPosition = Position + count; @@ -117,6 +139,7 @@ public override void Write(byte[] buffer, int offset, int count) public override void Write(ReadOnlySpan buffer) { + this.ThrowIfDisposed(isDisposed); this.ThrowIfNotWritable(); long newPosition = Position + buffer.Length; diff --git a/OpenMcdf/FatStream.cs b/OpenMcdf/FatStream.cs index bf4cbd1a..d7620fa2 100644 --- a/OpenMcdf/FatStream.cs +++ b/OpenMcdf/FatStream.cs @@ -9,7 +9,7 @@ internal class FatStream : Stream readonly FatChainEnumerator chain; long position; bool isDirty; - bool disposed; + bool isDisposed; private RootContext Context => rootContextSite.Context; @@ -47,12 +47,12 @@ public override long Position /// protected override void Dispose(bool disposing) { - if (!disposed) + if (!isDisposed) { Flush(); chain.Dispose(); - disposed = true; + isDisposed = true; } base.Dispose(disposing); @@ -61,7 +61,7 @@ protected override void Dispose(bool disposing) /// public override void Flush() { - this.ThrowIfDisposed(disposed); + this.ThrowIfDisposed(isDisposed); if (isDirty) { @@ -78,9 +78,7 @@ public override void Flush() /// public override int Read(byte[] buffer, int offset, int count) { - ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); - - this.ThrowIfDisposed(disposed); + this.ThrowIfDisposed(isDisposed); if (count == 0) return 0; @@ -118,7 +116,7 @@ public override int Read(byte[] buffer, int offset, int count) /// public override long Seek(long offset, SeekOrigin origin) { - this.ThrowIfDisposed(disposed); + this.ThrowIfDisposed(isDisposed); switch (origin) { @@ -150,7 +148,7 @@ public override long Seek(long offset, SeekOrigin origin) /// public override void SetLength(long value) { - this.ThrowIfNotWritable(); + this.ThrowIfDisposed(isDisposed); uint requiredChainLength = (uint)((value + Context.SectorSize - 1) / Context.SectorSize); if (value > ChainCapacity) @@ -165,10 +163,7 @@ public override void SetLength(long value) /// public override void Write(byte[] buffer, int offset, int count) { - ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); - - this.ThrowIfDisposed(disposed); - this.ThrowIfNotWritable(); + this.ThrowIfDisposed(isDisposed); if (count == 0) return; @@ -213,7 +208,7 @@ public override void Write(byte[] buffer, int offset, int count) public override int Read(Span buffer) { - this.ThrowIfDisposed(disposed); + this.ThrowIfDisposed(isDisposed); if (buffer.Length == 0) return 0; @@ -253,8 +248,7 @@ public override int Read(Span buffer) public override void Write(ReadOnlySpan buffer) { - this.ThrowIfDisposed(disposed); - this.ThrowIfNotWritable(); + this.ThrowIfDisposed(isDisposed); if (buffer.Length == 0) return; diff --git a/OpenMcdf/MiniFatStream.cs b/OpenMcdf/MiniFatStream.cs index d1c92770..130faaf7 100644 --- a/OpenMcdf/MiniFatStream.cs +++ b/OpenMcdf/MiniFatStream.cs @@ -8,7 +8,7 @@ internal sealed class MiniFatStream : Stream readonly RootContextSite rootContextSite; readonly MiniFatChainEnumerator miniChain; long position; - bool disposed; + bool isDisposed; bool isDirty; internal MiniFatStream(RootContextSite rootContextSite, DirectoryEntry directoryEntry) @@ -40,12 +40,12 @@ public override long Position protected override void Dispose(bool disposing) { - if (!disposed) + if (!isDisposed) { Flush(); miniChain.Dispose(); - disposed = true; + isDisposed = true; } base.Dispose(disposing); @@ -53,7 +53,7 @@ protected override void Dispose(bool disposing) public override void Flush() { - this.ThrowIfDisposed(disposed); + this.ThrowIfDisposed(isDisposed); if (isDirty) { @@ -68,9 +68,7 @@ public override void Flush() public override int Read(byte[] buffer, int offset, int count) { - ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); - - this.ThrowIfDisposed(disposed); + this.ThrowIfDisposed(isDisposed); if (count == 0) return 0; @@ -108,7 +106,7 @@ public override int Read(byte[] buffer, int offset, int count) public override long Seek(long offset, SeekOrigin origin) { - this.ThrowIfDisposed(disposed); + this.ThrowIfDisposed(isDisposed); switch (origin) { @@ -142,8 +140,7 @@ public override void SetLength(long value) if (value >= Header.MiniStreamCutoffSize) throw new ArgumentOutOfRangeException(nameof(value)); - this.ThrowIfDisposed(disposed); - this.ThrowIfNotWritable(); + this.ThrowIfDisposed(isDisposed); uint requiredChainLength = (uint)((value + Context.MiniSectorSize - 1) / Context.MiniSectorSize); if (value > ChainCapacity) @@ -157,9 +154,7 @@ public override void SetLength(long value) public override void Write(byte[] buffer, int offset, int count) { - ThrowHelper.ThrowIfStreamArgumentsAreInvalid(buffer, offset, count); - - this.ThrowIfDisposed(disposed); + this.ThrowIfDisposed(isDisposed); this.ThrowIfNotWritable(); if (count == 0) @@ -204,7 +199,7 @@ public override void Write(byte[] buffer, int offset, int count) public override int Read(Span buffer) { - this.ThrowIfDisposed(disposed); + this.ThrowIfDisposed(isDisposed); if (buffer.Length == 0) return 0; @@ -245,8 +240,7 @@ public override int Read(Span buffer) public override void Write(ReadOnlySpan buffer) { - this.ThrowIfDisposed(disposed); - this.ThrowIfNotWritable(); + this.ThrowIfDisposed(isDisposed); if (buffer.Length == 0) return; From 090fb4ed871e76c7698a265ba5e021fc39c47e62 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 10:12:43 +1300 Subject: [PATCH 121/134] Improve format/state validation --- OpenMcdf/CfbStream.cs | 15 ++++++----- OpenMcdf/FatStream.cs | 53 ++++++++++++++++++--------------------- OpenMcdf/MiniFatStream.cs | 26 ++++++++++--------- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/OpenMcdf/CfbStream.cs b/OpenMcdf/CfbStream.cs index 8805cbfa..66ee73aa 100644 --- a/OpenMcdf/CfbStream.cs +++ b/OpenMcdf/CfbStream.cs @@ -74,6 +74,13 @@ public override long Seek(long offset, SeekOrigin origin) return stream.Seek(offset, origin); } + private void EnsureLengthToWrite(int count) + { + long newPosition = Position + count; + if (newPosition > stream.Length) + SetLength(newPosition); + } + public override void SetLength(long value) { this.ThrowIfDisposed(isDisposed); @@ -122,9 +129,7 @@ public override void Write(byte[] buffer, int offset, int count) this.ThrowIfDisposed(isDisposed); this.ThrowIfNotWritable(); - long newPosition = Position + count; - if (newPosition > stream.Length) - SetLength(newPosition); + EnsureLengthToWrite(count); stream.Write(buffer, offset, count); } @@ -142,9 +147,7 @@ public override void Write(ReadOnlySpan buffer) this.ThrowIfDisposed(isDisposed); this.ThrowIfNotWritable(); - long newPosition = Position + buffer.Length; - if (newPosition > stream.Length) - SetLength(newPosition); + EnsureLengthToWrite(buffer.Length); stream.Write(buffer); } diff --git a/OpenMcdf/FatStream.cs b/OpenMcdf/FatStream.cs index d7620fa2..81c6de15 100644 --- a/OpenMcdf/FatStream.cs +++ b/OpenMcdf/FatStream.cs @@ -89,11 +89,11 @@ public override int Read(byte[] buffer, int offset, int count) uint chainIndex = GetFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!chain.MoveTo(chainIndex)) - return 0; + throw new FormatException($"The FAT chain was shorter than the stream length."); int realCount = Math.Min(count, maxCount); int readCount = 0; - do + while (true) { Sector sector = chain.CurrentSector; int remaining = realCount - readCount; @@ -108,9 +108,9 @@ public override int Read(byte[] buffer, int offset, int count) sectorOffset = 0; if (readCount >= realCount) return readCount; - } while (chain.MoveNext()); - - return readCount; + if (!chain.MoveNext()) + throw new FormatException($"The FAT chain was shorter than the stream length."); + }; } /// @@ -173,7 +173,7 @@ public override void Write(byte[] buffer, int offset, int count) CfbBinaryWriter writer = Context.Writer; int writeCount = 0; uint lastIndex = 0; - for (; ; ) + do { if (!chain.MoveTo(chainIndex)) lastIndex = chain.ExtendFrom(lastIndex); @@ -187,19 +187,15 @@ public override void Write(byte[] buffer, int offset, int count) Context.ExtendStreamLength(sector.EndPosition); position += writeLength; writeCount += (int)writeLength; - if (position > Length) - { - DirectoryEntry.StreamLength = position; - isDirty = true; - } sectorOffset = 0; - if (writeCount >= count) - return; - chainIndex++; - } + } while (writeCount < count); - throw new InvalidOperationException($"End of FAT chain was reached"); + if (position > Length) + { + DirectoryEntry.StreamLength = position; + isDirty = true; + } } #if (!NETSTANDARD2_0 && !NETFRAMEWORK) @@ -219,11 +215,11 @@ public override int Read(Span buffer) uint chainIndex = GetFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!chain.MoveTo(chainIndex)) - return 0; + throw new FormatException($"The FAT chain was shorter than the stream length."); int realCount = Math.Min(buffer.Length, maxCount); int readCount = 0; - do + while (true) { Sector sector = chain.CurrentSector; int remaining = realCount - readCount; @@ -239,9 +235,9 @@ public override int Read(Span buffer) sectorOffset = 0; if (readCount >= realCount) return readCount; - } while (chain.MoveNext()); - - return readCount; + if (!chain.MoveNext()) + throw new FormatException($"The FAT chain was shorter than the stream length."); + } } public override void WriteByte(byte value) => this.WriteByteCore(value); @@ -258,7 +254,7 @@ public override void Write(ReadOnlySpan buffer) CfbBinaryWriter writer = Context.Writer; int writeCount = 0; uint lastIndex = 0; - for (; ; ) + do { if (!chain.MoveTo(chainIndex)) lastIndex = chain.ExtendFrom(lastIndex); @@ -273,16 +269,15 @@ public override void Write(ReadOnlySpan buffer) Context.ExtendStreamLength(sector.EndPosition); position += writeLength; writeCount += (int)writeLength; - if (position > Length) - DirectoryEntry.StreamLength = position; sectorOffset = 0; - if (writeCount >= buffer.Length) - return; - chainIndex++; - } + } while (writeCount < buffer.Length); - throw new InvalidOperationException($"End of FAT chain was reached"); + if (position > Length) + { + DirectoryEntry.StreamLength = position; + isDirty = true; + } } #endif diff --git a/OpenMcdf/MiniFatStream.cs b/OpenMcdf/MiniFatStream.cs index 130faaf7..ba40a293 100644 --- a/OpenMcdf/MiniFatStream.cs +++ b/OpenMcdf/MiniFatStream.cs @@ -79,12 +79,12 @@ public override int Read(byte[] buffer, int offset, int count) uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) - return 0; + throw new FormatException($"The mini FAT chain was shorter than the stream length."); FatStream miniStream = Context.MiniStream; int realCount = Math.Min(count, maxCount); int readCount = 0; - do + while (true) { MiniSector miniSector = miniChain.CurrentSector; int remaining = realCount - readCount; @@ -99,9 +99,9 @@ public override int Read(byte[] buffer, int offset, int count) sectorOffset = 0; if (readCount >= realCount) return readCount; - } while (miniChain.MoveNext()); - - return readCount; + if (!miniChain.MoveNext()) + throw new FormatException($"The mini FAT chain was shorter than the stream length."); + } } public override long Seek(long offset, SeekOrigin origin) @@ -190,7 +190,8 @@ public override void Write(byte[] buffer, int offset, int count) return; } while (miniChain.MoveNext()); - throw new InvalidOperationException($"End of mini FAT chain was reached."); + // Should be impossible since the chain length was validated + throw new InvalidOperationException($"The end of mini FAT chain was reached."); } #if (!NETSTANDARD2_0 && !NETFRAMEWORK) @@ -210,12 +211,12 @@ public override int Read(Span buffer) uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset); if (!miniChain.MoveTo(chainIndex)) - return 0; + throw new FormatException($"The mini FAT chain was shorter than the stream length."); FatStream miniStream = Context.MiniStream; int realCount = Math.Min(buffer.Length, maxCount); int readCount = 0; - do + while (true) { MiniSector miniSector = miniChain.CurrentSector; int remaining = realCount - readCount; @@ -231,9 +232,9 @@ public override int Read(Span buffer) sectorOffset = 0; if (readCount >= realCount) return readCount; - } while (miniChain.MoveNext()); - - return readCount; + if (!miniChain.MoveNext()) + throw new FormatException($"The mini FAT chain was shorter than the stream length."); + } } public override void WriteByte(byte value) => this.WriteByteCore(value); @@ -273,7 +274,8 @@ public override void Write(ReadOnlySpan buffer) return; } while (miniChain.MoveNext()); - throw new InvalidOperationException($"End of mini FAT chain was reached."); + // Should be impossible since the chain length was validated + throw new InvalidOperationException($"The end of mini FAT chain was reached."); } #endif From e32809e5e619275d9c87e9f77d1dda1b71ee7c9b Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 12:51:24 +1300 Subject: [PATCH 122/134] Expose storage metadata --- OpenMcdf/DirectoryEntry.cs | 2 +- OpenMcdf/Storage.cs | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/OpenMcdf/DirectoryEntry.cs b/OpenMcdf/DirectoryEntry.cs index 1e67470c..297c3a31 100644 --- a/OpenMcdf/DirectoryEntry.cs +++ b/OpenMcdf/DirectoryEntry.cs @@ -205,7 +205,7 @@ public void Recycle(StorageType storageType, string name) CreationTime = ZeroFileTime; ModifiedTime = DateTime.UtcNow; } - if (storageType is StorageType.Storage) + else if (storageType is StorageType.Storage) { DateTime now = DateTime.UtcNow; CreationTime = now; diff --git a/OpenMcdf/Storage.cs b/OpenMcdf/Storage.cs index ac905d17..90f9812b 100644 --- a/OpenMcdf/Storage.cs +++ b/OpenMcdf/Storage.cs @@ -26,6 +26,46 @@ internal Storage(RootContextSite rootContextSite, DirectoryEntry directoryEntry, public EntryInfo EntryInfo => DirectoryEntry.ToEntryInfo(path); + public Guid CLISD + { + get => DirectoryEntry.CLSID; + set + { + DirectoryEntry.CLSID = value; + Context.DirectoryEntries.Write(DirectoryEntry); + } + } + + public DateTime CreationTime + { + get => DirectoryEntry.CreationTime; + set + { + DirectoryEntry.CreationTime = value; + Context.DirectoryEntries.Write(DirectoryEntry); + } + } + + public DateTime ModifiedTime + { + get => DirectoryEntry.ModifiedTime; + set + { + DirectoryEntry.ModifiedTime = value; + Context.DirectoryEntries.Write(DirectoryEntry); + } + } + + public uint StateBits + { + get => DirectoryEntry.StateBits; + set + { + DirectoryEntry.StateBits = value; + Context.DirectoryEntries.Write(DirectoryEntry); + } + } + public IEnumerable EnumerateEntries() { this.ThrowIfDisposed(Context.IsDisposed); From 85a32d8df3997ec2aaf1db4b1a4fc10d5b3b1cf1 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 12:54:40 +1300 Subject: [PATCH 123/134] Handle OLE VT_I8 --- OpenMcdf.Ole/PropertyFactory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenMcdf.Ole/PropertyFactory.cs b/OpenMcdf.Ole/PropertyFactory.cs index 9a1df14a..e2c51fb0 100644 --- a/OpenMcdf.Ole/PropertyFactory.cs +++ b/OpenMcdf.Ole/PropertyFactory.cs @@ -16,6 +16,7 @@ public ITypedPropertyValue CreateProperty(VTPropertyType vType, int codePage, ui VTPropertyType.VT_I1 => new VT_I1_Property(vType, isVariant), VTPropertyType.VT_I2 => new VT_I2_Property(vType, isVariant), VTPropertyType.VT_I4 => new VT_I4_Property(vType, isVariant), + VTPropertyType.VT_I8 => new VT_I8_Property(vType, isVariant), VTPropertyType.VT_R4 => new VT_R4_Property(vType, isVariant), VTPropertyType.VT_R8 => new VT_R8_Property(vType, isVariant), VTPropertyType.VT_CY => new VT_CY_Property(vType, isVariant), From 223606c244c352e39bd6df71a032a5d189f70ff4 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 12:58:43 +1300 Subject: [PATCH 124/134] Update Microsoft.Bcl.HashCode to v6.0.0 --- OpenMcdf/OpenMcdf.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenMcdf/OpenMcdf.csproj b/OpenMcdf/OpenMcdf.csproj index 7a2d5751..64857232 100644 --- a/OpenMcdf/OpenMcdf.csproj +++ b/OpenMcdf/OpenMcdf.csproj @@ -11,7 +11,7 @@ - + From 8be9e91e07478f442bf7721ab104509557755f50 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 13:22:49 +1300 Subject: [PATCH 125/134] Add Directory.Build files and config --- Directory.Build.props | 4 ++++ Directory.Build.targets | 3 +++ Directory.csproj.props | 19 +++++++++++++++++++ Directory.csproj.targets | 3 +++ .../OpenMcdf.Benchmarks.csproj | 3 --- OpenMcdf.Ole.Tests/OpenMcdf.Ole.Tests.csproj | 3 --- OpenMcdf.Ole/OpenMcdf.Ole.csproj | 4 +--- OpenMcdf.Perf/OpenMcdf.Perf.csproj | 4 +--- OpenMcdf.Tests/OpenMcdf.Tests.csproj | 3 --- OpenMcdf.sln | 4 ++++ OpenMcdf/OpenMcdf.csproj | 4 ---- StructuredStorage/StructuredStorage.csproj | 3 --- .../StructuredStorageExplorer.csproj | 5 ++--- 13 files changed, 37 insertions(+), 25 deletions(-) create mode 100644 Directory.Build.props create mode 100644 Directory.Build.targets create mode 100644 Directory.csproj.props create mode 100644 Directory.csproj.targets diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..2f0930f5 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 00000000..27a60831 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Directory.csproj.props b/Directory.csproj.props new file mode 100644 index 00000000..adca077d --- /dev/null +++ b/Directory.csproj.props @@ -0,0 +1,19 @@ + + + + + 12 + enable + enable + true + + + + + latest + true + + + true + + \ No newline at end of file diff --git a/Directory.csproj.targets b/Directory.csproj.targets new file mode 100644 index 00000000..472bb788 --- /dev/null +++ b/Directory.csproj.targets @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/OpenMcdf.Benchmarks/OpenMcdf.Benchmarks.csproj b/OpenMcdf.Benchmarks/OpenMcdf.Benchmarks.csproj index cdcc1431..d379344e 100644 --- a/OpenMcdf.Benchmarks/OpenMcdf.Benchmarks.csproj +++ b/OpenMcdf.Benchmarks/OpenMcdf.Benchmarks.csproj @@ -2,10 +2,7 @@ net8.0-windows - 12.0 Exe - enable - enable diff --git a/OpenMcdf.Ole.Tests/OpenMcdf.Ole.Tests.csproj b/OpenMcdf.Ole.Tests/OpenMcdf.Ole.Tests.csproj index 59b58f11..7ca403af 100644 --- a/OpenMcdf.Ole.Tests/OpenMcdf.Ole.Tests.csproj +++ b/OpenMcdf.Ole.Tests/OpenMcdf.Ole.Tests.csproj @@ -3,9 +3,6 @@ net48;net8.0 Exe - 12.0 - enable - enable false true diff --git a/OpenMcdf.Ole/OpenMcdf.Ole.csproj b/OpenMcdf.Ole/OpenMcdf.Ole.csproj index 12de727d..c2f72f10 100644 --- a/OpenMcdf.Ole/OpenMcdf.Ole.csproj +++ b/OpenMcdf.Ole/OpenMcdf.Ole.csproj @@ -1,9 +1,7 @@  + netstandard2.0;net8.0 - 12.0 - enable - enable diff --git a/OpenMcdf.Perf/OpenMcdf.Perf.csproj b/OpenMcdf.Perf/OpenMcdf.Perf.csproj index e41e919c..01fa9fd6 100644 --- a/OpenMcdf.Perf/OpenMcdf.Perf.csproj +++ b/OpenMcdf.Perf/OpenMcdf.Perf.csproj @@ -1,10 +1,8 @@  + net4.8;net8.0 Exe - net8.0 - enable - enable diff --git a/OpenMcdf.Tests/OpenMcdf.Tests.csproj b/OpenMcdf.Tests/OpenMcdf.Tests.csproj index dcba798f..d3d502ff 100644 --- a/OpenMcdf.Tests/OpenMcdf.Tests.csproj +++ b/OpenMcdf.Tests/OpenMcdf.Tests.csproj @@ -3,9 +3,6 @@ net48;net8.0-windows Exe - 12.0 - enable - enable false true diff --git a/OpenMcdf.sln b/OpenMcdf.sln index d8639c4d..9315355d 100644 --- a/OpenMcdf.sln +++ b/OpenMcdf.sln @@ -11,6 +11,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .globalconfig = .globalconfig + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + Directory.csproj.props = Directory.csproj.props + Directory.csproj.targets = Directory.csproj.targets exclusion.dic = exclusion.dic EndProjectSection EndProject diff --git a/OpenMcdf/OpenMcdf.csproj b/OpenMcdf/OpenMcdf.csproj index 64857232..6d746033 100644 --- a/OpenMcdf/OpenMcdf.csproj +++ b/OpenMcdf/OpenMcdf.csproj @@ -2,10 +2,6 @@ netstandard2.0;net8.0 - 12.0 - enable - enable - true 3.0.0.0 ironfede,Jeremy Powell diff --git a/StructuredStorage/StructuredStorage.csproj b/StructuredStorage/StructuredStorage.csproj index 40393c66..12c9a903 100644 --- a/StructuredStorage/StructuredStorage.csproj +++ b/StructuredStorage/StructuredStorage.csproj @@ -2,9 +2,6 @@ net8.0-windows - enable - enable - 12.0 true diff --git a/StructuredStorageExplorer/StructuredStorageExplorer.csproj b/StructuredStorageExplorer/StructuredStorageExplorer.csproj index 0d089f2d..9e873312 100644 --- a/StructuredStorageExplorer/StructuredStorageExplorer.csproj +++ b/StructuredStorageExplorer/StructuredStorageExplorer.csproj @@ -1,12 +1,11 @@  + net8.0-windows WinExe true - 12.0 - enable - enable + From 1634e8126e8a26dbd0742a612c7c8b39638e400b Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 13:29:04 +1300 Subject: [PATCH 126/134] Fix warnings --- .../OlePropertiesExtensionsTests.cs | 13 +++++++++- OpenMcdf.Ole/CodePages.cs | 4 +-- OpenMcdf.Ole/OlePropertiesContainer.cs | 2 +- OpenMcdf.Ole/PropertyFactory.cs | 15 ++++------- OpenMcdf.Tests/StorageTests.cs | 1 + OpenMcdf/DirectoryEntryComparer.cs | 2 +- .../MainForm.Designer.cs | 26 +++++++++---------- StructuredStorageExplorer/MainForm.cs | 26 +++++++++---------- .../PreferencesForm.Designer.cs | 4 +-- StructuredStorageExplorer/PreferencesForm.cs | 4 +-- StructuredStorageExplorer/Utils.cs | 2 +- 11 files changed, 52 insertions(+), 47 deletions(-) diff --git a/OpenMcdf.Ole.Tests/OlePropertiesExtensionsTests.cs b/OpenMcdf.Ole.Tests/OlePropertiesExtensionsTests.cs index 6537e36a..e92126d5 100644 --- a/OpenMcdf.Ole.Tests/OlePropertiesExtensionsTests.cs +++ b/OpenMcdf.Ole.Tests/OlePropertiesExtensionsTests.cs @@ -214,10 +214,13 @@ public void TestReadUnicodeUserPropertiesDictionary() OlePropertiesContainer co = new(dsiStream); OlePropertiesContainer? userProps = co.UserDefinedProperties; + Assert.IsNotNull(userProps); + // CodePage should be CP_WINUNICODE (1200) Assert.AreEqual(1200, userProps.Context.CodePage); // There should be 5 property names present, and 6 properties (the properties include the code page) + Assert.IsNotNull(userProps.PropertyNames); Assert.AreEqual(5, userProps.PropertyNames.Count); Assert.AreEqual(6, userProps.Properties.Count); @@ -260,6 +263,8 @@ public void AddDocumentSummaryInformationCustomInfo() OlePropertiesContainer co = new(dsiStream); OlePropertiesContainer? userProperties = co.UserDefinedProperties; + Assert.IsNotNull(userProperties?.PropertyNames); + userProperties.PropertyNames[2] = "StringProperty"; userProperties.PropertyNames[3] = "BooleanProperty"; userProperties.PropertyNames[4] = "IntegerProperty"; @@ -293,6 +298,8 @@ public void AddDocumentSummaryInformationCustomInfo() { using CfbStream stream = cf.OpenStream("\u0005DocumentSummaryInformation"); OlePropertiesContainer co = new(stream); + + Assert.IsNotNull(co.UserDefinedProperties); OleProperty[] propArray = co.UserDefinedProperties.Properties.ToArray(); Assert.AreEqual(6, propArray.Length); @@ -329,9 +336,12 @@ public void ReadLpwstringVector() using CfbStream stream = cf.OpenStream("\u0005DocumentSummaryInformation"); OlePropertiesContainer co = new(stream); - OleProperty docPartsProperty = co.Properties.FirstOrDefault(property => property.PropertyIdentifier == 13); //13 == PIDDSI_DOCPARTS + OleProperty? docPartsProperty = co.Properties.FirstOrDefault(property => property.PropertyIdentifier == 13); //13 == PIDDSI_DOCPARTS + Assert.IsNotNull(docPartsProperty); var docPartsValues = docPartsProperty.Value as IList; + Assert.IsNotNull(docPartsValues); + Assert.AreEqual(3, docPartsValues.Count); Assert.AreEqual("Sheet1", docPartsValues[0]); Assert.AreEqual("Sheet2", docPartsValues[1]); @@ -367,6 +377,7 @@ public void AddUserDefinedPropertiesSection() OlePropertiesContainer newUserDefinedProperties = co.CreateUserDefinedProperties(65001); // 65001 - UTF-8 + Assert.IsNotNull(newUserDefinedProperties.PropertyNames); newUserDefinedProperties.PropertyNames[2] = "MyCustomProperty"; OleProperty CreateProperty = co.CreateProperty(VTPropertyType.VT_LPSTR, 2); diff --git a/OpenMcdf.Ole/CodePages.cs b/OpenMcdf.Ole/CodePages.cs index fdf34886..1f5a22c7 100644 --- a/OpenMcdf.Ole/CodePages.cs +++ b/OpenMcdf.Ole/CodePages.cs @@ -1,6 +1,4 @@ -using System.Text; - -namespace OpenMcdf.Ole; +namespace OpenMcdf.Ole; internal static class CodePages { diff --git a/OpenMcdf.Ole/OlePropertiesContainer.cs b/OpenMcdf.Ole/OlePropertiesContainer.cs index f21fea91..8c8d564c 100644 --- a/OpenMcdf.Ole/OlePropertiesContainer.cs +++ b/OpenMcdf.Ole/OlePropertiesContainer.cs @@ -143,7 +143,7 @@ public void RemoveProperty(uint propertyIdentifier) /// /// The code page to use for the user defined properties. /// The UserDefinedProperties container. - /// If this container is a type that doesn't suppose user defined properties. + /// If this container is a type that doesn't suppose user defined properties. public OlePropertiesContainer CreateUserDefinedProperties(int codePage) { // Only the DocumentSummaryInfo stream can contain a UserDefinedProperties diff --git a/OpenMcdf.Ole/PropertyFactory.cs b/OpenMcdf.Ole/PropertyFactory.cs index e2c51fb0..328c5494 100644 --- a/OpenMcdf.Ole/PropertyFactory.cs +++ b/OpenMcdf.Ole/PropertyFactory.cs @@ -29,7 +29,7 @@ public ITypedPropertyValue CreateProperty(VTPropertyType vType, int codePage, ui VTPropertyType.VT_UI8 => new VT_UI8_Property(vType, isVariant), VTPropertyType.VT_BSTR => new VT_LPSTR_Property(vType, codePage, isVariant), VTPropertyType.VT_LPSTR => CreateLpstrProperty(vType, codePage, propertyIdentifier, isVariant), - VTPropertyType.VT_LPWSTR => new VT_LPWSTR_Property(vType, codePage, isVariant), + VTPropertyType.VT_LPWSTR => new VT_LPWSTR_Property(vType, isVariant), VTPropertyType.VT_FILETIME => new VT_FILETIME_Property(vType, isVariant), VTPropertyType.VT_DECIMAL => new VT_DECIMAL_Property(vType, isVariant), VTPropertyType.VT_BOOL => new VT_BOOL_Property(vType, isVariant), @@ -349,8 +349,6 @@ public override void WriteScalarValue(BinaryWriter bw, string pValue) //if (addNullTerminator) dataLength += 1; // null terminator \u+0000 - uint mod = dataLength % 4; // pad to multiple of 4 bytes - bw.Write(dataLength); // data length of string + null char (unicode) bw.Write(data); // string @@ -375,11 +373,8 @@ public VT_Unaligned_LPSTR_Property(VTPropertyType vType, int codePage, bool isVa private sealed class VT_LPWSTR_Property : TypedPropertyValue { - private readonly int codePage; - - public VT_LPWSTR_Property(VTPropertyType vType, int codePage, bool isVariant) : base(vType, isVariant) + public VT_LPWSTR_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) { - this.codePage = codePage; } public override string ReadScalarValue(BinaryReader br) @@ -489,7 +484,7 @@ public VT_BOOL_Property(VTPropertyType vType, bool isVariant) : base(vType, isVa public override bool ReadScalarValue(BinaryReader br) { - propertyValue = br.ReadUInt16() == 0xFFFF ? true : false; + propertyValue = br.ReadUInt16() == 0xFFFF; return (bool)propertyValue; //br.ReadUInt16();//padding } @@ -587,7 +582,7 @@ public VT_VariantVector(VTPropertyType vType, int codePage, bool isVariant, Prop public override object ReadScalarValue(BinaryReader br) { - VTPropertyType vType = (VTPropertyType)br.ReadUInt16(); + var vType = (VTPropertyType)br.ReadUInt16(); br.ReadUInt16(); // Ushort Padding ITypedPropertyValue p = factory.CreateProperty(vType, codePage, propertyIdentifier, true); @@ -597,7 +592,7 @@ public override object ReadScalarValue(BinaryReader br) public override void WriteScalarValue(BinaryWriter bw, object pValue) { - ITypedPropertyValue p = (ITypedPropertyValue)pValue; + var p = (ITypedPropertyValue)pValue; p.Write(bw); } diff --git a/OpenMcdf.Tests/StorageTests.cs b/OpenMcdf.Tests/StorageTests.cs index c8c95488..1d7ea7c6 100644 --- a/OpenMcdf.Tests/StorageTests.cs +++ b/OpenMcdf.Tests/StorageTests.cs @@ -8,6 +8,7 @@ public sealed class StorageTests [DataRow("MultipleStorage2.cfs", 1)] [DataRow("MultipleStorage3.cfs", 1)] [DataRow("MultipleStorage4.cfs", 1)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0063:Use simple 'using' statement", Justification = "Conditional build")] public void Read(string fileName, long storageCount) { using (var rootStorage = RootStorage.OpenRead(fileName)) diff --git a/OpenMcdf/DirectoryEntryComparer.cs b/OpenMcdf/DirectoryEntryComparer.cs index d97a19b5..30f683d1 100644 --- a/OpenMcdf/DirectoryEntryComparer.cs +++ b/OpenMcdf/DirectoryEntryComparer.cs @@ -3,7 +3,7 @@ namespace OpenMcdf; /// -/// Provides a for objects. +/// Provides a for objects. /// internal class DirectoryEntryComparer : IComparer { diff --git a/StructuredStorageExplorer/MainForm.Designer.cs b/StructuredStorageExplorer/MainForm.Designer.cs index a20ed86b..614214ce 100644 --- a/StructuredStorageExplorer/MainForm.Designer.cs +++ b/StructuredStorageExplorer/MainForm.Designer.cs @@ -99,7 +99,7 @@ private void InitializeComponent() this.treeView1.Name = "treeView1"; this.treeView1.Size = new System.Drawing.Size(281, 201); this.treeView1.TabIndex = 4; - this.treeView1.MouseUp += new System.Windows.Forms.MouseEventHandler(this.treeView1_MouseUp); + this.treeView1.MouseUp += new System.Windows.Forms.MouseEventHandler(this.TreeView1_MouseUp); // // contextMenuStrip1 // @@ -112,42 +112,42 @@ private void InitializeComponent() this.removeToolStripMenuItem}); this.contextMenuStrip1.Name = "contextMenuStrip1"; this.contextMenuStrip1.Size = new System.Drawing.Size(148, 114); - this.contextMenuStrip1.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuStrip1_Opening); + this.contextMenuStrip1.Opening += new System.ComponentModel.CancelEventHandler(this.ContextMenuStrip1_Opening); // // importDataStripMenuItem1 // this.importDataStripMenuItem1.Name = "importDataStripMenuItem1"; this.importDataStripMenuItem1.Size = new System.Drawing.Size(147, 22); this.importDataStripMenuItem1.Text = "Import data..."; - this.importDataStripMenuItem1.Click += new System.EventHandler(this.importDataStripMenuItem1_Click); + this.importDataStripMenuItem1.Click += new System.EventHandler(this.ImportDataStripMenuItem1_Click); // // exportDataToolStripMenuItem // this.exportDataToolStripMenuItem.Name = "exportDataToolStripMenuItem"; this.exportDataToolStripMenuItem.Size = new System.Drawing.Size(147, 22); this.exportDataToolStripMenuItem.Text = "Export data..."; - this.exportDataToolStripMenuItem.Click += new System.EventHandler(this.exportDataToolStripMenuItem_Click); + this.exportDataToolStripMenuItem.Click += new System.EventHandler(this.ExportDataToolStripMenuItem_Click); // // addStorageStripMenuItem1 // this.addStorageStripMenuItem1.Name = "addStorageStripMenuItem1"; this.addStorageStripMenuItem1.Size = new System.Drawing.Size(147, 22); this.addStorageStripMenuItem1.Text = "Add storage..."; - this.addStorageStripMenuItem1.Click += new System.EventHandler(this.addStorageStripMenuItem1_Click); + this.addStorageStripMenuItem1.Click += new System.EventHandler(this.AddStorageStripMenuItem1_Click); // // addStreamToolStripMenuItem // this.addStreamToolStripMenuItem.Name = "addStreamToolStripMenuItem"; this.addStreamToolStripMenuItem.Size = new System.Drawing.Size(147, 22); this.addStreamToolStripMenuItem.Text = "Add stream..."; - this.addStreamToolStripMenuItem.Click += new System.EventHandler(this.addStreamToolStripMenuItem_Click); + this.addStreamToolStripMenuItem.Click += new System.EventHandler(this.AddStreamToolStripMenuItem_Click); // // removeToolStripMenuItem // this.removeToolStripMenuItem.Name = "removeToolStripMenuItem"; this.removeToolStripMenuItem.Size = new System.Drawing.Size(147, 22); this.removeToolStripMenuItem.Text = "Remove"; - this.removeToolStripMenuItem.Click += new System.EventHandler(this.removeToolStripMenuItem_Click); + this.removeToolStripMenuItem.Click += new System.EventHandler(this.RemoveToolStripMenuItem_Click); // // saveFileDialog1 // @@ -186,7 +186,7 @@ private void InitializeComponent() this.openFileMenuItem.Name = "openFileMenuItem"; this.openFileMenuItem.Size = new System.Drawing.Size(187, 26); this.openFileMenuItem.Text = "Open..."; - this.openFileMenuItem.Click += new System.EventHandler(this.openFileMenuItem_Click); + this.openFileMenuItem.Click += new System.EventHandler(this.OpenFileMenuItem_Click); // // newStripMenuItem1 // @@ -194,14 +194,14 @@ private void InitializeComponent() this.newStripMenuItem1.Name = "newStripMenuItem1"; this.newStripMenuItem1.Size = new System.Drawing.Size(187, 26); this.newStripMenuItem1.Text = "New Compound File"; - this.newStripMenuItem1.Click += new System.EventHandler(this.newStripMenuItem1_Click); + this.newStripMenuItem1.Click += new System.EventHandler(this.NewStripMenuItem1_Click); // // closeStripMenuItem1 // this.closeStripMenuItem1.Name = "closeStripMenuItem1"; this.closeStripMenuItem1.Size = new System.Drawing.Size(187, 26); this.closeStripMenuItem1.Text = "Close file"; - this.closeStripMenuItem1.Click += new System.EventHandler(this.closeStripMenuItem1_Click); + this.closeStripMenuItem1.Click += new System.EventHandler(this.CloseStripMenuItem1_Click); // // toolStripSeparator2 // @@ -214,14 +214,14 @@ private void InitializeComponent() this.updateCurrentFileToolStripMenuItem.Name = "updateCurrentFileToolStripMenuItem"; this.updateCurrentFileToolStripMenuItem.Size = new System.Drawing.Size(187, 26); this.updateCurrentFileToolStripMenuItem.Text = "Save"; - this.updateCurrentFileToolStripMenuItem.Click += new System.EventHandler(this.updateCurrentFileToolStripMenuItem_Click); + this.updateCurrentFileToolStripMenuItem.Click += new System.EventHandler(this.UpdateCurrentFileToolStripMenuItem_Click); // // saveAsToolStripMenuItem // this.saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem"; this.saveAsToolStripMenuItem.Size = new System.Drawing.Size(187, 26); this.saveAsToolStripMenuItem.Text = "Save As..."; - this.saveAsToolStripMenuItem.Click += new System.EventHandler(this.saveAsToolStripMenuItem_Click); + this.saveAsToolStripMenuItem.Click += new System.EventHandler(this.SaveAsToolStripMenuItem_Click); // // editToolStripMenuItem // @@ -236,7 +236,7 @@ private void InitializeComponent() this.preferencesToolStripMenuItem.Name = "preferencesToolStripMenuItem"; this.preferencesToolStripMenuItem.Size = new System.Drawing.Size(135, 22); this.preferencesToolStripMenuItem.Text = "Preferences"; - this.preferencesToolStripMenuItem.Click += new System.EventHandler(this.preferencesToolStripMenuItem_Click); + this.preferencesToolStripMenuItem.Click += new System.EventHandler(this.PreferencesToolStripMenuItem_Click); // // statusStrip1 // diff --git a/StructuredStorageExplorer/MainForm.cs b/StructuredStorageExplorer/MainForm.cs index 1532099a..f9037e45 100644 --- a/StructuredStorageExplorer/MainForm.cs +++ b/StructuredStorageExplorer/MainForm.cs @@ -185,7 +185,7 @@ private static void AddNodes(TreeNode node, Storage storage) } } - private void exportDataToolStripMenuItem_Click(object sender, EventArgs e) + private void ExportDataToolStripMenuItem_Click(object sender, EventArgs e) { // No export if storage if (treeView1.SelectedNode?.Tag is not NodeSelection selection || selection.EntryInfo.Type is not EntryType.Stream || selection.Parent is null) @@ -211,7 +211,7 @@ private void exportDataToolStripMenuItem_Click(object sender, EventArgs e) } } - private void removeToolStripMenuItem_Click(object sender, EventArgs e) + private void RemoveToolStripMenuItem_Click(object sender, EventArgs e) { if (treeView1.SelectedNode?.Tag is NodeSelection selection && selection.Parent is not null) selection.Parent.Delete(selection.EntryInfo.Name); @@ -219,7 +219,7 @@ private void removeToolStripMenuItem_Click(object sender, EventArgs e) RefreshTree(); } - private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) + private void SaveAsToolStripMenuItem_Click(object sender, EventArgs e) { saveFileDialog1.FilterIndex = 2; if (saveFileDialog1.ShowDialog() == DialogResult.OK) @@ -228,7 +228,7 @@ private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) } } - private void updateCurrentFileToolStripMenuItem_Click(object sender, EventArgs e) + private void UpdateCurrentFileToolStripMenuItem_Click(object sender, EventArgs e) { if (cf is null) return; @@ -238,7 +238,7 @@ private void updateCurrentFileToolStripMenuItem_Click(object sender, EventArgs e cf.Commit(); } - private void addStreamToolStripMenuItem_Click(object sender, EventArgs e) + private void AddStreamToolStripMenuItem_Click(object sender, EventArgs e) { string streamName = string.Empty; @@ -258,7 +258,7 @@ private void addStreamToolStripMenuItem_Click(object sender, EventArgs e) } } - private void addStorageStripMenuItem1_Click(object sender, EventArgs e) + private void AddStorageStripMenuItem1_Click(object sender, EventArgs e) { string storageName = string.Empty; @@ -278,7 +278,7 @@ private void addStorageStripMenuItem1_Click(object sender, EventArgs e) } } - private void importDataStripMenuItem1_Click(object sender, EventArgs e) + private void ImportDataStripMenuItem1_Click(object sender, EventArgs e) { if (openDataFileDialog.ShowDialog() == DialogResult.OK && treeView1.SelectedNode.Tag is CfbStream stream) @@ -295,16 +295,16 @@ private void MainForm_FormClosing(object sender, FormClosingEventArgs e) CloseCurrentFile(); } - private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) + private void ContextMenuStrip1_Opening(object sender, CancelEventArgs e) { } - private void newStripMenuItem1_Click(object sender, EventArgs e) + private void NewStripMenuItem1_Click(object sender, EventArgs e) { CreateNewFile(); } - private void openFileMenuItem_Click(object sender, EventArgs e) + private void OpenFileMenuItem_Click(object sender, EventArgs e) { if (openFileDialog1.ShowDialog() == DialogResult.OK) { @@ -319,7 +319,7 @@ private void openFileMenuItem_Click(object sender, EventArgs e) } } - private void treeView1_MouseUp(object sender, MouseEventArgs e) + private void TreeView1_MouseUp(object sender, MouseEventArgs e) { TreeNode? n = treeView1.GetNodeAt(e.X, e.Y); if (n?.Tag is not NodeSelection nodeSelection) @@ -448,7 +448,7 @@ private void UpdateOleTab(CfbStream stream) } } - private void closeStripMenuItem1_Click(object sender, EventArgs e) + private void CloseStripMenuItem1_Click(object sender, EventArgs e) { if (hexEditor.ByteProvider is not null && hexEditor.ByteProvider.HasChanges() @@ -460,7 +460,7 @@ private void closeStripMenuItem1_Click(object sender, EventArgs e) CloseCurrentFile(); } - private void preferencesToolStripMenuItem_Click(object sender, EventArgs e) + private void PreferencesToolStripMenuItem_Click(object sender, EventArgs e) { using PreferencesForm pref = new(); pref.ShowDialog(); diff --git a/StructuredStorageExplorer/PreferencesForm.Designer.cs b/StructuredStorageExplorer/PreferencesForm.Designer.cs index 9d6f703c..98b9e3fe 100644 --- a/StructuredStorageExplorer/PreferencesForm.Designer.cs +++ b/StructuredStorageExplorer/PreferencesForm.Designer.cs @@ -63,7 +63,7 @@ private void InitializeComponent() this.btnSavePreferences.TabIndex = 2; this.btnSavePreferences.Text = "OK"; this.btnSavePreferences.UseVisualStyleBackColor = true; - this.btnSavePreferences.Click += new System.EventHandler(this.btnSavePreferences_Click); + this.btnSavePreferences.Click += new System.EventHandler(this.BtnSavePreferences_Click); // // btnCancelPreferences // @@ -73,7 +73,7 @@ private void InitializeComponent() this.btnCancelPreferences.TabIndex = 3; this.btnCancelPreferences.Text = "Cancel"; this.btnCancelPreferences.UseVisualStyleBackColor = true; - this.btnCancelPreferences.Click += new System.EventHandler(this.btnCancelPreferences_Click); + this.btnCancelPreferences.Click += new System.EventHandler(this.BtnCancelPreferences_Click); // // PreferencesForm // diff --git a/StructuredStorageExplorer/PreferencesForm.cs b/StructuredStorageExplorer/PreferencesForm.cs index 66db24e7..d32d95a4 100644 --- a/StructuredStorageExplorer/PreferencesForm.cs +++ b/StructuredStorageExplorer/PreferencesForm.cs @@ -9,7 +9,7 @@ public PreferencesForm() InitializeComponent(); } - private void btnSavePreferences_Click(object sender, EventArgs e) + private void BtnSavePreferences_Click(object sender, EventArgs e) { Settings.Default.EnableValidation = cbEnableValidation.Checked; Settings.Default.Save(); @@ -17,7 +17,7 @@ private void btnSavePreferences_Click(object sender, EventArgs e) Close(); } - private void btnCancelPreferences_Click(object sender, EventArgs e) + private void BtnCancelPreferences_Click(object sender, EventArgs e) { cbEnableValidation.Checked = Settings.Default.EnableValidation; DialogResult = DialogResult.Cancel; diff --git a/StructuredStorageExplorer/Utils.cs b/StructuredStorageExplorer/Utils.cs index def2be8c..0d610efb 100644 --- a/StructuredStorageExplorer/Utils.cs +++ b/StructuredStorageExplorer/Utils.cs @@ -25,7 +25,7 @@ public static DialogResult InputBox(string title, string promptText, ref string buttonCancel.SetBounds(309, 72, 75, 23); label.AutoSize = true; - textBox.Anchor = textBox.Anchor | AnchorStyles.Right; + textBox.Anchor |= AnchorStyles.Right; buttonOK.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; From a4b5e609ec3eccb83ab3720e7f683c26b538536d Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 13:50:08 +1300 Subject: [PATCH 127/134] Remove .globalconfig --- .globalconfig | 186 ---------------------------------- OpenMcdf.sln | 1 - OpenMcdf/System/.globalconfig | 3 - 3 files changed, 190 deletions(-) delete mode 100644 .globalconfig delete mode 100644 OpenMcdf/System/.globalconfig diff --git a/.globalconfig b/.globalconfig deleted file mode 100644 index 8be6656a..00000000 --- a/.globalconfig +++ /dev/null @@ -1,186 +0,0 @@ -# Remove the line below if you want to inherit .editorconfig settings from higher directories -root = true - -dotnet_analyzer_diagnostic.category-Design.severity = warning -dotnet_analyzer_diagnostic.category-Interoperability.severity = suggestion -dotnet_analyzer_diagnostic.category-Maintainability.severity = warning -dotnet_analyzer_diagnostic.category-Naming.severity = warning -dotnet_analyzer_diagnostic.category-Performance.severity = warning -dotnet_analyzer_diagnostic.category-Reliability.severity = warning -dotnet_analyzer_diagnostic.category-Style.severity = warning - -dotnet_analyzer_diagnostic.category-StyleCop.CSharp.DocumentationRules.severity = none - -# IDE0010: Populate switch -dotnet_diagnostic.IDE0010.severity = none - -# IDE0011: Add braces to 'if' statement -dotnet_diagnostic.IDE0011.severity = suggestion - -# IDE0022: Use expression body for method -dotnet_diagnostic.IDE0022.severity = suggestion - -# IDE0025: Use expression body for property -dotnet_diagnostic.IDE0025.severity = suggestion - -# IDE0028: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0028) -dotnet_diagnostic.IDE0028.severity = none - -# IDE0028: Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0028) -dotnet_diagnostic.IDE0030.severity = suggestion - -# IDE0040: Accessibility modifiers required (disabled on build) -dotnet_diagnostic.IDE0040.severity = none - -# IDE0045: Use conditional expression for assignment -dotnet_diagnostic.IDE0045.severity = suggestion - -# IDE0046: Use conditional expression for return -dotnet_diagnostic.IDE0046.severity = none - -# IDE0047: Parentheses can be removed -dotnet_diagnostic.IDE0047.severity = suggestion - -# IDE0052: Remove unread private member -dotnet_diagnostic.IDE0052.severity = warning - -# IDE0058: Expression value is never used -dotnet_diagnostic.IDE0058.severity = suggestion - -# IDE0060: Remove unused parameter -dotnet_diagnostic.IDE0060.severity = suggestion - -# IDE0072: Populate switch -dotnet_diagnostic.IDE0072.severity = none - -# IDE0130: Namespace does not match folder structure -dotnet_diagnostic.IDE0130.severity = suggestion - -# IDE0251: Make member readonly -dotnet_diagnostic.IDE0251.severity = warning - -# IDE0270: Null check can be simplified -dotnet_diagnostic.IDE0270.severity = suggestion - -# IDE0290: Use primary constructor -dotnet_diagnostic.IDE0290.severity = suggestion - -# IDE0300: Collection initialization can be simplified -dotnet_diagnostic.IDE0300.severity = suggestion - -# IDE0300: Collection initialization can be simplified (false positive on empty arrays) -dotnet_diagnostic.IDE0301.severity = suggestion - -# IDE0303: Collection initialization can be simplified -dotnet_diagnostic.IDE0302.severity = suggestion - -# IDE0303: Collection initialization can be simplified -dotnet_diagnostic.IDE0303.severity = suggestion - -# IDE0305: Collection initialization can be simplified -dotnet_diagnostic.IDE0305.severity = suggestion - -#CA1008: Enums should have zero value -dotnet_diagnostic.CA1008.severity = suggestion - -# CA1028: Enum storage should be Int32 -dotnet_diagnostic.CA1028.severity = suggestion - -# CA1031: Do not catch general exception types -dotnet_diagnostic.CA1031.severity = suggestion - -# CA1051: Do not declare visible instance fields -dotnet_diagnostic.CA1051.severity = none - -# CA1062: Validate arguments of public methods -dotnet_diagnostic.CA1062.severity = none - -# CA1304: Specify CultureInfo -dotnet_diagnostic.CA1304.severity = suggestion - -# CA1305: Specify IFormatProvider -dotnet_diagnostic.CA1305.severity = suggestion - -# CA1308: Normalize strings to uppercase -dotnet_diagnostic.CA1308.severity = suggestion - -# CA1309: Use ordinal string comparison -dotnet_diagnostic.CA1309.severity = suggestion - -# CA1416: Validate platform compatibility -dotnet_diagnostic.CA1416.severity = none - -# CA1848: Use the LoggerMessage delegates -dotnet_diagnostic.CA1848.severity = none - -# CA1711: Identifiers should not have incorrect suffix -dotnet_code_quality.CA1711.allowed_suffixes = Flag|Flags - -# CA1721: Property names should not match get methods -dotnet_diagnostic.CA1721.severity = suggestion - -# CA1724: Type names should not match namespaces -dotnet_diagnostic.CA1724.severity = suggestion - -# CA1814: Prefer jagged arrays over multidimensional -dotnet_diagnostic.CA1814.severity = suggestion - -# CA1826: Use property instead of Linq Enumerable method -dotnet_code_quality.CA1826.exclude_ordefault_methods = true - -# CA1863: Use 'CompositeFormat' -dotnet_diagnostic.CA1863.severity = none - -# CA2007: Do not directly await a Task -dotnet_diagnostic.CA2007.severity = suggestion - -# CA2008: Do not create tasks without passing a TaskScheduler -dotnet_diagnostic.CA2008.severity = suggestion - -# CA2109: Review visible event handlers -dotnet_diagnostic.CA2109.severity = none - -# CA2229: Implement serialization constructors -dotnet_diagnostic.CA2229.severity = none - -# CA2300: Do not use insecure deserializer BinaryFormatter -dotnet_diagnostic.CA2300.severity = none - -# CA2302: Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize -dotnet_diagnostic.CA2302.severity = suggestion - -# CA5392: Use DefaultDllImportSearchPaths attribute for P/Invokes -dotnet_diagnostic.CA5392.severity = suggestion - -# CA5393: Do not use unsafe DllImportSearchPath value -dotnet_diagnostic.CA5393.severity = none - -# CA5394: Do not use insecure randomness -dotnet_diagnostic.CA5394.severity = suggestion - -# CS1591: Missing XML comment for publicly visible type or member -dotnet_diagnostic.CS1591.severity = none - -# MSTEST0015: Test method should not be ignored -dotnet_diagnostic.MSTEST0015.severity = none - -# SYSLIB0011: Type or member is obsolete -dotnet_diagnostic.SYSLIB0011.severity = none - -# SA0001: XML comment analysis disabled -dotnet_diagnostic.SA0001.severity = none - -# SA1215: An instance readonly element is positioned beneath an instance non-readonly element of the same type -dotnet_diagnostic.SA1215.severity = warning - -# SA1515: Single-line comment should be preceded by blank line -dotnet_diagnostic.SA1515.severity = suggestion - -# SA1628: Documentation text should begin with a capital letter -dotnet_diagnostic.SA1628.severity = warning - -# SA1201: Elements must appear in the correct order -dotnet_diagnostic.SA1201.severity = suggestion - -# SYSLIB1045: Use GeneratedRegexAttribute to generate the regular expression implementation at compile time -dotnet_diagnostic.SYSLIB1045.severity = suggestion diff --git a/OpenMcdf.sln b/OpenMcdf.sln index 9315355d..cd501aa3 100644 --- a/OpenMcdf.sln +++ b/OpenMcdf.sln @@ -10,7 +10,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{34030FA7-0A06-43D1-85DD-ADD39D502C3C}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - .globalconfig = .globalconfig Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets Directory.csproj.props = Directory.csproj.props diff --git a/OpenMcdf/System/.globalconfig b/OpenMcdf/System/.globalconfig deleted file mode 100644 index 54c4c5b9..00000000 --- a/OpenMcdf/System/.globalconfig +++ /dev/null @@ -1,3 +0,0 @@ -# Remove the line below if you want to inherit .editorconfig settings from higher directories - -dotnet_analyzer_diagnostic.severity = none \ No newline at end of file From 5a068c8142df36a07bfcf3fcee42441084399393 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 14:04:52 +1300 Subject: [PATCH 128/134] Add README.md --- OpenMcdf.sln | 1 + README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 README.md diff --git a/OpenMcdf.sln b/OpenMcdf.sln index cd501aa3..0bf29dc3 100644 --- a/OpenMcdf.sln +++ b/OpenMcdf.sln @@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.csproj.props = Directory.csproj.props Directory.csproj.targets = Directory.csproj.targets exclusion.dic = exclusion.dic + README.md = README.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Benchmarks", "OpenMcdf.Benchmarks\OpenMcdf.Benchmarks.csproj", "{44C718AD-F7FE-4733-80A8-636E5E7E63F3}" diff --git a/README.md b/README.md new file mode 100644 index 00000000..a62555d6 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +[![Build Status](https://fb8.visualstudio.com/Openmcdf/_apis/build/status/Openmcdf-CI?branchName=master)](https://fb8.visualstudio.com/Openmcdf/_build/latest?definitionId=1&branchName=master) +![GitHub Actions](https://github.com/ironfede/openmcdf/actions/workflows/dotnet.yml/badge.svg) + +# OpenMcdf + +OpenMcdf is a 100% .NET / C# component that allows developers to manipulate [Compound File Binary File Format](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-cfb/53989ce4-7b05-4f8d-829b-d08d6148375b) files (also known as OLE structured storage). + +Compound file includes multiple streams of information (document summary, user data) in a single container. + +This file format is used under the hood by a lot of applications: all the documents created by Microsoft Office until the 2007 product release are structured storage files. Windows thumbnails cache files (thumbs.db) are compound documents as well as .msg Outlook messages. Visual Studio .suo files (solution options) are compound files and a lot of audio/video editing tools save project file in a compound container (*.aaf files for example). + +OpenMcdf supports read/write operations on streams and storages and traversal of structures tree. It supports version 3 and 4 of the specifications, uses lazy loading wherever possible to reduce memory usage and offer an intuitive API to work with structured files. + +It's very easy to **create a new compound file** + +```C# +byte[] b = new byte[10000]; + +using var root = RootStorage.Create("test.cfb"); +using CfbStream stream = root.CreateStream("MyStream"); +stream.Write(b, 0, b.Length); +``` + +You can **open an existing one**, an Excel workbook (.xls) and use its main data stream + +```C# +using var root = RootStorage.OpenRead("report.xls"); +using CfbStream workbookStream = root.OpenStream("Workbook"); +``` + +Adding **storage and stream items** is just as easy... + +```C# +using var root = RootStorage.Create("test.cfb"); +root.AddStorage("MyStorage"); +root.AddStream("MyStream"); +``` +...as removing them + +```C# +root.Delete("MyStream"); +``` + +For transacted storages, changes can either be committed or reverted: + +```C# +using var root = RootStorage.Create("test.cfb", StorageModeFlags.Transacted); +root.Commit(); +// +root.Revert(); +``` + +If you need to compress a compound file, you can purge its unused space: + +```C# +root.Flush(consolidate: true); +``` + +[OLE Properties](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-oleps/bf7aeae8-c47a-4939-9f45-700158dac3bc) handling for DocumentSummaryInfo`` and SummaryInfo streams +is available via extension methods ***(experimental - API subjected to changes)*** + +```C# +PropertySetStream mgr = ((CFStream)target).AsOLEProperties(); +for (int i = 0; i < mgr.PropertySet0.NumProperties; i++) +{ + ITypedPropertyValue p = mgr.PropertySet0.Properties[i]; + ... +``` + +OpenMcdf runs happily on the [Mono](http://www.mono-project.com/) platform and multi-targets **netstandard2.0** and **net8.0** to allow maximum client compatibility. From 2ea038cc7efde8fc76ce00cebe1e354c3deb974c Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 14:05:43 +1300 Subject: [PATCH 129/134] Add Mozilla license --- License.txt | 373 +++++++++++++++++++++++++++++++++++++++++++++++++++ OpenMcdf.sln | 1 + 2 files changed, 374 insertions(+) create mode 100644 License.txt diff --git a/License.txt b/License.txt new file mode 100644 index 00000000..fa0086a9 --- /dev/null +++ b/License.txt @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/OpenMcdf.sln b/OpenMcdf.sln index 0bf29dc3..7ee72bec 100644 --- a/OpenMcdf.sln +++ b/OpenMcdf.sln @@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.csproj.props = Directory.csproj.props Directory.csproj.targets = Directory.csproj.targets exclusion.dic = exclusion.dic + License.txt = License.txt README.md = README.md EndProjectSection EndProject From e150e4c11faaaf9e8a45ee62285e4753f8a9291c Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 14:21:44 +1300 Subject: [PATCH 130/134] Add NuGet packaging configuration --- Directory.csproj.props | 31 +++++++++++++++++++++++++++++++ OpenMcdf.Ole/OpenMcdf.Ole.csproj | 12 +++++++++++- OpenMcdf.Ole/icon.png | Bin 0 -> 1569 bytes OpenMcdf/OpenMcdf.csproj | 12 ++++++++++-- OpenMcdf/icon.png | Bin 0 -> 1569 bytes 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 OpenMcdf.Ole/icon.png create mode 100644 OpenMcdf/icon.png diff --git a/Directory.csproj.props b/Directory.csproj.props index adca077d..c636f96e 100644 --- a/Directory.csproj.props +++ b/Directory.csproj.props @@ -8,6 +8,37 @@ true + + + 3.0.0.0 + $(Version)-preview1 + ironfede,jpowell + Copyright © 2010-2024, Federico Blaseotto, Jeremy Powell + https://github.com/ironfede/openmcdf + icon.png + README.md + https://github.com/ironfede/openmcdf + git + + MPL-2.0 + True + True + snupkg + + + + + + True + \ + + + + True + \ + + + latest diff --git a/OpenMcdf.Ole/OpenMcdf.Ole.csproj b/OpenMcdf.Ole/OpenMcdf.Ole.csproj index c2f72f10..17660fee 100644 --- a/OpenMcdf.Ole/OpenMcdf.Ole.csproj +++ b/OpenMcdf.Ole/OpenMcdf.Ole.csproj @@ -1,9 +1,19 @@  - + netstandard2.0;net8.0 + + True + OpenMcdf OLE + + OpenMcdf is a 100% .NET / C# component that allows developers to manipulate Microsoft Compound Document File Format for OLE structured storage. + It supports read/write operations on streams and storages and traversal of directory trees. + + MS-CFB Compound File Binary File Format Structured Storage OLE Object Linking and Embedding Property Set Data Structures + + diff --git a/OpenMcdf.Ole/icon.png b/OpenMcdf.Ole/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..423a40b9347a570f664f92246996639cebe1ebd1 GIT binary patch literal 1569 zcmV++2HyFJP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGqB>(^xB>_oNB=7(L1-3~Byjjmj{Gchq?AtoA^uvJW8feDES8Dm5i5Ht`H660qaB21hJ3^D_Z0r`YL1~x1R zgv~g{2+V{(C?5sn-_5t7yMg|4rp~2nz@!JOinKKgPjXYQUcIV*=iT4Cuft#f4Gj&5 zjg3W8QWBDrld-e26L|9Vp_@QUOA8Vb65#jy<-W7C6VcJp(pIs%y9>A59k|=a_V#w5 z^*Mr#)e7ANjvqgc&CN||d%a${Uc7h_tE;QhA0Ho&r%#{CwWOp(o_jnVWMyUH*|TTJ z%gd8Km&=7~*RG+sxER;3U&q6T52ar(bQ3sv@+8*Q)^O#@6`VM60*i}_^5cDdec0I8 z!1?p%MLX02pL2C1NIf0QtN=gdm=H~GD@nd9UWFR*;7ung_GLFQqUcD;gNZ`VS z3)0pL-2~Fo(!`0W1+Kc&>6F#uB&dy>H*W?;0ud1rVm_s%rD$z!4fO7T?;Rv?_Uu_{ z>xFItwEM)wL=+Vjp}DzPOlfd%P@a*%>C>mt)YK$qRZvhM{akI1qZz$;@j`rseU~p^ zmggjJ=FAys>xFItD#pgf@aom8&msuT=RY%XdiNphN4ykqB@iUjic!Bi7{P zBqAdtCA@O=&z(Dmva&K6Ljqd96%0orz^FqLIZU|v6B85Y@9#%?dOGghxg&Yv)~#Ep ztE-c5nMQy%&8WjRuLni#+90C*=+xA&GjPW1VW8( z1k6Ur2{7i<&M9Cz=hYP5ZppQaPg@}pdBo?g_)5}A;8r$8{Y_+jgS-Y`+e~FJn;EFN`1g% z7-n3CSg-W?72y>ycA61#0-N?(*fxi;y)%mO$!ffQ`vczo^#t~<*Kqp=;qvrg%hjbs zq^-ak`VevgPNx+P=L9_7CH(kPA_j(wFg)@c-{;1`qY&`zj>7HjQ?wNPzk@H~NCdQm zQM<#UXkij=j|IP!rC`n0uJk(bTg7*nnyFF@XIjxhw>bouVR=-3m{_c1iU#K3a$E6p zNeWii|4`nx-0hOHIrgfCs#+&N%V%@^aAzYXP+;rk1oUMlc+S0E=^CKf3Hg?c>U` zoMckV&(D{C=c}r!q=@0_bLCYtG6KT{2u_Osyc{O1zF1OnEH%IrX;xu88#0}M7N{mf zGKGbOXl!hhTA26LAScgzE#?>uAtx|1GlQO<9?=4YH8GFeSu2wqHA8bU=ET51HW<3< T+MFJZ00000NkvXXu0mjfrJeG{ literal 0 HcmV?d00001 diff --git a/OpenMcdf/OpenMcdf.csproj b/OpenMcdf/OpenMcdf.csproj index 6d746033..7df5bc53 100644 --- a/OpenMcdf/OpenMcdf.csproj +++ b/OpenMcdf/OpenMcdf.csproj @@ -2,8 +2,16 @@ netstandard2.0;net8.0 - 3.0.0.0 - ironfede,Jeremy Powell + + + + True + OpenMcdf + + OpenMcdf is a 100% .NET / C# component that allows developers to manipulate Microsoft Compound Document File Format for OLE structured storage. + It supports read/write operations on streams and storages and traversal of directory trees. + + MS-CFB Compound File Binary File Format Structured Storage diff --git a/OpenMcdf/icon.png b/OpenMcdf/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..423a40b9347a570f664f92246996639cebe1ebd1 GIT binary patch literal 1569 zcmV++2HyFJP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGqB>(^xB>_oNB=7(L1-3~Byjjmj{Gchq?AtoA^uvJW8feDES8Dm5i5Ht`H660qaB21hJ3^D_Z0r`YL1~x1R zgv~g{2+V{(C?5sn-_5t7yMg|4rp~2nz@!JOinKKgPjXYQUcIV*=iT4Cuft#f4Gj&5 zjg3W8QWBDrld-e26L|9Vp_@QUOA8Vb65#jy<-W7C6VcJp(pIs%y9>A59k|=a_V#w5 z^*Mr#)e7ANjvqgc&CN||d%a${Uc7h_tE;QhA0Ho&r%#{CwWOp(o_jnVWMyUH*|TTJ z%gd8Km&=7~*RG+sxER;3U&q6T52ar(bQ3sv@+8*Q)^O#@6`VM60*i}_^5cDdec0I8 z!1?p%MLX02pL2C1NIf0QtN=gdm=H~GD@nd9UWFR*;7ung_GLFQqUcD;gNZ`VS z3)0pL-2~Fo(!`0W1+Kc&>6F#uB&dy>H*W?;0ud1rVm_s%rD$z!4fO7T?;Rv?_Uu_{ z>xFItwEM)wL=+Vjp}DzPOlfd%P@a*%>C>mt)YK$qRZvhM{akI1qZz$;@j`rseU~p^ zmggjJ=FAys>xFItD#pgf@aom8&msuT=RY%XdiNphN4ykqB@iUjic!Bi7{P zBqAdtCA@O=&z(Dmva&K6Ljqd96%0orz^FqLIZU|v6B85Y@9#%?dOGghxg&Yv)~#Ep ztE-c5nMQy%&8WjRuLni#+90C*=+xA&GjPW1VW8( z1k6Ur2{7i<&M9Cz=hYP5ZppQaPg@}pdBo?g_)5}A;8r$8{Y_+jgS-Y`+e~FJn;EFN`1g% z7-n3CSg-W?72y>ycA61#0-N?(*fxi;y)%mO$!ffQ`vczo^#t~<*Kqp=;qvrg%hjbs zq^-ak`VevgPNx+P=L9_7CH(kPA_j(wFg)@c-{;1`qY&`zj>7HjQ?wNPzk@H~NCdQm zQM<#UXkij=j|IP!rC`n0uJk(bTg7*nnyFF@XIjxhw>bouVR=-3m{_c1iU#K3a$E6p zNeWii|4`nx-0hOHIrgfCs#+&N%V%@^aAzYXP+;rk1oUMlc+S0E=^CKf3Hg?c>U` zoMckV&(D{C=c}r!q=@0_bLCYtG6KT{2u_Osyc{O1zF1OnEH%IrX;xu88#0}M7N{mf zGKGbOXl!hhTA26LAScgzE#?>uAtx|1GlQO<9?=4YH8GFeSu2wqHA8bU=ET51HW<3< T+MFJZ00000NkvXXu0mjfrJeG{ literal 0 HcmV?d00001 From 81b945f6c3cc12736a38fbec6f39ace33daf8cf3 Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 14:56:25 +1300 Subject: [PATCH 131/134] Add MIT license to System folder --- OpenMcdf/System/LICENSE.TXT | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 OpenMcdf/System/LICENSE.TXT diff --git a/OpenMcdf/System/LICENSE.TXT b/OpenMcdf/System/LICENSE.TXT new file mode 100644 index 00000000..984713a4 --- /dev/null +++ b/OpenMcdf/System/LICENSE.TXT @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 0ff2dc8010c7ae53bc044fcb11672d337685401a Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 15:06:57 +1300 Subject: [PATCH 132/134] Add release notes --- OpenMcdf.sln | 1 + ReleaseNotes.md | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 ReleaseNotes.md diff --git a/OpenMcdf.sln b/OpenMcdf.sln index 7ee72bec..6024b522 100644 --- a/OpenMcdf.sln +++ b/OpenMcdf.sln @@ -17,6 +17,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution exclusion.dic = exclusion.dic License.txt = License.txt README.md = README.md + ReleaseNotes.md = ReleaseNotes.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Benchmarks", "OpenMcdf.Benchmarks\OpenMcdf.Benchmarks.csproj", "{44C718AD-F7FE-4733-80A8-636E5E7E63F3}" diff --git a/ReleaseNotes.md b/ReleaseNotes.md new file mode 100644 index 00000000..f45776bd --- /dev/null +++ b/ReleaseNotes.md @@ -0,0 +1,88 @@ +## 3.0.0-preview1 +* Multi-targeting for netstandard2.0 and net8.0 +* Improved performance +* Reduced memory usage +* Idiomatic C# API +* Idiomatic exception hierarchy +* Nullable warnings and annotations +* 16 TB files support +* Transacted root storage support +* Optional consolidation +* Switching streams (i.e. save as) +* Storage/stream full path discovery + +## 2.2 +* ADD: NET Standard 2.0 platform support + +## 2.1 +* Migration to GitHub +* FIXED: Issues with failed initialization to FREESECT (0xFFFFFFFF) of FAT sectors +* FIXED: Issues with file-corruption detection + +## 2.0 - Stable release. +* Last SourceForge release + +## 2.0 pre-release (NOT FOR PRODUCTION) +* ADD: Red-Black tree full implementation to speed up large data structure read access (thousands of stream/storage objects) +* ADD: Enhanced Stream resizing +* ADD: Extensions to use native .net framework Stream object +* ADD: Code has been ported to .net 4.0 framework +* NOTE: This is a technical preview not aimed to production use. + +## 1.5.4 +* FIXED: In particular conditions, an opened file could be left opened after a loading exception +* FIXED: Circular references of corrupted files could lead to stack overflows +* FIXED: Enhanced standard compliance: corrupted file loading defaults to abort operation. +* ADD: Version property +* ADD: New overloaded constructors to force the load of possibly corrupted files. + +## 1.5.3 +* ADD: 'GetAllNamedEntries' Method to access structured files without tree-loading performance penalties +* ADD: New hex editor for structured storage explorer sample application + +## 1.5.2 +* FIXED: Math error in sector number recognition caused exception when reading some streams +* FIXED: Saving twice caused OutOfMemoryException +* FIXED: Error when using names of exactly 31 characters for streams or storages + +## 1.5.1. +* FIXED: Casting error when removing uncommitted-added Stream. +* ADDED: CFDuplicatedItem exception thrown when trying to add duplicated items (previously item addition was silently failing). + +## 1.5.0 +* FIXED: Exception thrown when removing a stream of length equals to zero. + +## 1.5.0 - RC1 +* ADD: New Update mode to commit changes to the underlying stream +* ADD: Sector recycle to reuse unallocated sectors +* ADD: File shrinking to compact compound files +* ADD: Support for version 4 of specs (4096 bytes sectors) +* ADD: Partial stream data reading to read data from a specified offset +* ADD: Advanced lazy loading to reduce memory footprint of application +* FIXED: CHANGED NAMESPACE to OpenMcdf + +## 1.4.1 +* FIXED: ERROR, internal modifier applied to Delete method +* FIXED: Redundant method call for DIFAT chain +* ADD: 'Delete' feature for sample project + +## 1.4.0 +* ADD: 'Remove' feature for storage and stream objects. +* FIXED: ERROR in manipulation of streams with a length of 4096 bytes (cutoff bug) (Thx to meddingt) +* FIXED: ERROR in zero sized streams + +## 1.3.1 +* FIXED: Error in DIFAT sectors manipulation + +## 1.3 +* FIXED: Null pointer in traversal with empty storages; + +## 1.2 +* FIXED: Fixed ministream (<4096 bytes) bug; + +## 1.1 +* ADD: Added traversal of Compound file method (VisitEntries); +* FIXED: Fixed bug when multiple storage added; + +## 1.0 +* Initial release From 7208926fbebcc53a10a49747824d67686d520c2d Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 15:21:08 +1300 Subject: [PATCH 133/134] Update Nuget Authors --- Directory.csproj.props | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Directory.csproj.props b/Directory.csproj.props index c636f96e..e96fe54d 100644 --- a/Directory.csproj.props +++ b/Directory.csproj.props @@ -12,7 +12,7 @@ 3.0.0.0 $(Version)-preview1 - ironfede,jpowell + ironfede,jeremy-visionaid Copyright © 2010-2024, Federico Blaseotto, Jeremy Powell https://github.com/ironfede/openmcdf icon.png @@ -26,7 +26,6 @@ snupkg - True From 4a195964bff6c6d8183098310d6206417949f66b Mon Sep 17 00:00:00 2001 From: Jeremy Powell Date: Thu, 14 Nov 2024 15:49:32 +1300 Subject: [PATCH 134/134] Prepare for 3.0 --- .editorconfig | 277 -- .github/workflows/dotnet.yml | 44 - .gitignore | 363 --- Directory.Build.props | 4 - Directory.Build.targets | 3 - Directory.csproj.props | 16 - Directory.csproj.targets | 3 - License.txt | 373 --- MakeRelease.cmd | 1 - OpenMcdf.sln | 124 - OpenMcdf.sln.DotSettings | 2 - OpenMcdf.snk | Bin 596 -> 0 bytes README.md | 77 - Release notes.txt | 94 - Support.txt | 5 - azure-pipelines.yml | 37 - docs/img/LOGO_OpenMcdf.png | Bin 22388 -> 0 bytes docs/img/LOGO_OpenMcdf_H.png | Bin 16575 -> 0 bytes docs/img/PartialStream.PNG | Bin 4415 -> 0 bytes docs/img/read_stream.PNG | Bin 7217 -> 0 bytes docs/img/structured_storage.png | Bin 37667 -> 0 bytes docs/img/visit_entries.PNG | Bin 10196 -> 0 bytes docs/img/write_stream.PNG | Bin 7368 -> 0 bytes docs/index.htm | 219 -- docs/main.css | 133 - exclusion.dic | 14 - sources/Html Help/OpenMcdfHelp.shfbproj | 80 - .../OpenMcdf.Extensions/CFStreamExtensions.cs | 31 - .../OLEProperties/Common.cs | 43 - .../OLEProperties/DictionaryEntry.cs | 63 - .../OLEProperties/DictionaryProperty.cs | 136 - .../Interfaces/IBinarySerializable.cs | 10 - .../Interfaces/IDictionaryProperty.cs | 6 - .../OLEProperties/Interfaces/IProperty.cs | 16 - .../Interfaces/ITypedPropertyValue.cs | 21 - .../OLEProperties/OLEPropertiesContainer.cs | 363 --- .../OLEProperties/OLEProperty.cs | 65 - .../OLEProperties/PropertyFactory.cs | 690 ----- .../PropertyIdentifierAndOffset.cs | 23 - .../OLEProperties/PropertyReader.cs | 34 - .../OLEProperties/PropertySet.cs | 38 - .../OLEProperties/PropertySetStream.cs | 242 -- .../OLEProperties/ProperyIdentifiers.cs | 88 - .../OLEProperties/TypedPropertyValue.cs | 161 -- .../OLEProperties/VTPropertyType.cs | 42 - .../OLEProperties/VTVectorHeader.cs | 3 - .../OlePropertiesExtensions.cs | 27 - .../OpenMcdf.Extensions.csproj | 94 - .../OpenMcdf.Extensions.csproj.user | 6 - sources/OpenMcdf.Extensions/OpenMcdf.snk | Bin 596 -> 0 bytes .../Properties/AssemblyInfo.cs | 40 - .../OpenMcdf.Extensions/StreamDecorator.cs | 125 - sources/OpenMcdf/BinaryPrimitives.cs | 39 - sources/OpenMcdf/CFException.cs | 214 -- sources/OpenMcdf/CFItem.cs | 223 -- sources/OpenMcdf/CFStorage.cs | 547 ---- sources/OpenMcdf/CFStream.cs | 234 -- sources/OpenMcdf/CompoundFile.cs | 2568 ----------------- sources/OpenMcdf/DirectoryEntry.cs | 445 --- sources/OpenMcdf/Header.cs | 195 -- sources/OpenMcdf/IDirectoryEntry.cs | 37 - sources/OpenMcdf/OpenMcdf.csproj | 115 - sources/OpenMcdf/OpenMcdf.snk | Bin 596 -> 0 bytes sources/OpenMcdf/OpenMcdfClassDiagram.cd | 140 - sources/OpenMcdf/Properties/AssemblyInfo.cs | 37 - sources/OpenMcdf/RBTree/IRBNode.cs | 33 - sources/OpenMcdf/RBTree/RBTree.cs | 438 --- sources/OpenMcdf/RBTree/RBTreeEnumerator.cs | 55 - sources/OpenMcdf/Sector.cs | 124 - sources/OpenMcdf/SectorCollection.cs | 161 -- sources/OpenMcdf/StreamExtensions.cs | 24 - sources/OpenMcdf/StreamRW.cs | 137 - sources/OpenMcdf/StreamView.cs | 328 --- sources/SharedAssemblyInfo.cs | 8 - .../Structured Storage Explorer/InputBox.cs | 53 - .../MainForm.Designer.cs | 466 --- .../Structured Storage Explorer/MainForm.cs | 502 ---- .../Structured Storage Explorer/MainForm.resx | 141 - .../Structured Storage Explorer/OpenMcdf.snk | Bin 596 -> 0 bytes .../PreferencesForm.Designer.cs | 106 - .../PreferencesForm.cs | 34 - .../PreferencesForm.resx | 120 - .../Structured Storage Explorer/Program.cs | 19 - .../Properties/AssemblyInfo.cs | 34 - .../Properties/Resources.Designer.cs | 123 - .../Properties/Resources.resx | 139 - .../Properties/Settings.Designer.cs | 50 - .../Properties/Settings.settings | 12 - .../StreamDataProvider.cs | 158 - .../StructuredStorageExplorer.csproj | 74 - sources/Structured Storage Explorer/Utils.cs | 57 - .../Structured Storage Explorer/app.config | 18 - .../Structured Storage Explorer/img/disk.png | Bin 620 -> 0 bytes .../img/door_out.png | Bin 688 -> 0 bytes .../img/folder.png | Bin 537 -> 0 bytes .../img/page_white.png | Bin 294 -> 0 bytes .../img/storage.png | Bin 396 -> 0 bytes .../img/stream.png | Bin 409 -> 0 bytes sources/Test/OpenMcdf.Benchmark/Extensions.cs | 40 - sources/Test/OpenMcdf.Benchmark/InMemory.cs | 99 - .../OpenMcdf.Benchmark.csproj | 23 - sources/Test/OpenMcdf.Benchmark/Program.cs | 12 - .../CFSStreamExtensionsTest.cs | 140 - .../Test/OpenMcdf.Extensions.Test/Helpers.cs | 26 - .../OLEPropertiesExtensionsTest.cs | 551 ---- .../OpenMcdf.Extensions.Test.csproj | 31 - .../Properties/AssemblyInfo.cs | 34 - .../OpenMcdf.MemTest/OpenMcdf.MemTest.csproj | 46 - sources/Test/OpenMcdf.MemTest/Program.cs | 290 -- .../Properties/AssemblyInfo.cs | 34 - sources/Test/OpenMcdf.MemTest/app.config | 3 - .../Test/OpenMcdf.MemTest/testfile/report.xls | Bin 16896 -> 0 bytes .../testfile/reportOverwriteMultiple.xls | Bin 1832960 -> 0 bytes sources/Test/OpenMcdf.PerfTest/Helpers.cs | 49 - .../OpenMcdf.PerfTest.csproj | 43 - sources/Test/OpenMcdf.PerfTest/Program.cs | 39 - .../Properties/AssemblyInfo.cs | 35 - sources/Test/OpenMcdf.PerfTest/app.config | 3 - sources/Test/OpenMcdf.Test/AuthoringTests.txt | 136 - sources/Test/OpenMcdf.Test/CFStorageTest.cs | 335 --- sources/Test/OpenMcdf.Test/CFStreamTest.cs | 1051 ------- .../Test/OpenMcdf.Test/CompoundFileTest.cs | 1094 ------- sources/Test/OpenMcdf.Test/Helpers.cs | 36 - .../Test/OpenMcdf.Test/OpenMcdf.Test.csproj | 93 - sources/Test/OpenMcdf.Test/OpenMcdf.snk | Bin 596 -> 0 bytes .../OpenMcdf.Test/Properties/AssemblyInfo.cs | 19 - sources/Test/OpenMcdf.Test/RBTreeTest.cs | 195 -- .../OpenMcdf.Test/SectorCollectionTest.cs | 151 - sources/Test/OpenMcdf.Test/StreamRWTest.cs | 55 - sources/Test/TestFiles/2_MB-W.ppt | Bin 2037248 -> 0 bytes sources/Test/TestFiles/2custom.doc | Bin 27136 -> 0 bytes .../64-67.numberOfMiniFATSectors.docx | Bin 12427 -> 0 bytes sources/Test/TestFiles/BUG_16_.xls | Bin 31232 -> 0 bytes sources/Test/TestFiles/CLSIDPropertyTest.file | Bin 245760 -> 0 bytes .../TestFiles/CorruptedDoc_bug3547815.doc | Bin 22016 -> 0 bytes .../TestFiles/CorruptedDoc_bug3547815_B.doc | Bin 22016 -> 0 bytes sources/Test/TestFiles/CorruptedDoc_bug36.doc | Bin 21137 -> 0 bytes sources/Test/TestFiles/CyclicFAT.cfs | Bin 53248 -> 0 bytes sources/Test/TestFiles/Issue134.cfs | Bin 2560 -> 0 bytes sources/Test/TestFiles/MultipleStorage.cfs | Bin 4096 -> 0 bytes sources/Test/TestFiles/MultipleStorage2.cfs | Bin 4608 -> 0 bytes sources/Test/TestFiles/MultipleStorage3.cfs | Bin 22016 -> 0 bytes sources/Test/TestFiles/MultipleStorage4.cfs | Bin 53248 -> 0 bytes .../Test/TestFiles/SampleWorkBook_bug98.xls | Bin 19456 -> 0 bytes sources/Test/TestFiles/_Test.ppt | Bin 174592 -> 0 bytes sources/Test/TestFiles/_thumbs_bug_24.db | Bin 61440 -> 0 bytes .../TestFiles/corrupted-sector-chain-2.doc | Bin 9728 -> 0 bytes .../Test/TestFiles/corrupted-sector-chain.cfs | Bin 9728 -> 0 bytes .../Test/TestFiles/corrupted-sector-chain.doc | Bin 9532 -> 0 bytes .../Test/TestFiles/empty_directory_chain.doc | Bin 9728 -> 0 bytes sources/Test/TestFiles/english.presets.doc | Bin 9728 -> 0 bytes sources/Test/TestFiles/mediationform.doc | Bin 52736 -> 0 bytes sources/Test/TestFiles/no_sectors.doc | Bin 9728 -> 0 bytes sources/Test/TestFiles/report.xls | Bin 16896 -> 0 bytes sources/Test/TestFiles/reportREAD.xls | Bin 16896 -> 0 bytes sources/Test/TestFiles/report_name_fix.xls | Bin 16896 -> 0 bytes sources/Test/TestFiles/testbad.ole | Bin 20700 -> 0 bytes .../Test/TestFiles/winUnicodeDictionary.doc | Bin 26624 -> 0 bytes sources/Test/TestFiles/wstr_presets.doc | Bin 27136 -> 0 bytes sources/icon.png | Bin 1569 -> 0 bytes 160 files changed, 17107 deletions(-) delete mode 100644 .editorconfig delete mode 100644 .github/workflows/dotnet.yml delete mode 100644 .gitignore delete mode 100644 Directory.Build.props delete mode 100644 Directory.Build.targets delete mode 100644 Directory.csproj.props delete mode 100644 Directory.csproj.targets delete mode 100644 License.txt delete mode 100644 MakeRelease.cmd delete mode 100644 OpenMcdf.sln delete mode 100644 OpenMcdf.sln.DotSettings delete mode 100644 OpenMcdf.snk delete mode 100644 README.md delete mode 100644 Release notes.txt delete mode 100644 Support.txt delete mode 100644 azure-pipelines.yml delete mode 100644 docs/img/LOGO_OpenMcdf.png delete mode 100644 docs/img/LOGO_OpenMcdf_H.png delete mode 100644 docs/img/PartialStream.PNG delete mode 100644 docs/img/read_stream.PNG delete mode 100644 docs/img/structured_storage.png delete mode 100644 docs/img/visit_entries.PNG delete mode 100644 docs/img/write_stream.PNG delete mode 100644 docs/index.htm delete mode 100644 docs/main.css delete mode 100644 exclusion.dic delete mode 100644 sources/Html Help/OpenMcdfHelp.shfbproj delete mode 100644 sources/OpenMcdf.Extensions/CFStreamExtensions.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/Common.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/DictionaryEntry.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/DictionaryProperty.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/Interfaces/IBinarySerializable.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/Interfaces/IDictionaryProperty.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/Interfaces/IProperty.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/Interfaces/ITypedPropertyValue.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/OLEPropertiesContainer.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/OLEProperty.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/PropertyFactory.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/PropertyIdentifierAndOffset.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/PropertyReader.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/PropertySet.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/PropertySetStream.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/ProperyIdentifiers.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/TypedPropertyValue.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/VTPropertyType.cs delete mode 100644 sources/OpenMcdf.Extensions/OLEProperties/VTVectorHeader.cs delete mode 100644 sources/OpenMcdf.Extensions/OlePropertiesExtensions.cs delete mode 100644 sources/OpenMcdf.Extensions/OpenMcdf.Extensions.csproj delete mode 100644 sources/OpenMcdf.Extensions/OpenMcdf.Extensions.csproj.user delete mode 100644 sources/OpenMcdf.Extensions/OpenMcdf.snk delete mode 100644 sources/OpenMcdf.Extensions/Properties/AssemblyInfo.cs delete mode 100644 sources/OpenMcdf.Extensions/StreamDecorator.cs delete mode 100644 sources/OpenMcdf/BinaryPrimitives.cs delete mode 100644 sources/OpenMcdf/CFException.cs delete mode 100644 sources/OpenMcdf/CFItem.cs delete mode 100644 sources/OpenMcdf/CFStorage.cs delete mode 100644 sources/OpenMcdf/CFStream.cs delete mode 100644 sources/OpenMcdf/CompoundFile.cs delete mode 100644 sources/OpenMcdf/DirectoryEntry.cs delete mode 100644 sources/OpenMcdf/Header.cs delete mode 100644 sources/OpenMcdf/IDirectoryEntry.cs delete mode 100644 sources/OpenMcdf/OpenMcdf.csproj delete mode 100644 sources/OpenMcdf/OpenMcdf.snk delete mode 100644 sources/OpenMcdf/OpenMcdfClassDiagram.cd delete mode 100644 sources/OpenMcdf/Properties/AssemblyInfo.cs delete mode 100644 sources/OpenMcdf/RBTree/IRBNode.cs delete mode 100644 sources/OpenMcdf/RBTree/RBTree.cs delete mode 100644 sources/OpenMcdf/RBTree/RBTreeEnumerator.cs delete mode 100644 sources/OpenMcdf/Sector.cs delete mode 100644 sources/OpenMcdf/SectorCollection.cs delete mode 100644 sources/OpenMcdf/StreamExtensions.cs delete mode 100644 sources/OpenMcdf/StreamRW.cs delete mode 100644 sources/OpenMcdf/StreamView.cs delete mode 100644 sources/SharedAssemblyInfo.cs delete mode 100644 sources/Structured Storage Explorer/InputBox.cs delete mode 100644 sources/Structured Storage Explorer/MainForm.Designer.cs delete mode 100644 sources/Structured Storage Explorer/MainForm.cs delete mode 100644 sources/Structured Storage Explorer/MainForm.resx delete mode 100644 sources/Structured Storage Explorer/OpenMcdf.snk delete mode 100644 sources/Structured Storage Explorer/PreferencesForm.Designer.cs delete mode 100644 sources/Structured Storage Explorer/PreferencesForm.cs delete mode 100644 sources/Structured Storage Explorer/PreferencesForm.resx delete mode 100644 sources/Structured Storage Explorer/Program.cs delete mode 100644 sources/Structured Storage Explorer/Properties/AssemblyInfo.cs delete mode 100644 sources/Structured Storage Explorer/Properties/Resources.Designer.cs delete mode 100644 sources/Structured Storage Explorer/Properties/Resources.resx delete mode 100644 sources/Structured Storage Explorer/Properties/Settings.Designer.cs delete mode 100644 sources/Structured Storage Explorer/Properties/Settings.settings delete mode 100644 sources/Structured Storage Explorer/StreamDataProvider.cs delete mode 100644 sources/Structured Storage Explorer/StructuredStorageExplorer.csproj delete mode 100644 sources/Structured Storage Explorer/Utils.cs delete mode 100644 sources/Structured Storage Explorer/app.config delete mode 100644 sources/Structured Storage Explorer/img/disk.png delete mode 100644 sources/Structured Storage Explorer/img/door_out.png delete mode 100644 sources/Structured Storage Explorer/img/folder.png delete mode 100644 sources/Structured Storage Explorer/img/page_white.png delete mode 100644 sources/Structured Storage Explorer/img/storage.png delete mode 100644 sources/Structured Storage Explorer/img/stream.png delete mode 100644 sources/Test/OpenMcdf.Benchmark/Extensions.cs delete mode 100644 sources/Test/OpenMcdf.Benchmark/InMemory.cs delete mode 100644 sources/Test/OpenMcdf.Benchmark/OpenMcdf.Benchmark.csproj delete mode 100644 sources/Test/OpenMcdf.Benchmark/Program.cs delete mode 100644 sources/Test/OpenMcdf.Extensions.Test/CFSStreamExtensionsTest.cs delete mode 100644 sources/Test/OpenMcdf.Extensions.Test/Helpers.cs delete mode 100644 sources/Test/OpenMcdf.Extensions.Test/OLEPropertiesExtensionsTest.cs delete mode 100644 sources/Test/OpenMcdf.Extensions.Test/OpenMcdf.Extensions.Test.csproj delete mode 100644 sources/Test/OpenMcdf.Extensions.Test/Properties/AssemblyInfo.cs delete mode 100644 sources/Test/OpenMcdf.MemTest/OpenMcdf.MemTest.csproj delete mode 100644 sources/Test/OpenMcdf.MemTest/Program.cs delete mode 100644 sources/Test/OpenMcdf.MemTest/Properties/AssemblyInfo.cs delete mode 100644 sources/Test/OpenMcdf.MemTest/app.config delete mode 100644 sources/Test/OpenMcdf.MemTest/testfile/report.xls delete mode 100644 sources/Test/OpenMcdf.MemTest/testfile/reportOverwriteMultiple.xls delete mode 100644 sources/Test/OpenMcdf.PerfTest/Helpers.cs delete mode 100644 sources/Test/OpenMcdf.PerfTest/OpenMcdf.PerfTest.csproj delete mode 100644 sources/Test/OpenMcdf.PerfTest/Program.cs delete mode 100644 sources/Test/OpenMcdf.PerfTest/Properties/AssemblyInfo.cs delete mode 100644 sources/Test/OpenMcdf.PerfTest/app.config delete mode 100644 sources/Test/OpenMcdf.Test/AuthoringTests.txt delete mode 100644 sources/Test/OpenMcdf.Test/CFStorageTest.cs delete mode 100644 sources/Test/OpenMcdf.Test/CFStreamTest.cs delete mode 100644 sources/Test/OpenMcdf.Test/CompoundFileTest.cs delete mode 100644 sources/Test/OpenMcdf.Test/Helpers.cs delete mode 100644 sources/Test/OpenMcdf.Test/OpenMcdf.Test.csproj delete mode 100644 sources/Test/OpenMcdf.Test/OpenMcdf.snk delete mode 100644 sources/Test/OpenMcdf.Test/Properties/AssemblyInfo.cs delete mode 100644 sources/Test/OpenMcdf.Test/RBTreeTest.cs delete mode 100644 sources/Test/OpenMcdf.Test/SectorCollectionTest.cs delete mode 100644 sources/Test/OpenMcdf.Test/StreamRWTest.cs delete mode 100644 sources/Test/TestFiles/2_MB-W.ppt delete mode 100644 sources/Test/TestFiles/2custom.doc delete mode 100644 sources/Test/TestFiles/64-67.numberOfMiniFATSectors.docx delete mode 100644 sources/Test/TestFiles/BUG_16_.xls delete mode 100644 sources/Test/TestFiles/CLSIDPropertyTest.file delete mode 100644 sources/Test/TestFiles/CorruptedDoc_bug3547815.doc delete mode 100644 sources/Test/TestFiles/CorruptedDoc_bug3547815_B.doc delete mode 100644 sources/Test/TestFiles/CorruptedDoc_bug36.doc delete mode 100644 sources/Test/TestFiles/CyclicFAT.cfs delete mode 100644 sources/Test/TestFiles/Issue134.cfs delete mode 100644 sources/Test/TestFiles/MultipleStorage.cfs delete mode 100644 sources/Test/TestFiles/MultipleStorage2.cfs delete mode 100644 sources/Test/TestFiles/MultipleStorage3.cfs delete mode 100644 sources/Test/TestFiles/MultipleStorage4.cfs delete mode 100644 sources/Test/TestFiles/SampleWorkBook_bug98.xls delete mode 100644 sources/Test/TestFiles/_Test.ppt delete mode 100644 sources/Test/TestFiles/_thumbs_bug_24.db delete mode 100644 sources/Test/TestFiles/corrupted-sector-chain-2.doc delete mode 100644 sources/Test/TestFiles/corrupted-sector-chain.cfs delete mode 100644 sources/Test/TestFiles/corrupted-sector-chain.doc delete mode 100644 sources/Test/TestFiles/empty_directory_chain.doc delete mode 100644 sources/Test/TestFiles/english.presets.doc delete mode 100644 sources/Test/TestFiles/mediationform.doc delete mode 100644 sources/Test/TestFiles/no_sectors.doc delete mode 100644 sources/Test/TestFiles/report.xls delete mode 100644 sources/Test/TestFiles/reportREAD.xls delete mode 100644 sources/Test/TestFiles/report_name_fix.xls delete mode 100644 sources/Test/TestFiles/testbad.ole delete mode 100644 sources/Test/TestFiles/winUnicodeDictionary.doc delete mode 100644 sources/Test/TestFiles/wstr_presets.doc delete mode 100644 sources/icon.png diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index e187e441..00000000 --- a/.editorconfig +++ /dev/null @@ -1,277 +0,0 @@ -# Remove the line below if you want to inherit .editorconfig settings from higher directories -root = true - -#### Visual Studio Spell Checker #### - -[*] -spelling_exclusion_path = .\exclusion.dic - -#### VS Spell Checker #### - -vsspell_section_id = root -vsspell_determine_resource_file_language_from_name = true -vsspell_ignored_words_root = clear_inherited|File:exclusion.dic - -[*.{csproj,props,pubxml,runsettings,targets}] - -# Indentation and spacing -indent_size = 2 -indent_style = space -tab_width = 2 -trim_trailing_whitespace = true - - -# C# files -[*.cs] - -#### Core EditorConfig Options #### - -# Indentation and spacing -indent_size = 4 -indent_style = space -tab_width = 4 - -# New line preferences -end_of_line = crlf -insert_final_newline = false - -#### .NET Coding Conventions #### - -# Organize usings -dotnet_separate_import_directive_groups = false -dotnet_sort_system_directives_first = false -file_header_template = unset - -# this. and Me. preferences -dotnet_style_qualification_for_event = false -dotnet_style_qualification_for_field = false -dotnet_style_qualification_for_method = false -dotnet_style_qualification_for_property = false - -# Language keywords vs BCL types preferences -dotnet_style_predefined_type_for_locals_parameters_members = true -dotnet_style_predefined_type_for_member_access = true - -# Parentheses preferences -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 - -# Modifier preferences -dotnet_style_require_accessibility_modifiers = for_non_interface_members - -# Expression-level preferences -dotnet_style_coalesce_expression = true -dotnet_style_collection_initializer = true -dotnet_style_explicit_tuple_names = true -dotnet_style_namespace_match_folder = true -dotnet_style_null_propagation = true -dotnet_style_object_initializer = true -dotnet_style_operator_placement_when_wrapping = beginning_of_line -dotnet_style_prefer_auto_properties = true -dotnet_style_prefer_collection_expression = when_types_loosely_match -dotnet_style_prefer_compound_assignment = true -dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = true:suggestion -dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed -dotnet_style_prefer_inferred_anonymous_type_member_names = true -dotnet_style_prefer_inferred_tuple_names = true -dotnet_style_prefer_is_null_check_over_reference_equality_method = true -dotnet_style_prefer_simplified_boolean_expressions = true -dotnet_style_prefer_simplified_interpolation = true - -# Field preferences -dotnet_style_readonly_field = true - -# Parameter preferences -dotnet_code_quality_unused_parameters = all - -# Suppression preferences -dotnet_remove_unnecessary_suppression_exclusions = none - -# New line preferences -dotnet_style_allow_multiple_blank_lines_experimental = true -dotnet_style_allow_statement_immediately_after_block_experimental = true - -#### C# Coding Conventions #### - -# var preferences -csharp_style_var_elsewhere = false:suggestion -csharp_style_var_for_built_in_types = false:silent -csharp_style_var_when_type_is_apparent = true:suggestion - -# Expression-bodied members -csharp_style_expression_bodied_accessors = true:silent -csharp_style_expression_bodied_constructors = false:silent -csharp_style_expression_bodied_indexers = true:silent -csharp_style_expression_bodied_lambdas = true:silent -csharp_style_expression_bodied_local_functions = false:silent -csharp_style_expression_bodied_methods = false:silent -csharp_style_expression_bodied_operators = false:silent -csharp_style_expression_bodied_properties = true:silent - -# Pattern matching preferences -csharp_style_pattern_matching_over_as_with_null_check = true -csharp_style_pattern_matching_over_is_with_cast_check = true -csharp_style_prefer_extended_property_pattern = true -csharp_style_prefer_not_pattern = true -csharp_style_prefer_pattern_matching = true -csharp_style_prefer_switch_expression = true - -# Null-checking preferences -csharp_style_conditional_delegate_call = true:suggestion - -# Modifier preferences -csharp_prefer_static_anonymous_function = true -csharp_prefer_static_local_function = true -csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async -csharp_style_prefer_readonly_struct = true -csharp_style_prefer_readonly_struct_member = true - -# Code-block preferences -csharp_prefer_braces = true:silent -csharp_prefer_simple_using_statement = true:suggestion -csharp_style_namespace_declarations = block_scoped:silent -csharp_style_prefer_method_group_conversion = true:silent -csharp_style_prefer_primary_constructors = true:suggestion -csharp_style_prefer_top_level_statements = true:silent - -# Expression-level preferences -csharp_prefer_simple_default_expression = true -csharp_style_deconstructed_variable_declaration = true:suggestion -csharp_style_implicit_object_creation_when_type_is_apparent = true -csharp_style_inlined_variable_declaration = true:suggestion -csharp_style_prefer_index_operator = true -csharp_style_prefer_local_over_anonymous_function = true -csharp_style_prefer_null_check_over_type_check = true -csharp_style_prefer_range_operator = true -csharp_style_prefer_tuple_swap = true -csharp_style_prefer_utf8_string_literals = true -csharp_style_throw_expression = true -csharp_style_unused_value_assignment_preference = discard_variable -csharp_style_unused_value_expression_statement_preference = discard_variable - -# 'using' directive preferences -csharp_using_directive_placement = outside_namespace:silent - -# New line preferences -csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true -csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true -csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true -csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true -csharp_style_allow_embedded_statements_on_same_line_experimental = true - -#### C# Formatting Rules #### - -# New line preferences -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 - -# Indentation preferences -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 - -# Space preferences -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 - -# Wrapping preferences -csharp_preserve_single_line_blocks = true -csharp_preserve_single_line_statements = true - -#### Naming styles #### - -# Naming rules - -dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion -dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface -dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i - -dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.types_should_be_pascal_case.symbols = types -dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case - -dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members -dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case - -# Symbol specifications - -dotnet_naming_symbols.interface.applicable_kinds = interface -dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interface.required_modifiers = - -dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum -dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types.required_modifiers = - -dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method -dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = - -# Naming styles - -dotnet_naming_style.pascal_case.required_prefix = -dotnet_naming_style.pascal_case.required_suffix = -dotnet_naming_style.pascal_case.word_separator = -dotnet_naming_style.pascal_case.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.begins_with_i.capitalization = pascal_case - -# CS1591: Missing XML comment for publicly visible type or member -dotnet_diagnostic.CS1591.severity = suggestion - -[*.{cs,vb}] -dotnet_style_operator_placement_when_wrapping = beginning_of_line -tab_width = 4 -indent_size = 4 -end_of_line = crlf -dotnet_style_coalesce_expression = true:suggestion -dotnet_style_null_propagation = true:suggestion -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion -dotnet_style_prefer_auto_properties = true:silent -dotnet_style_object_initializer = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_prefer_simplified_boolean_expressions = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = true:suggestion -dotnet_style_explicit_tuple_names = true:suggestion -dotnet_style_prefer_inferred_tuple_names = true:suggestion -dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion -dotnet_style_prefer_compound_assignment = true:suggestion -dotnet_style_prefer_simplified_interpolation = true:suggestion -dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion -dotnet_style_namespace_match_folder = true:suggestion \ No newline at end of file diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml deleted file mode 100644 index f3789b2d..00000000 --- a/.github/workflows/dotnet.yml +++ /dev/null @@ -1,44 +0,0 @@ -# This workflow will build a .NET project -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net - -name: OpenMcdf pipeline .NET - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -env: - buildConfiguration: 'Release' - libFramework: 'netstandard2.0' - appFramework: 'net6.0' - # pay attention to slashes - testsProject: 'sources/Test/OpenMcdf.Test/OpenMcdf.Test.csproj' - extensionTestsProject: 'sources/Test/OpenMcdf.Extensions.Test/OpenMcdf.Extensions.Test.csproj' - buildProject: 'sources/OpenMcdf/OpenMcdf.csproj' - # without filter it will timeout in azure AFTER 60+ min - testFilter: 'Name!=Test_FIX_BUG_GH_14&Name!=Test_FIX_BUG_GH_15' - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore --configuration ${{env.buildConfiguration}} -f ${{env.libFramework}} ${{env.buildProject}} - - name: Test - run: dotnet test -f ${{env.appFramework}} ${{env.testsProject}} --filter="${{env.testFilter}}" --logger "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true" - - name: Test Extensions - run: dotnet test -f ${{env.appFramework}} ${{env.extensionTestsProject}} --logger "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true" - - - diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 7d8da627..00000000 --- a/.gitignore +++ /dev/null @@ -1,363 +0,0 @@ -## 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/ -[Oo]ut/ -[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/ - -# 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 \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props deleted file mode 100644 index 2f0930f5..00000000 --- a/Directory.Build.props +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets deleted file mode 100644 index 27a60831..00000000 --- a/Directory.Build.targets +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/Directory.csproj.props b/Directory.csproj.props deleted file mode 100644 index a3950fe3..00000000 --- a/Directory.csproj.props +++ /dev/null @@ -1,16 +0,0 @@ - - - - - 10 - - - - - latest - true - - - true - - \ No newline at end of file diff --git a/Directory.csproj.targets b/Directory.csproj.targets deleted file mode 100644 index 472bb788..00000000 --- a/Directory.csproj.targets +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/License.txt b/License.txt deleted file mode 100644 index 016b985c..00000000 --- a/License.txt +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/MakeRelease.cmd b/MakeRelease.cmd deleted file mode 100644 index c5c8952c..00000000 --- a/MakeRelease.cmd +++ /dev/null @@ -1 +0,0 @@ -msbuild OpenMcdf.sln /property:Configuration=Release \ No newline at end of file diff --git a/OpenMcdf.sln b/OpenMcdf.sln deleted file mode 100644 index d6e3a65c..00000000 --- a/OpenMcdf.sln +++ /dev/null @@ -1,124 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.5.33530.505 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6C619B4F-100F-4D60-BEF1-60B770D24E5E}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - azure-pipelines.yml = azure-pipelines.yml - Directory.Build.props = Directory.Build.props - Directory.Build.targets = Directory.Build.targets - Directory.csproj.props = Directory.csproj.props - Directory.csproj.targets = Directory.csproj.targets - exclusion.dic = exclusion.dic - OpenMcdf.snk = OpenMcdf.snk - sources\SharedAssemblyInfo.cs = sources\SharedAssemblyInfo.cs - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestFiles", "TestFiles", "{8DC8730A-F254-4848-B272-BDFFCB5FDC00}" - ProjectSection(SolutionItems) = preProject - sources\Test\TestFiles\2custom.doc = sources\Test\TestFiles\2custom.doc - sources\TestFiles\2_MB-W.ppt = sources\TestFiles\2_MB-W.ppt - sources\Test\TestFiles\64-67.numberOfMiniFATSectors.docx = sources\Test\TestFiles\64-67.numberOfMiniFATSectors.docx - sources\TestFiles\BUG_16_.xls = sources\TestFiles\BUG_16_.xls - sources\Test\TestFiles\corrupted-sector-chain-2.doc = sources\Test\TestFiles\corrupted-sector-chain-2.doc - sources\Test\TestFiles\corrupted-sector-chain.cfs = sources\Test\TestFiles\corrupted-sector-chain.cfs - sources\Test\TestFiles\corrupted-sector-chain.doc = sources\Test\TestFiles\corrupted-sector-chain.doc - sources\TestFiles\CorruptedDoc_bug3547815.doc = sources\TestFiles\CorruptedDoc_bug3547815.doc - sources\TestFiles\CorruptedDoc_bug3547815_B.doc = sources\TestFiles\CorruptedDoc_bug3547815_B.doc - sources\Test\TestFiles\CorruptedDoc_bug36.doc = sources\Test\TestFiles\CorruptedDoc_bug36.doc - sources\TestFiles\CyclicFAT.cfs = sources\TestFiles\CyclicFAT.cfs - sources\Test\TestFiles\empty_directory_chain.doc = sources\Test\TestFiles\empty_directory_chain.doc - sources\Test\TestFiles\english.presets.doc = sources\Test\TestFiles\english.presets.doc - sources\Test\TestFiles\mediationform.doc = sources\Test\TestFiles\mediationform.doc - sources\TestFiles\MultipleStorage.cfs = sources\TestFiles\MultipleStorage.cfs - sources\TestFiles\MultipleStorage2.cfs = sources\TestFiles\MultipleStorage2.cfs - sources\TestFiles\MultipleStorage3.cfs = sources\TestFiles\MultipleStorage3.cfs - sources\TestFiles\MultipleStorage4.cfs = sources\TestFiles\MultipleStorage4.cfs - sources\Test\TestFiles\no_sectors.doc = sources\Test\TestFiles\no_sectors.doc - sources\TestFiles\report.xls = sources\TestFiles\report.xls - sources\TestFiles\reportREAD.xls = sources\TestFiles\reportREAD.xls - sources\TestFiles\report_name_fix.xls = sources\TestFiles\report_name_fix.xls - sources\Test\TestFiles\SampleWorkBook_bug98.xls = sources\Test\TestFiles\SampleWorkBook_bug98.xls - sources\TestFiles\testbad.ole = sources\TestFiles\testbad.ole - sources\Test\TestFiles\winUnicodeDictionary.doc = sources\Test\TestFiles\winUnicodeDictionary.doc - sources\Test\TestFiles\wstr_presets.doc = sources\Test\TestFiles\wstr_presets.doc - sources\TestFiles\_thumbs_bug_24.db = sources\TestFiles\_thumbs_bug_24.db - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf", "sources\OpenMcdf\OpenMcdf.csproj", "{56E15D4A-8A37-4C7C-BB44-FD59AFF220C1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Extensions", "sources\OpenMcdf.Extensions\OpenMcdf.Extensions.csproj", "{DB748C1D-D71C-442B-832D-2E33BE816CBB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StructuredStorageExplorer", "sources\Structured Storage Explorer\StructuredStorageExplorer.csproj", "{4F6323A8-9C06-4D94-808F-EBD69B8370D7}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{73814657-FC73-4066-AABD-86062F2A132E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Extensions.Test", "sources\Test\OpenMcdf.Extensions.Test\OpenMcdf.Extensions.Test.csproj", "{B9CB103F-0AA3-486D-9C9C-672924B6169C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.MemTest", "sources\Test\OpenMcdf.MemTest\OpenMcdf.MemTest.csproj", "{E2BAD82D-3040-462B-BAA2-6E608A9054F4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.PerfTest", "sources\Test\OpenMcdf.PerfTest\OpenMcdf.PerfTest.csproj", "{7077508F-B313-4DF6-8855-4764911BE161}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Test", "sources\Test\OpenMcdf.Test\OpenMcdf.Test.csproj", "{FD339266-8842-40B4-9230-F8E84FC42AC2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenMcdf.Benchmark", "sources\Test\OpenMcdf.Benchmark\OpenMcdf.Benchmark.csproj", "{B3645D34-1E22-4BCC-8956-A8A56FA9F114}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {56E15D4A-8A37-4C7C-BB44-FD59AFF220C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {56E15D4A-8A37-4C7C-BB44-FD59AFF220C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {56E15D4A-8A37-4C7C-BB44-FD59AFF220C1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {56E15D4A-8A37-4C7C-BB44-FD59AFF220C1}.Release|Any CPU.Build.0 = Release|Any CPU - {DB748C1D-D71C-442B-832D-2E33BE816CBB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB748C1D-D71C-442B-832D-2E33BE816CBB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB748C1D-D71C-442B-832D-2E33BE816CBB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB748C1D-D71C-442B-832D-2E33BE816CBB}.Release|Any CPU.Build.0 = Release|Any CPU - {4F6323A8-9C06-4D94-808F-EBD69B8370D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F6323A8-9C06-4D94-808F-EBD69B8370D7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4F6323A8-9C06-4D94-808F-EBD69B8370D7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4F6323A8-9C06-4D94-808F-EBD69B8370D7}.Release|Any CPU.Build.0 = Release|Any CPU - {B9CB103F-0AA3-486D-9C9C-672924B6169C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B9CB103F-0AA3-486D-9C9C-672924B6169C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9CB103F-0AA3-486D-9C9C-672924B6169C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B9CB103F-0AA3-486D-9C9C-672924B6169C}.Release|Any CPU.Build.0 = Release|Any CPU - {E2BAD82D-3040-462B-BAA2-6E608A9054F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E2BAD82D-3040-462B-BAA2-6E608A9054F4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E2BAD82D-3040-462B-BAA2-6E608A9054F4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E2BAD82D-3040-462B-BAA2-6E608A9054F4}.Release|Any CPU.Build.0 = Release|Any CPU - {7077508F-B313-4DF6-8855-4764911BE161}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7077508F-B313-4DF6-8855-4764911BE161}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7077508F-B313-4DF6-8855-4764911BE161}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7077508F-B313-4DF6-8855-4764911BE161}.Release|Any CPU.Build.0 = Release|Any CPU - {FD339266-8842-40B4-9230-F8E84FC42AC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FD339266-8842-40B4-9230-F8E84FC42AC2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FD339266-8842-40B4-9230-F8E84FC42AC2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FD339266-8842-40B4-9230-F8E84FC42AC2}.Release|Any CPU.Build.0 = Release|Any CPU - {B3645D34-1E22-4BCC-8956-A8A56FA9F114}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B3645D34-1E22-4BCC-8956-A8A56FA9F114}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B3645D34-1E22-4BCC-8956-A8A56FA9F114}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B3645D34-1E22-4BCC-8956-A8A56FA9F114}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {8DC8730A-F254-4848-B272-BDFFCB5FDC00} = {73814657-FC73-4066-AABD-86062F2A132E} - {B9CB103F-0AA3-486D-9C9C-672924B6169C} = {73814657-FC73-4066-AABD-86062F2A132E} - {E2BAD82D-3040-462B-BAA2-6E608A9054F4} = {73814657-FC73-4066-AABD-86062F2A132E} - {7077508F-B313-4DF6-8855-4764911BE161} = {73814657-FC73-4066-AABD-86062F2A132E} - {FD339266-8842-40B4-9230-F8E84FC42AC2} = {73814657-FC73-4066-AABD-86062F2A132E} - {B3645D34-1E22-4BCC-8956-A8A56FA9F114} = {73814657-FC73-4066-AABD-86062F2A132E} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {DEAA45F3-54F5-4F50-8402-9D89EB95A04C} - EndGlobalSection - GlobalSection(TestCaseManagementSettings) = postSolution - CategoryFile = OpenMcdf2.vsmdi - EndGlobalSection -EndGlobal diff --git a/OpenMcdf.sln.DotSettings b/OpenMcdf.sln.DotSettings deleted file mode 100644 index 43677b3c..00000000 --- a/OpenMcdf.sln.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/OpenMcdf.snk b/OpenMcdf.snk deleted file mode 100644 index b2990e73c74b1b002bd693d3d8008085b96f461f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 596 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 diff --git a/docs/img/LOGO_OpenMcdf_H.png b/docs/img/LOGO_OpenMcdf_H.png deleted file mode 100644 index 94cf3360716ac0e7537ce516fab79e71415dcf7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16575 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 diff --git a/docs/img/PartialStream.PNG b/docs/img/PartialStream.PNG deleted file mode 100644 index 70d4e7c319abb726e57f6654ba142ab258259a43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4415 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 diff --git a/docs/img/read_stream.PNG b/docs/img/read_stream.PNG deleted file mode 100644 index 9c8ed4334128439bba3b8af7b115e9f2dc498d7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7217 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

KGIVO$+=!_Sx((|T4xIN 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 W^1O2yyhj84Jyp?oSbX0k=>Gy3_M;g9 diff --git a/docs/img/visit_entries.PNG b/docs/img/visit_entries.PNG deleted file mode 100644 index b3dcfe144a1c20888aa1a7fa758c0710f80c534e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10196 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 P8UX_x6YWY(=STkqi!H1{ diff --git a/docs/index.htm b/docs/index.htm deleted file mode 100644 index 01f9cc33..00000000 --- a/docs/index.htm +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - OpenMCDF - .net component for ole compound file storage format - - - - - - -

- - -
-

- What is OpenMCDF ? -

-

- OpenMCDF is a 100% managed .net component that allows client applications to manipulate - COM structured storage files, also known as Microsoft Compound Document Format files. -

-

- Ok, so what's 'structured storage' anyway ? -

-

- This file format is used under the hood by a lot of applications: the files created - by Microsoft Office until the 2007 product release are all structured storage files. - They include multiple streams of information (document summary, user data) in a - single physical container (the file). Also the omnipresent Thumbs.db, used by Windows as thumbnails - cache, is a structured storage file. -

-

- How does it work ? -

-

- OpenMCDF makes available to the developer an easy interface to - read, write, add - and remove structured storage primitives. Items are organized - in a hierarchical tree - where 'storage' nodes act like a directory and 'stream' nodes like a file. Developers - can use OpenMCDF to view storages and streams, - traverse hierarchical trees of items, explore existing compound file and modify them or - create a new compound file from scratch. -

-

- ...mmm yes, but I've already seen a lot of wrappers... -

- -
    -
  • OpenMCDF is NOT a wrapper component: it's 100% pure C#, nothing else.
  • -
  • There are NO ActiveX, COM or P/Invoke dependencies
  • -
  • OpenMCDF supports large files and Version 4 of Compound File Format
  • -
  • Low memory footprint: incremental data updating and partial stream data reading
  • -
  • Works on Mono platform out of the box
  • -
-

-

- Well, can I try it ? -

-

- You can get OpenMCDF from Github - or use NuGet to install it into your projects. OpenMCDF is MPL 2.0 licensed. -

-
- - -
- - - - - - - - - - - - - - diff --git a/docs/main.css b/docs/main.css deleted file mode 100644 index d29ab81e..00000000 --- a/docs/main.css +++ /dev/null @@ -1,133 +0,0 @@ -body -{ - /*overflow:hidden;*/ - position: relative; - background-color: white; - /*font-family: Segoe UI;*/ - font-family: "wf_SegoeUILight","wf_SegoeUI","Segoe UI Light","Segoe WP Light","Segoe UI","Segoe","Segoe WP","Tahoma","Verdana","Arial","sans-serif"; - /*font-weight: lighter;*/ - font-size:14pt; - text-align: center; - margin: 0px; - margin-top: 10px; - padding: 0px; - display: block; -} - -li -{ - margin-top: 3px; -} - -div#container -{ - margin-top: 0px; - margin-left: auto; - margin-bottom: 14px; - margin-right: auto; - width: 760px; - text-align: center; -} - -div#header -{ - width: 780px; - height: 120px; -} - -div#main -{ - width: 780px; /*border-color: green; border-width: 1px; border-style: solid;*/ - text-align: justify; -} - -div#footer -{ - width: 780px; /*border-color: green; border-width: 1px; border-style: solid;*/ - font-size: 8pt; - text-align: right; -} - -p -{ - margin-bottom: 65px; -} - -strong -{ - font-weight: 600; -} - -h1 -{ - font-size: 16pt; - font-weight: 600; -} - -h2 -{ - font-size: 16pt; - font-weight: bold; -} - -#header -{ - width: 780px; - height: 130px; /*border-color: red; border-width: 1px; border-style: solid;*/ -} - -.modal_popup -{ - - position: absolute; - z-index: 10000; - -} - -.popup_block -{ - border:2px solid; - border-color:Black; - background-color: White; - position:relative; - z-index: 10000; - padding: 30px; -} - -.popup_footer -{ - position: relative; - background-color: White; - z-index: 10000; - padding-top:20px; - bottom: 0px; -} - -.fader -{ - background: black; - - background: -webkit-gradient( - linear, - right top, - left bottom, - color-stop(0.18, rgb(5,3,0)), - color-stop(0.59, rgb(148,147,143))); - - background: -moz-linear-gradient( - right top, - rgb(5,3,0) 18%, - rgb(148,147,143) 59% - ); - - position: fixed; - width: 100%; - height: 100%; - filter: alpha(opacity=70); - opacity: .70; - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; - left: 0; - top: 0; - z-index: 10; -} - diff --git a/exclusion.dic b/exclusion.dic deleted file mode 100644 index 2f9d70d1..00000000 --- a/exclusion.dic +++ /dev/null @@ -1,14 +0,0 @@ -Blaseotto -CLSID -codepage -DIFAT -endian -enqueuing -Java -MCDF -metadata -netstandard -OpenMcdf -toolbar -typelib -Unicode \ No newline at end of file diff --git a/sources/Html Help/OpenMcdfHelp.shfbproj b/sources/Html Help/OpenMcdfHelp.shfbproj deleted file mode 100644 index 16775b96..00000000 --- a/sources/Html Help/OpenMcdfHelp.shfbproj +++ /dev/null @@ -1,80 +0,0 @@ - - - - - Debug - AnyCPU - 2.0 - {6b2e0fe5-8246-4f87-9663-d72ebadfc53f} - 2015.6.5.0 - - Documentation - Documentation - Documentation - - .\Help\ - OpenMCDF - Copyright 2010 - 2016 &#169%3b Federico Blaseotto - Open MCDF - Open MCDF - Open MCDF - Summary, AutoDocumentCtors - - - - ironfede%40users.sourceforge.net - - - - - - - - - - - VS2013 - en-US - - - - - InheritedMembers, InheritedFrameworkMembers - - - - - - - - 2.1 - Hierarchical - 2 - False - C# - Blank - False - False - Guid - AboveNamespaces - OnlyWarningsAndErrors - HtmlHelp1 - False - .NET Framework 4.0 - True - False - False - True - - - - - - - - - \ No newline at end of file diff --git a/sources/OpenMcdf.Extensions/CFStreamExtensions.cs b/sources/OpenMcdf.Extensions/CFStreamExtensions.cs deleted file mode 100644 index 6bab3571..00000000 --- a/sources/OpenMcdf.Extensions/CFStreamExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.IO; - -namespace OpenMcdf.Extensions -{ - public static class CFStreamExtension - { - /// - /// Return the current CFStream object - /// as a Stream object. - /// - /// Current CFStream object - /// A Stream object representing structured stream data - public static Stream AsIOStream(this CFStream cfStream) - { - return new StreamDecorator(cfStream); - } - - ///// - ///// Return the current CFStream object - ///// as a OLE properties Stream. - ///// - ///// - ///// A OLE Property Set Stream - //public static OLEProperties.PropertySetStream AsOLEProperties(this CFStream cfStream) - //{ - // var result = new OLEProperties.PropertySetStream(); - // result.Read(new BinaryReader(new StreamDecorator(cfStream))); - // return result; - //} - } -} diff --git a/sources/OpenMcdf.Extensions/OLEProperties/Common.cs b/sources/OpenMcdf.Extensions/OLEProperties/Common.cs deleted file mode 100644 index b61ddcc9..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/Common.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace OpenMcdf.Extensions.OLEProperties -{ - public enum Behavior - { - CaseSensitive, - CaseInsensitive - } - - public class PropertyContext - { - public int CodePage { get; set; } - public Behavior Behavior { get; set; } - public uint Locale { get; set; } - } - - public static class WellKnownFMTID - { - public const string FMTID_SummaryInformation = "{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"; - public const string FMTID_DocSummaryInformation = "{D5CDD502-2E9C-101B-9397-08002B2CF9AE}"; - public const string FMTID_UserDefinedProperties = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"; - public const string FMTID_GlobalInfo = "{56616F00-C154-11CE-8553-00AA00A1F95B}"; - public const string FMTID_ImageContents = "{56616400-C154-11CE-8553-00AA00A1F95B}"; - public const string FMTID_ImageInfo = "{56616500-C154-11CE-8553-00AA00A1F95B}"; - } - - public enum PropertyDimensions - { - IsScalar, - IsVector, - IsArray - } - - public enum PropertyType - { - TypedPropertyValue = 0, - DictionaryProperty = 1 - } - - internal static class CodePages - { - public const int CP_WINUNICODE = 0x04B0; - } -} diff --git a/sources/OpenMcdf.Extensions/OLEProperties/DictionaryEntry.cs b/sources/OpenMcdf.Extensions/OLEProperties/DictionaryEntry.cs deleted file mode 100644 index 3c691307..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/DictionaryEntry.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.IO; -using System.Text; - -namespace OpenMcdf.Extensions.OLEProperties -{ - public class DictionaryEntry - { - readonly int codePage; - - public DictionaryEntry(int codePage) - { - this.codePage = codePage; - } - - public uint PropertyIdentifier { get; set; } - public int Length { get; set; } - public string Name => GetName(); - - private byte[] nameBytes; - - public void Read(BinaryReader br) - { - PropertyIdentifier = br.ReadUInt32(); - Length = br.ReadInt32(); - - if (codePage != CodePages.CP_WINUNICODE) - { - nameBytes = br.ReadBytes(Length); - } - else - { - nameBytes = br.ReadBytes(Length << 1); - - int m = Length * 2 % 4; - if (m > 0) - br.ReadBytes(m); - } - } - - public void Write(BinaryWriter bw) - { - bw.Write(PropertyIdentifier); - bw.Write(Length); - bw.Write(nameBytes); - - //if (codePage == CP_WINUNICODE) - // int m = Length % 4; - - //if (m > 0) - // for (int i = 0; i < m; i++) - // bw.Write((byte)m); - } - - private string GetName() - { - var result = Encoding.GetEncoding(codePage).GetString(nameBytes); - - result = result.Trim('\0'); - - return result; - } - } -} diff --git a/sources/OpenMcdf.Extensions/OLEProperties/DictionaryProperty.cs b/sources/OpenMcdf.Extensions/OLEProperties/DictionaryProperty.cs deleted file mode 100644 index 44cccfd3..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/DictionaryProperty.cs +++ /dev/null @@ -1,136 +0,0 @@ -using OpenMcdf.Extensions.OLEProperties.Interfaces; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace OpenMcdf.Extensions.OLEProperties -{ - public class DictionaryProperty : IDictionaryProperty - { - private readonly int codePage; - - public DictionaryProperty(int codePage) - { - this.codePage = codePage; - entries = new Dictionary(); - } - - public PropertyType PropertyType => PropertyType.DictionaryProperty; - - private Dictionary entries; - - public object Value - { - get => entries; - set => entries = (Dictionary)value; - } - - public void Read(BinaryReader br) - { - long curPos = br.BaseStream.Position; - - uint numEntries = br.ReadUInt32(); - - for (uint i = 0; i < numEntries; i++) - { - DictionaryEntry de = new DictionaryEntry(codePage); - - de.Read(br); - entries.Add(de.PropertyIdentifier, de.Name); - } - - int m = (int)(br.BaseStream.Position - curPos) % 4; - - if (m > 0) - { - for (int i = 0; i < m; i++) - { - br.ReadByte(); - } - } - } - - /// - /// Write the dictionary and all its values into the specified . - /// - /// - /// Based on the Microsoft specifications at https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-oleps/99127b7f-c440-4697-91a4-c853086d6b33 - /// - /// A writer to write the dictionary into. - public void Write(BinaryWriter bw) - { - long curPos = bw.BaseStream.Position; - - bw.Write(entries.Count); - - foreach (KeyValuePair kv in entries) - { - WriteEntry(bw, kv.Key, kv.Value); - } - - var size = (int)(bw.BaseStream.Position - curPos); - WritePaddingIfNeeded(bw, size); - } - - // Write a single entry to the dictionary, and handle and required null termination and padding. - private void WriteEntry(BinaryWriter bw, uint propertyIdentifier, string name) - { - // Write the PropertyIdentifier - bw.Write(propertyIdentifier); - - // Encode string data with the current codepage - var nameBytes = Encoding.GetEncoding(codePage).GetBytes(name); - uint byteLength = (uint)nameBytes.Length; - - // If the code page is WINUNICODE, write the length as the number of characters and pad the length to a multiple of 4 bytes - // Otherwise, write the length as the number of bytes and don't pad. - // In either case, the string must be null terminated - if (codePage == CodePages.CP_WINUNICODE) - { - bool addNullTerminator = - byteLength == 0 || nameBytes[byteLength - 1] != '\0' || nameBytes[byteLength - 2] != '\0'; - - if (addNullTerminator) - byteLength += 2; - - bw.Write(byteLength / 2); - bw.Write(nameBytes); - - if (addNullTerminator) - { - bw.Write((byte)0); - bw.Write((byte)0); - } - - WritePaddingIfNeeded(bw, (int)byteLength); - } - else - { - bool addNullTerminator = - byteLength == 0 || nameBytes[byteLength - 1] != '\0'; - - if (addNullTerminator) - byteLength += 1; - - bw.Write(byteLength); - bw.Write(nameBytes); - - if (addNullTerminator) - bw.Write((byte)0); - } - } - - // Write as much padding as needed to pad fieldLength to a multiple of 4 bytes - private static void WritePaddingIfNeeded(BinaryWriter bw, int fieldLength) - { - var m = fieldLength % 4; - - if (m > 0) - { - for (int i = 0; i < 4 - m; i++) // padding - bw.Write((byte)0); - } - } - } -} - diff --git a/sources/OpenMcdf.Extensions/OLEProperties/Interfaces/IBinarySerializable.cs b/sources/OpenMcdf.Extensions/OLEProperties/Interfaces/IBinarySerializable.cs deleted file mode 100644 index 9ac2ba74..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/Interfaces/IBinarySerializable.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.IO; - -namespace OpenMcdf.Extensions.OLEProperties.Interfaces -{ - public interface IBinarySerializable - { - void Write(BinaryWriter bw); - void Read(BinaryReader br); - } -} diff --git a/sources/OpenMcdf.Extensions/OLEProperties/Interfaces/IDictionaryProperty.cs b/sources/OpenMcdf.Extensions/OLEProperties/Interfaces/IDictionaryProperty.cs deleted file mode 100644 index d5b86761..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/Interfaces/IDictionaryProperty.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace OpenMcdf.Extensions.OLEProperties.Interfaces -{ - public interface IDictionaryProperty : IProperty - { - } -} \ No newline at end of file diff --git a/sources/OpenMcdf.Extensions/OLEProperties/Interfaces/IProperty.cs b/sources/OpenMcdf.Extensions/OLEProperties/Interfaces/IProperty.cs deleted file mode 100644 index f3cd4985..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/Interfaces/IProperty.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace OpenMcdf.Extensions.OLEProperties.Interfaces -{ - public interface IProperty : IBinarySerializable - { - object Value - { - get; - set; - } - - PropertyType PropertyType - { - get; - } - } -} diff --git a/sources/OpenMcdf.Extensions/OLEProperties/Interfaces/ITypedPropertyValue.cs b/sources/OpenMcdf.Extensions/OLEProperties/Interfaces/ITypedPropertyValue.cs deleted file mode 100644 index a223716f..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/Interfaces/ITypedPropertyValue.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace OpenMcdf.Extensions.OLEProperties.Interfaces -{ - public interface ITypedPropertyValue : IProperty - { - VTPropertyType VTType - { - get; - //set; - } - - PropertyDimensions PropertyDimensions - { - get; - } - - bool IsVariant - { - get; - } - } -} diff --git a/sources/OpenMcdf.Extensions/OLEProperties/OLEPropertiesContainer.cs b/sources/OpenMcdf.Extensions/OLEProperties/OLEPropertiesContainer.cs deleted file mode 100644 index b565daf6..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/OLEPropertiesContainer.cs +++ /dev/null @@ -1,363 +0,0 @@ -using OpenMcdf.Extensions.OLEProperties.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace OpenMcdf.Extensions.OLEProperties -{ - public class OLEPropertiesContainer - { - public Dictionary PropertyNames; - - public OLEPropertiesContainer UserDefinedProperties { get; private set; } - - public bool HasUserDefinedProperties { get; private set; } - - public ContainerType ContainerType { get; } - private Guid? FmtID0 { get; } - - public PropertyContext Context { get; } - - private readonly List properties = new(); - internal CFStream cfStream; - - /* - Property name Property ID PID Type - Codepage PID_CODEPAGE 1 VT_I2 - Title PID_TITLE 2 VT_LPSTR - Subject PID_SUBJECT 3 VT_LPSTR - Author PID_AUTHOR 4 VT_LPSTR - Keywords PID_KEYWORDS 5 VT_LPSTR - Comments PID_COMMENTS 6 VT_LPSTR - Template PID_TEMPLATE 7 VT_LPSTR - Last Saved By PID_LASTAUTHOR 8 VT_LPSTR - Revision Number PID_REVNUMBER 9 VT_LPSTR - Last Printed PID_LASTPRINTED 11 VT_FILETIME - Create Time/Date PID_CREATE_DTM 12 VT_FILETIME - Last Save Time/Date PID_LASTSAVE_DTM 13 VT_FILETIME - Page Count PID_PAGECOUNT 14 VT_I4 - Word Count PID_WORDCOUNT 15 VT_I4 - Character Count PID_CHARCOUNT 16 VT_I4 - Creating Application PID_APPNAME 18 VT_LPSTR - Security PID_SECURITY 19 VT_I4 - */ - public class SummaryInfoProperties - { - public short CodePage { get; set; } - public string Title { get; set; } - public string Subject { get; set; } - public string Author { get; set; } - public string KeyWords { get; set; } - public string Comments { get; set; } - public string Template { get; set; } - public string LastSavedBy { get; set; } - public string RevisionNumber { get; set; } - public DateTime LastPrinted { get; set; } - public DateTime CreateTime { get; set; } - public DateTime LastSavedTime { get; set; } - public int PageCount { get; set; } - public int WordCount { get; set; } - public int CharacterCount { get; set; } - public string CreatingApplication { get; set; } - public int Security { get; set; } - } - - public static OLEPropertiesContainer CreateNewSummaryInfo(SummaryInfoProperties sumInfoProps) - { - return null; - } - - public OLEPropertiesContainer(int codePage, ContainerType containerType) - { - Context = new PropertyContext - { - CodePage = codePage, - Behavior = Behavior.CaseInsensitive - }; - - ContainerType = containerType; - } - - internal OLEPropertiesContainer(CFStream cfStream) - { - PropertySetStream pStream = new PropertySetStream(); - - this.cfStream = cfStream; - - using StreamDecorator stream = new(cfStream); - using BinaryReader reader = new(stream); - pStream.Read(reader); - - ContainerType = pStream.FMTID0.ToString("B").ToUpperInvariant() switch - { - WellKnownFMTID.FMTID_SummaryInformation => ContainerType.SummaryInfo, - WellKnownFMTID.FMTID_DocSummaryInformation => ContainerType.DocumentSummaryInfo, - _ => ContainerType.AppSpecific, - }; - FmtID0 = pStream.FMTID0; - - PropertyNames = (Dictionary)pStream.PropertySet0.Properties - .FirstOrDefault(p => p.PropertyType == PropertyType.DictionaryProperty)?.Value; - - Context = new PropertyContext() - { - CodePage = pStream.PropertySet0.PropertyContext.CodePage - }; - - for (int i = 0; i < pStream.PropertySet0.Properties.Count; i++) - { - if (pStream.PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier == 0) continue; - //if (pStream.PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier == 1) continue; - //if (pStream.PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier == 0x80000000) continue; - - var p = (ITypedPropertyValue)pStream.PropertySet0.Properties[i]; - var poi = pStream.PropertySet0.PropertyIdentifierAndOffsets[i]; - - var op = new OLEProperty(this) - { - VTType = p.VTType, - PropertyIdentifier = pStream.PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier, - Value = p.Value - }; - - properties.Add(op); - } - - if (pStream.NumPropertySets == 2) - { - UserDefinedProperties = new OLEPropertiesContainer(pStream.PropertySet1.PropertyContext.CodePage, ContainerType.UserDefinedProperties); - HasUserDefinedProperties = true; - - for (int i = 0; i < pStream.PropertySet1.Properties.Count; i++) - { - if (pStream.PropertySet1.PropertyIdentifierAndOffsets[i].PropertyIdentifier == 0) continue; - //if (pStream.PropertySet1.PropertyIdentifierAndOffsets[i].PropertyIdentifier == 1) continue; - if (pStream.PropertySet1.PropertyIdentifierAndOffsets[i].PropertyIdentifier == 0x80000000) continue; - - var p = (ITypedPropertyValue)pStream.PropertySet1.Properties[i]; - var poi = pStream.PropertySet1.PropertyIdentifierAndOffsets[i]; - - var op = new OLEProperty(UserDefinedProperties) - { - VTType = p.VTType, - PropertyIdentifier = pStream.PropertySet1.PropertyIdentifierAndOffsets[i].PropertyIdentifier, - Value = p.Value - }; - - UserDefinedProperties.properties.Add(op); - } - - var existingPropertyNames = (Dictionary)pStream.PropertySet1.Properties - .FirstOrDefault(p => p.PropertyType == PropertyType.DictionaryProperty)?.Value; - - UserDefinedProperties.PropertyNames = existingPropertyNames ?? new Dictionary(); - } - } - - public IEnumerable Properties => properties; - - public OLEProperty NewProperty(VTPropertyType vtPropertyType, uint propertyIdentifier, string propertyName = null) - { - //throw new NotImplementedException("API Unstable - Work in progress - Milestone 2.3.0.0"); - var op = new OLEProperty(this) - { - VTType = vtPropertyType, - PropertyIdentifier = propertyIdentifier - }; - - return op; - } - - public void AddProperty(OLEProperty property) - { - //throw new NotImplementedException("API Unstable - Work in progress - Milestone 2.3.0.0"); - properties.Add(property); - } - - /// - /// Create a new UserDefinedProperty. - /// - /// The type of property to create. - /// The name of the new property. - /// The new property. - /// If UserDefinedProperties aren't allowed for this container. - /// If a property with the name already exists."/> - public OLEProperty AddUserDefinedProperty(VTPropertyType vtPropertyType, string name) - { - // @@TBD@@ If this is a DocumentSummaryInfo container, we could forward the add on to that. - if (this.ContainerType != ContainerType.UserDefinedProperties) - { - throw new InvalidOperationException($"UserDefinedProperties are not allowed in containers of type {this.ContainerType}"); - } - - // As per https://learn.microsoft.com/en-us/openspecs/windows_protocols/MS-OLEPS/4177a4bc-5547-49fe-a4d9-4767350fd9cf - // the property names have to be unique, and are case insensitive. - if (this.PropertyNames.Any(property => property.Value.Equals(name, StringComparison.InvariantCultureIgnoreCase))) - { - throw new ArgumentException($"User defined property names must be unique and {name} already exists", nameof(name)); - } - - // Work out a property identifier - must be > 1 and unique as per - // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-oleps/333959a3-a999-4eca-8627-48a224e63e77 - uint identifier = 2; - - if (this.PropertyNames.Count > 0) - { - uint highestIdentifier = this.PropertyNames.Keys.Max(); - identifier = Math.Max(highestIdentifier, 2) + 1; - } - - this.PropertyNames[identifier] = name; - - var op = new OLEProperty(this) - { - VTType = vtPropertyType, - PropertyIdentifier = identifier - }; - - properties.Add(op); - - return op; - } - - public void RemoveProperty(uint propertyIdentifier) - { - //throw new NotImplementedException("API Unstable - Work in progress - Milestone 2.3.0.0"); - var toRemove = properties.FirstOrDefault(o => o.PropertyIdentifier == propertyIdentifier); - - if (toRemove != null) - properties.Remove(toRemove); - } - - /// - /// Create a new UserDefinedProperties container within this container. - /// - /// - /// Only containers of type DocumentSummaryInfo can contain user defined properties. - /// - /// The code page to use for the user defined properties. - /// The UserDefinedProperties container. - /// If this container is a type that doesn't suppose user defined properties. - public OLEPropertiesContainer CreateUserDefinedProperties(int codePage) - { - // Only the DocumentSummaryInfo stream can contain a UserDefinedProperties - if (ContainerType != ContainerType.DocumentSummaryInfo) - { - throw new CFInvalidOperation($"Only a DocumentSummaryInfo can contain user defined properties. Current container type is {ContainerType}"); - } - - // Create the container, and add the codepage to the initial set of properties - UserDefinedProperties = new OLEPropertiesContainer(codePage, ContainerType.UserDefinedProperties) - { - PropertyNames = new Dictionary() - }; - - var op = new OLEProperty(UserDefinedProperties) - { - VTType = VTPropertyType.VT_I2, - PropertyIdentifier = 1, - Value = (short)codePage - }; - - UserDefinedProperties.properties.Add(op); - HasUserDefinedProperties = true; - - return UserDefinedProperties; - } - - public void Save(CFStream cfStream) - { - //throw new NotImplementedException("API Unstable - Work in progress - Milestone 2.3.0.0"); - //properties.Sort((a, b) => a.PropertyIdentifier.CompareTo(b.PropertyIdentifier)); - - using StreamDecorator s = new(cfStream); - using BinaryWriter bw = new BinaryWriter(s); - - Guid fmtId0 = FmtID0 ?? (ContainerType == ContainerType.SummaryInfo ? new Guid(WellKnownFMTID.FMTID_SummaryInformation) : new Guid(WellKnownFMTID.FMTID_DocSummaryInformation)); - - PropertySetStream ps = new PropertySetStream - { - ByteOrder = 0xFFFE, - Version = 0, - SystemIdentifier = 0x00020006, - CLSID = Guid.Empty, - - NumPropertySets = 1, - - FMTID0 = fmtId0, - Offset0 = 0, - - FMTID1 = Guid.Empty, - Offset1 = 0, - - PropertySet0 = new PropertySet - { - NumProperties = (uint)Properties.Count(), - PropertyIdentifierAndOffsets = new List(), - Properties = new List(), - PropertyContext = Context - } - }; - - // If we're writing an AppSpecific property set and have property names, then add a dictionary property - if (ContainerType == ContainerType.AppSpecific && PropertyNames != null && PropertyNames.Count > 0) - { - AddDictionaryPropertyToPropertySet(PropertyNames, ps.PropertySet0); - ps.PropertySet0.NumProperties += 1; - } - - PropertyFactory factory = - ContainerType == ContainerType.DocumentSummaryInfo ? DocumentSummaryInfoPropertyFactory.Instance : DefaultPropertyFactory.Instance; - - foreach (var op in Properties) - { - ITypedPropertyValue p = factory.NewProperty(op.VTType, Context.CodePage, op.PropertyIdentifier); - p.Value = op.Value; - ps.PropertySet0.Properties.Add(p); - ps.PropertySet0.PropertyIdentifierAndOffsets.Add(new PropertyIdentifierAndOffset() { PropertyIdentifier = op.PropertyIdentifier, Offset = 0 }); - } - - if (HasUserDefinedProperties) - { - ps.NumPropertySets = 2; - - ps.PropertySet1 = new PropertySet - { - // Number of user defined properties, plus 1 for the name dictionary - NumProperties = (uint)UserDefinedProperties.Properties.Count() + 1, - PropertyIdentifierAndOffsets = new List(), - Properties = new List(), - PropertyContext = UserDefinedProperties.Context - }; - - ps.FMTID1 = new Guid(WellKnownFMTID.FMTID_UserDefinedProperties); - ps.Offset1 = 0; - - // Add the dictionary containing the property names - AddDictionaryPropertyToPropertySet(UserDefinedProperties.PropertyNames, ps.PropertySet1); - - // Add the properties themselves - foreach (var op in UserDefinedProperties.Properties) - { - ITypedPropertyValue p = DefaultPropertyFactory.Instance.NewProperty(op.VTType, ps.PropertySet1.PropertyContext.CodePage, op.PropertyIdentifier); - p.Value = op.Value; - ps.PropertySet1.Properties.Add(p); - ps.PropertySet1.PropertyIdentifierAndOffsets.Add(new PropertyIdentifierAndOffset() { PropertyIdentifier = op.PropertyIdentifier, Offset = 0 }); - } - } - - ps.Write(bw); - } - - private static void AddDictionaryPropertyToPropertySet(Dictionary propertyNames, PropertySet propertySet) - { - IDictionaryProperty dictionaryProperty = new DictionaryProperty(propertySet.PropertyContext.CodePage) - { - Value = propertyNames - }; - propertySet.Properties.Add(dictionaryProperty); - propertySet.PropertyIdentifierAndOffsets.Add(new PropertyIdentifierAndOffset() { PropertyIdentifier = 0, Offset = 0 }); - } - } -} diff --git a/sources/OpenMcdf.Extensions/OLEProperties/OLEProperty.cs b/sources/OpenMcdf.Extensions/OLEProperties/OLEProperty.cs deleted file mode 100644 index 85048f31..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/OLEProperty.cs +++ /dev/null @@ -1,65 +0,0 @@ -namespace OpenMcdf.Extensions.OLEProperties -{ - public class OLEProperty - { - private readonly OLEPropertiesContainer container; - - internal OLEProperty(OLEPropertiesContainer container) - { - this.container = container; - } - - public string PropertyName => DecodePropertyIdentifier(); - - private string DecodePropertyIdentifier() - { - return PropertyIdentifier.GetDescription(container.ContainerType, container.PropertyNames); - } - - //public string Description { get { return description; } - public uint PropertyIdentifier { get; internal set; } - - public VTPropertyType VTType - { - get; - internal set; - } - - object value; - - public object Value - { - get - { - switch (VTType) - { - case VTPropertyType.VT_LPSTR: - case VTPropertyType.VT_LPWSTR: - if (value is string str && !string.IsNullOrEmpty(str)) - return str.Trim('\0'); - break; - default: - return value; - } - - return value; - } - set => this.value = value; - } - - public override bool Equals(object obj) - { - if (obj is not OLEProperty other) - return false; - - return other.PropertyIdentifier == PropertyIdentifier; - } - - public override int GetHashCode() - { - return (int)PropertyIdentifier; - } - - public override string ToString() => $"{PropertyName} - {VTType} - {Value}"; - } -} \ No newline at end of file diff --git a/sources/OpenMcdf.Extensions/OLEProperties/PropertyFactory.cs b/sources/OpenMcdf.Extensions/OLEProperties/PropertyFactory.cs deleted file mode 100644 index a787b8ae..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/PropertyFactory.cs +++ /dev/null @@ -1,690 +0,0 @@ -using OpenMcdf.Extensions.OLEProperties.Interfaces; -using System; -using System.IO; -using System.Text; - -namespace OpenMcdf.Extensions.OLEProperties -{ - internal abstract class PropertyFactory - { - static PropertyFactory() - { -#if NETSTANDARD2_0_OR_GREATER - Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); -#endif - } - - public ITypedPropertyValue NewProperty(VTPropertyType vType, int codePage, uint propertyIdentifier, bool isVariant = false) - { - ITypedPropertyValue pr = (VTPropertyType)((ushort)vType & 0x00FF) switch - { - VTPropertyType.VT_I1 => new VT_I1_Property(vType, isVariant), - VTPropertyType.VT_I2 => new VT_I2_Property(vType, isVariant), - VTPropertyType.VT_I4 => new VT_I4_Property(vType, isVariant), - VTPropertyType.VT_R4 => new VT_R4_Property(vType, isVariant), - VTPropertyType.VT_R8 => new VT_R8_Property(vType, isVariant), - VTPropertyType.VT_CY => new VT_CY_Property(vType, isVariant), - VTPropertyType.VT_DATE => new VT_DATE_Property(vType, isVariant), - VTPropertyType.VT_INT => new VT_INT_Property(vType, isVariant), - VTPropertyType.VT_UINT => new VT_UINT_Property(vType, isVariant), - VTPropertyType.VT_UI1 => new VT_UI1_Property(vType, isVariant), - VTPropertyType.VT_UI2 => new VT_UI2_Property(vType, isVariant), - VTPropertyType.VT_UI4 => new VT_UI4_Property(vType, isVariant), - VTPropertyType.VT_UI8 => new VT_UI8_Property(vType, isVariant), - VTPropertyType.VT_BSTR => new VT_LPSTR_Property(vType, codePage, isVariant), - VTPropertyType.VT_LPSTR => CreateLpstrProperty(vType, codePage, propertyIdentifier, isVariant), - VTPropertyType.VT_LPWSTR => new VT_LPWSTR_Property(vType, codePage, isVariant), - VTPropertyType.VT_FILETIME => new VT_FILETIME_Property(vType, isVariant), - VTPropertyType.VT_DECIMAL => new VT_DECIMAL_Property(vType, isVariant), - VTPropertyType.VT_BOOL => new VT_BOOL_Property(vType, isVariant), - VTPropertyType.VT_EMPTY => new VT_EMPTY_Property(vType, isVariant), - VTPropertyType.VT_VARIANT_VECTOR => new VT_VariantVector(vType, codePage, isVariant, this, propertyIdentifier), - VTPropertyType.VT_CF => new VT_CF_Property(vType, isVariant), - VTPropertyType.VT_BLOB_OBJECT or VTPropertyType.VT_BLOB => new VT_BLOB_Property(vType, isVariant), - VTPropertyType.VT_CLSID => new VT_CLSID_Property(vType, isVariant), - _ => throw new Exception("Unrecognized property type"), - }; - return pr; - } - - protected virtual ITypedPropertyValue CreateLpstrProperty(VTPropertyType vType, int codePage, uint propertyIdentifier, bool isVariant) - { - return new VT_LPSTR_Property(vType, codePage, isVariant); - } - - #region Property implementations - - private sealed class VT_EMPTY_Property : TypedPropertyValue - { - public VT_EMPTY_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override object ReadScalarValue(BinaryReader br) - { - return null; - } - - public override void WriteScalarValue(BinaryWriter bw, object pValue) - { - } - } - - private sealed class VT_I1_Property : TypedPropertyValue - { - public VT_I1_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override sbyte ReadScalarValue(BinaryReader br) - { - var r = br.ReadSByte(); - return r; - } - - public override void WriteScalarValue(BinaryWriter bw, sbyte pValue) - { - bw.Write(pValue); - } - } - - private sealed class VT_UI1_Property : TypedPropertyValue - { - public VT_UI1_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override byte ReadScalarValue(BinaryReader br) - { - var r = br.ReadByte(); - return r; - } - - public override void WriteScalarValue(BinaryWriter bw, byte pValue) - { - bw.Write(pValue); - } - } - - private sealed class VT_UI4_Property : TypedPropertyValue - { - public VT_UI4_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override uint ReadScalarValue(BinaryReader br) - { - var r = br.ReadUInt32(); - return r; - } - - public override void WriteScalarValue(BinaryWriter bw, uint pValue) - { - bw.Write(pValue); - } - } - - private sealed class VT_UI8_Property : TypedPropertyValue - { - public VT_UI8_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override ulong ReadScalarValue(BinaryReader br) - { - var r = br.ReadUInt64(); - return r; - } - - public override void WriteScalarValue(BinaryWriter bw, ulong pValue) - { - bw.Write(pValue); - } - } - - private sealed class VT_I2_Property : TypedPropertyValue - { - public VT_I2_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override short ReadScalarValue(BinaryReader br) - { - var r = br.ReadInt16(); - return r; - } - - public override void WriteScalarValue(BinaryWriter bw, short pValue) - { - bw.Write(pValue); - } - } - - private sealed class VT_UI2_Property : TypedPropertyValue - { - public VT_UI2_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override ushort ReadScalarValue(BinaryReader br) - { - var r = br.ReadUInt16(); - return r; - } - - public override void WriteScalarValue(BinaryWriter bw, ushort pValue) - { - bw.Write(pValue); - } - } - - private sealed class VT_I4_Property : TypedPropertyValue - { - public VT_I4_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override int ReadScalarValue(BinaryReader br) - { - var r = br.ReadInt32(); - return r; - } - - public override void WriteScalarValue(BinaryWriter bw, int pValue) - { - bw.Write(pValue); - } - } - - private sealed class VT_I8_Property : TypedPropertyValue - { - public VT_I8_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override long ReadScalarValue(BinaryReader br) - { - var r = br.ReadInt64(); - return r; - } - - public override void WriteScalarValue(BinaryWriter bw, long pValue) - { - bw.Write(pValue); - } - } - - private sealed class VT_INT_Property : TypedPropertyValue - { - public VT_INT_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override int ReadScalarValue(BinaryReader br) - { - var r = br.ReadInt32(); - return r; - } - - public override void WriteScalarValue(BinaryWriter bw, int pValue) - { - bw.Write(pValue); - } - } - - private sealed class VT_UINT_Property : TypedPropertyValue - { - public VT_UINT_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override uint ReadScalarValue(BinaryReader br) - { - var r = br.ReadUInt32(); - return r; - } - - public override void WriteScalarValue(BinaryWriter bw, uint pValue) - { - bw.Write(pValue); - } - } - - private sealed class VT_R4_Property : TypedPropertyValue - { - public VT_R4_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override float ReadScalarValue(BinaryReader br) - { - var r = br.ReadSingle(); - return r; - } - - public override void WriteScalarValue(BinaryWriter bw, float pValue) - { - bw.Write(pValue); - } - } - - private sealed class VT_R8_Property : TypedPropertyValue - { - public VT_R8_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override double ReadScalarValue(BinaryReader br) - { - var r = br.ReadDouble(); - return r; - } - - public override void WriteScalarValue(BinaryWriter bw, double pValue) - { - bw.Write(pValue); - } - } - - private sealed class VT_CY_Property : TypedPropertyValue - { - public VT_CY_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override long ReadScalarValue(BinaryReader br) - { - long temp = br.ReadInt64(); - - var tmp = temp /= 10000; - - return tmp; - } - - public override void WriteScalarValue(BinaryWriter bw, long pValue) - { - bw.Write(pValue * 10000); - } - } - - private sealed class VT_DATE_Property : TypedPropertyValue - { - public VT_DATE_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override DateTime ReadScalarValue(BinaryReader br) - { - double temp = br.ReadDouble(); - - return DateTime.FromOADate(temp); - } - - public override void WriteScalarValue(BinaryWriter bw, DateTime pValue) - { - bw.Write(pValue.ToOADate()); - } - } - - protected class VT_LPSTR_Property : TypedPropertyValue - { - private byte[] data; - private readonly int codePage; - - public VT_LPSTR_Property(VTPropertyType vType, int codePage, bool isVariant) : base(vType, isVariant) - { - this.codePage = codePage; - } - - public override string ReadScalarValue(BinaryReader br) - { - uint size = br.ReadUInt32(); - data = br.ReadBytes((int)size); - - var result = Encoding.GetEncoding(codePage).GetString(data); - //result = result.Trim(new char[] { '\0' }); - - //if (this.codePage == CodePages.CP_WINUNICODE) - //{ - // result = result.Substring(0, result.Length - 2); - //} - //else - //{ - // result = result.Substring(0, result.Length - 1); - //} - - return result; - } - - public override void WriteScalarValue(BinaryWriter bw, string pValue) - { - //bool addNullTerminator = true; - - if (string.IsNullOrEmpty(pValue)) //|| String.IsNullOrEmpty(pValue.Trim(new char[] { '\0' }))) - { - bw.Write((uint)0); - } - else if (codePage == CodePages.CP_WINUNICODE) - { - data = Encoding.GetEncoding(codePage).GetBytes(pValue); - - //if (data.Length >= 2 && data[data.Length - 2] == '\0' && data[data.Length - 1] == '\0') - // addNullTerminator = false; - - uint dataLength = (uint)data.Length; - - //if (addNullTerminator) - dataLength += 2; // null terminator \u+0000 - - // var mod = dataLength % 4; // pad to multiple of 4 bytes - - bw.Write(dataLength); // datalength of string + null char (unicode) - bw.Write(data); // string - - //if (addNullTerminator) - //{ - bw.Write('\0'); // first byte of null unicode char - bw.Write('\0'); // second byte of null unicode char - //} - - //for (int i = 0; i < (4 - mod); i++) // padding - // bw.Write('\0'); - } - else - { - data = Encoding.GetEncoding(codePage).GetBytes(pValue); - - //if (data.Length >= 1 && data[data.Length - 1] == '\0') - // addNullTerminator = false; - - uint dataLength = (uint)data.Length; - - //if (addNullTerminator) - dataLength += 1; // null terminator \u+0000 - - bw.Write(dataLength); // datalength of string + null char (unicode) - bw.Write(data); // string - - //if (addNullTerminator) - //{ - bw.Write('\0'); // null terminator'\0' - //} - - //for (int i = 0; i < (4 - mod); i++) // padding - // bw.Write('\0'); - } - } - } - - protected sealed class VT_Unaligned_LPSTR_Property : VT_LPSTR_Property - { - public VT_Unaligned_LPSTR_Property(VTPropertyType vType, int codePage, bool isVariant) : base(vType, codePage, isVariant) - { - NeedsPadding = false; - } - } - - private sealed class VT_LPWSTR_Property : TypedPropertyValue - { - private byte[] data; - private readonly int codePage; - - public VT_LPWSTR_Property(VTPropertyType vType, int codePage, bool isVariant) : base(vType, isVariant) - { - this.codePage = codePage; - } - - public override string ReadScalarValue(BinaryReader br) - { - uint nChars = br.ReadUInt32(); - data = br.ReadBytes((int)((nChars - 1) * 2)); //WChar- null terminator - br.ReadBytes(2); // Skip null terminator - var result = Encoding.Unicode.GetString(data); - //result = result.Trim(new char[] { '\0' }); - - return result; - } - - public override void WriteScalarValue(BinaryWriter bw, string pValue) - { - data = Encoding.Unicode.GetBytes(pValue); - - // The written data length field is the number of characters (not bytes) and must include a null terminator - // add a null terminator if there isn't one already - var byteLength = data.Length; - - //bool addNullTerminator = - // byteLength == 0 || data[byteLength - 1] != '\0' || data[byteLength - 2] != '\0'; - - //if (addNullTerminator) - byteLength += 2; - - bw.Write((uint)byteLength / 2); - bw.Write(data); - - //if (addNullTerminator) - //{ - bw.Write((byte)0); - bw.Write((byte)0); - //} - - //var mod = byteLength % 4; // pad to multiple of 4 bytes - //for (int i = 0; i < (4 - mod); i++) // padding - // bw.Write('\0'); - } - } - - private sealed class VT_FILETIME_Property : TypedPropertyValue - { - public VT_FILETIME_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override DateTime ReadScalarValue(BinaryReader br) - { - long tmp = br.ReadInt64(); - - return DateTime.FromFileTimeUtc(tmp); - } - - public override void WriteScalarValue(BinaryWriter bw, DateTime pValue) - { - bw.Write(pValue.ToFileTimeUtc()); - } - } - - private sealed class VT_DECIMAL_Property : TypedPropertyValue - { - public VT_DECIMAL_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override decimal ReadScalarValue(BinaryReader br) - { - decimal d; - - br.ReadInt16(); // wReserved - byte scale = br.ReadByte(); - byte sign = br.ReadByte(); - - uint u = br.ReadUInt32(); - d = Convert.ToDecimal(Math.Pow(2, 64)) * u; - d += br.ReadUInt64(); - - if (sign != 0) - d = -d; - d /= 10 << scale; - - propertyValue = d; - return d; - } - - public override void WriteScalarValue(BinaryWriter bw, decimal pValue) - { - int[] parts = decimal.GetBits(pValue); - - bool sign = (parts[3] & 0x80000000) != 0; - byte scale = (byte)((parts[3] >> 16) & 0x7F); - - bw.Write((short)0); - bw.Write(scale); - bw.Write(sign ? (byte)0 : (byte)1); - - bw.Write(parts[2]); - bw.Write(parts[1]); - bw.Write(parts[0]); - } - } - - private sealed class VT_BOOL_Property : TypedPropertyValue - { - public VT_BOOL_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override bool ReadScalarValue(BinaryReader br) - { - propertyValue = br.ReadUInt16() == 0xFFFF; - return (bool)propertyValue; - //br.ReadUInt16();//padding - } - - public override void WriteScalarValue(BinaryWriter bw, bool pValue) - { - bw.Write(pValue ? (ushort)0xFFFF : (ushort)0); - } - } - - private sealed class VT_CF_Property : TypedPropertyValue - { - public VT_CF_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override object ReadScalarValue(BinaryReader br) - { - uint size = br.ReadUInt32(); - byte[] data = br.ReadBytes((int)size); - return data; - //br.ReadUInt16();//padding - } - - public override void WriteScalarValue(BinaryWriter bw, object pValue) - { - if (pValue is not byte[] r) - { - bw.Write(0u); - } - else - { - bw.Write((uint)r.Length); - bw.Write(r); - } - } - } - - private sealed class VT_BLOB_Property : TypedPropertyValue - { - public VT_BLOB_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override object ReadScalarValue(BinaryReader br) - { - uint size = br.ReadUInt32(); - byte[] data = br.ReadBytes((int)size); - return data; - } - - public override void WriteScalarValue(BinaryWriter bw, object pValue) - { - if (pValue is not byte[] r) - { - bw.Write(0u); - } - else - { - bw.Write((uint)r.Length); - bw.Write(r); - } - } - } - - private sealed class VT_CLSID_Property : TypedPropertyValue - { - public VT_CLSID_Property(VTPropertyType vType, bool isVariant) : base(vType, isVariant) - { - } - - public override object ReadScalarValue(BinaryReader br) - { - byte[] data = br.ReadBytes(16); - return new Guid(data); - } - - public override void WriteScalarValue(BinaryWriter bw, object pValue) - { - if (pValue is byte[] r) - bw.Write(r); - } - } - - private sealed class VT_VariantVector : TypedPropertyValue - { - private readonly int codePage; - private readonly PropertyFactory factory; - private readonly uint propertyIdentifier; - - public VT_VariantVector(VTPropertyType vType, int codePage, bool isVariant, PropertyFactory factory, uint propertyIdentifier) : base(vType, isVariant) - { - this.codePage = codePage; - this.factory = factory; - this.propertyIdentifier = propertyIdentifier; - NeedsPadding = false; - } - - public override object ReadScalarValue(BinaryReader br) - { - VTPropertyType vType = (VTPropertyType)br.ReadUInt16(); - br.ReadUInt16(); // Ushort Padding - - ITypedPropertyValue p = factory.NewProperty(vType, codePage, propertyIdentifier, true); - p.Read(br); - return p; - } - - public override void WriteScalarValue(BinaryWriter bw, object pValue) - { - ITypedPropertyValue p = (ITypedPropertyValue)pValue; - - p.Write(bw); - } - } - - #endregion - - } - - // The default property factory. - internal sealed class DefaultPropertyFactory : PropertyFactory - { - public static PropertyFactory Instance { get; } = new DefaultPropertyFactory(); - } - - // A separate factory for DocumentSummaryInformation properties, to handle special cases with unaligned strings. - internal sealed class DocumentSummaryInfoPropertyFactory : PropertyFactory - { - public static PropertyFactory Instance { get; } = new DocumentSummaryInfoPropertyFactory(); - - protected override ITypedPropertyValue CreateLpstrProperty(VTPropertyType vType, int codePage, uint propertyIdentifier, bool isVariant) - { - // PIDDSI_HEADINGPAIR and PIDDSI_DOCPARTS use unaligned (unpadded) strings - the others are padded - if (propertyIdentifier is 0x0000000C or 0x0000000D) - { - return new VT_Unaligned_LPSTR_Property(vType, codePage, isVariant); - } - - return base.CreateLpstrProperty(vType, codePage, propertyIdentifier, isVariant); - } - } -} diff --git a/sources/OpenMcdf.Extensions/OLEProperties/PropertyIdentifierAndOffset.cs b/sources/OpenMcdf.Extensions/OLEProperties/PropertyIdentifierAndOffset.cs deleted file mode 100644 index 62a63230..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/PropertyIdentifierAndOffset.cs +++ /dev/null @@ -1,23 +0,0 @@ -using OpenMcdf.Extensions.OLEProperties.Interfaces; -using System.IO; - -namespace OpenMcdf.Extensions.OLEProperties -{ - public class PropertyIdentifierAndOffset : IBinarySerializable - { - public uint PropertyIdentifier { get; set; } - public uint Offset { get; set; } - - public void Read(BinaryReader br) - { - PropertyIdentifier = br.ReadUInt32(); - Offset = br.ReadUInt32(); - } - - public void Write(BinaryWriter bw) - { - bw.Write(PropertyIdentifier); - bw.Write(Offset); - } - } -} \ No newline at end of file diff --git a/sources/OpenMcdf.Extensions/OLEProperties/PropertyReader.cs b/sources/OpenMcdf.Extensions/OLEProperties/PropertyReader.cs deleted file mode 100644 index 0274394d..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/PropertyReader.cs +++ /dev/null @@ -1,34 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.IO; -//using System.Text; -//using OpenMcdf.Extensions.OLEProperties.Interfaces; -//using System.Collections; - -//namespace OpenMcdf.Extensions.OLEProperties -//{ - -// public class PropertyReader -// { -// public PropertyContext Context { get { return ctx; } } -// private PropertyContext ctx = new PropertyContext(); - - -// public PropertyReader(int codePageOffset, BinaryReader br) -// { -// br.BaseStream.Seek(codePageOffset, SeekOrigin.Begin); - -// VTPropertyType vType = (VTPropertyType)br.ReadUInt16(); -// br.ReadUInt16(); // Ushort Padding - -// ITypedPropertyValue pr = PropertyFactory.Instance.NewProperty(vType, ctx); -// pr.Read(br); - -// this.ctx.CodePage = (int)(ushort)(short)pr.Value; -// } - - - - -// } -//} diff --git a/sources/OpenMcdf.Extensions/OLEProperties/PropertySet.cs b/sources/OpenMcdf.Extensions/OLEProperties/PropertySet.cs deleted file mode 100644 index 98aa9893..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/PropertySet.cs +++ /dev/null @@ -1,38 +0,0 @@ -using OpenMcdf.Extensions.OLEProperties.Interfaces; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace OpenMcdf.Extensions.OLEProperties -{ - internal sealed class PropertySet - { - public PropertyContext PropertyContext - { - get; set; - } - - public uint Size { get; set; } - - public uint NumProperties { get; set; } - - public List PropertyIdentifierAndOffsets { get; set; } = new List(); - - public List Properties { get; set; } = new List(); - - public void LoadContext(int propertySetOffset, BinaryReader br) - { - var currPos = br.BaseStream.Position; - - PropertyContext = new PropertyContext(); - var codePageOffset = (int)(propertySetOffset + PropertyIdentifierAndOffsets.First(pio => pio.PropertyIdentifier == 1).Offset); - br.BaseStream.Seek(codePageOffset, SeekOrigin.Begin); - - VTPropertyType vType = (VTPropertyType)br.ReadUInt16(); - br.ReadUInt16(); // Ushort Padding - PropertyContext.CodePage = (ushort)br.ReadInt16(); - - br.BaseStream.Position = currPos; - } - } -} diff --git a/sources/OpenMcdf.Extensions/OLEProperties/PropertySetStream.cs b/sources/OpenMcdf.Extensions/OLEProperties/PropertySetStream.cs deleted file mode 100644 index af0dc066..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/PropertySetStream.cs +++ /dev/null @@ -1,242 +0,0 @@ -using OpenMcdf.Extensions.OLEProperties.Interfaces; -using System; -using System.Collections.Generic; -using System.IO; - -namespace OpenMcdf.Extensions.OLEProperties -{ - internal sealed class PropertySetStream - { - public ushort ByteOrder { get; set; } - public ushort Version { get; set; } - public uint SystemIdentifier { get; set; } - public Guid CLSID { get; set; } - public uint NumPropertySets { get; set; } - public Guid FMTID0 { get; set; } - public uint Offset0 { get; set; } - public Guid FMTID1 { get; set; } - public uint Offset1 { get; set; } - public PropertySet PropertySet0 { get; set; } - public PropertySet PropertySet1 { get; set; } - - //private SummaryInfoMap map; - - public PropertySetStream() - { - } - - public void Read(BinaryReader br) - { - ByteOrder = br.ReadUInt16(); - Version = br.ReadUInt16(); - SystemIdentifier = br.ReadUInt32(); - CLSID = new Guid(br.ReadBytes(16)); - NumPropertySets = br.ReadUInt32(); - FMTID0 = new Guid(br.ReadBytes(16)); - Offset0 = br.ReadUInt32(); - - if (NumPropertySets == 2) - { - FMTID1 = new Guid(br.ReadBytes(16)); - Offset1 = br.ReadUInt32(); - } - - PropertySet0 = new PropertySet - { - Size = br.ReadUInt32(), - NumProperties = br.ReadUInt32() - }; - - // Create appropriate property factory based on the stream type - Guid docSummaryGuid = new Guid(WellKnownFMTID.FMTID_DocSummaryInformation); - PropertyFactory factory = FMTID0 == docSummaryGuid ? DocumentSummaryInfoPropertyFactory.Instance : DefaultPropertyFactory.Instance; - - // Read property offsets (P0) - for (int i = 0; i < PropertySet0.NumProperties; i++) - { - PropertyIdentifierAndOffset pio = new PropertyIdentifierAndOffset - { - PropertyIdentifier = br.ReadUInt32(), - Offset = br.ReadUInt32() - }; - PropertySet0.PropertyIdentifierAndOffsets.Add(pio); - } - - PropertySet0.LoadContext((int)Offset0, br); //Read CodePage, Locale - - // Read properties (P0) - for (int i = 0; i < PropertySet0.NumProperties; i++) - { - br.BaseStream.Seek(Offset0 + PropertySet0.PropertyIdentifierAndOffsets[i].Offset, SeekOrigin.Begin); - PropertySet0.Properties.Add(ReadProperty(PropertySet0.PropertyIdentifierAndOffsets[i].PropertyIdentifier, PropertySet0.PropertyContext.CodePage, br, factory)); - } - - if (NumPropertySets == 2) - { - br.BaseStream.Seek(Offset1, SeekOrigin.Begin); - PropertySet1 = new PropertySet - { - Size = br.ReadUInt32(), - NumProperties = br.ReadUInt32() - }; - - // Read property offsets - for (int i = 0; i < PropertySet1.NumProperties; i++) - { - PropertyIdentifierAndOffset pio = new PropertyIdentifierAndOffset - { - PropertyIdentifier = br.ReadUInt32(), - Offset = br.ReadUInt32() - }; - PropertySet1.PropertyIdentifierAndOffsets.Add(pio); - } - - PropertySet1.LoadContext((int)Offset1, br); - - // Read properties - for (int i = 0; i < PropertySet1.NumProperties; i++) - { - br.BaseStream.Seek(Offset1 + PropertySet1.PropertyIdentifierAndOffsets[i].Offset, SeekOrigin.Begin); - PropertySet1.Properties.Add(ReadProperty(PropertySet1.PropertyIdentifierAndOffsets[i].PropertyIdentifier, PropertySet1.PropertyContext.CodePage, br, DefaultPropertyFactory.Instance)); - } - } - } - - private sealed class OffsetContainer - { - public int OffsetPS { get; set; } - - public List PropertyIdentifierOffsets { get; set; } - public List PropertyOffsets { get; set; } - - public OffsetContainer() - { - PropertyOffsets = new List(); - PropertyIdentifierOffsets = new List(); - } - } - - public void Write(BinaryWriter bw) - { - var oc0 = new OffsetContainer(); - var oc1 = new OffsetContainer(); - - bw.Write(ByteOrder); - bw.Write(Version); - bw.Write(SystemIdentifier); - bw.Write(CLSID.ToByteArray()); - bw.Write(NumPropertySets); - bw.Write(FMTID0.ToByteArray()); - bw.Write(Offset0); - - if (NumPropertySets == 2) - { - bw.Write(FMTID1.ToByteArray()); - bw.Write(Offset1); - } - - oc0.OffsetPS = (int)bw.BaseStream.Position; - bw.Write(PropertySet0.Size); - bw.Write(PropertySet0.NumProperties); - - // w property offsets - for (int i = 0; i < PropertySet0.NumProperties; i++) - { - oc0.PropertyIdentifierOffsets.Add(bw.BaseStream.Position); //Offset of 4 to Offset value - PropertySet0.PropertyIdentifierAndOffsets[i].Write(bw); - } - - for (int i = 0; i < PropertySet0.NumProperties; i++) - { - oc0.PropertyOffsets.Add(bw.BaseStream.Position); - PropertySet0.Properties[i].Write(bw); - } - - var padding0 = bw.BaseStream.Position % 4; - - if (padding0 > 0) - { - for (int p = 0; p < 4 - padding0; p++) - bw.Write((byte)0); - } - - int size0 = (int)(bw.BaseStream.Position - oc0.OffsetPS); - - if (NumPropertySets == 2) - { - oc1.OffsetPS = (int)bw.BaseStream.Position; - - bw.Write(PropertySet1.Size); - bw.Write(PropertySet1.NumProperties); - - // w property offsets - for (int i = 0; i < PropertySet1.PropertyIdentifierAndOffsets.Count; i++) - { - oc1.PropertyIdentifierOffsets.Add(bw.BaseStream.Position); //Offset of 4 to Offset value - PropertySet1.PropertyIdentifierAndOffsets[i].Write(bw); - } - - for (int i = 0; i < PropertySet1.NumProperties; i++) - { - oc1.PropertyOffsets.Add(bw.BaseStream.Position); - PropertySet1.Properties[i].Write(bw); - } - - int size1 = (int)(bw.BaseStream.Position - oc1.OffsetPS); - - bw.Seek(oc1.OffsetPS, SeekOrigin.Begin); - bw.Write(size1); - } - - bw.Seek(oc0.OffsetPS, SeekOrigin.Begin); - bw.Write(size0); - - int shiftO1 = 2 + 2 + 4 + 16 + 4 + 16; //OFFSET0 - bw.Seek(shiftO1, SeekOrigin.Begin); - bw.Write(oc0.OffsetPS); - - if (NumPropertySets == 2) - { - bw.Seek(shiftO1 + 4 + 16, SeekOrigin.Begin); - bw.Write(oc1.OffsetPS); - } - - //----------- - - for (int i = 0; i < PropertySet0.PropertyIdentifierAndOffsets.Count; i++) - { - bw.Seek((int)oc0.PropertyIdentifierOffsets[i] + 4, SeekOrigin.Begin); //Offset of 4 to Offset value - bw.Write((int)(oc0.PropertyOffsets[i] - oc0.OffsetPS)); - } - - if (PropertySet1 != null) - { - for (int i = 0; i < PropertySet1.PropertyIdentifierAndOffsets.Count; i++) - { - bw.Seek((int)oc1.PropertyIdentifierOffsets[i] + 4, SeekOrigin.Begin); //Offset of 4 to Offset value - bw.Write((int)(oc1.PropertyOffsets[i] - oc1.OffsetPS)); - } - } - } - - private static IProperty ReadProperty(uint propertyIdentifier, int codePage, BinaryReader br, PropertyFactory factory) - { - if (propertyIdentifier != 0) - { - VTPropertyType vType = (VTPropertyType)br.ReadUInt16(); - br.ReadUInt16(); // Ushort Padding - - ITypedPropertyValue pr = factory.NewProperty(vType, codePage, propertyIdentifier); - pr.Read(br); - - return pr; - } - else - { - DictionaryProperty dictionaryProperty = new DictionaryProperty(codePage); - dictionaryProperty.Read(br); - return dictionaryProperty; - } - } - } -} \ No newline at end of file diff --git a/sources/OpenMcdf.Extensions/OLEProperties/ProperyIdentifiers.cs b/sources/OpenMcdf.Extensions/OLEProperties/ProperyIdentifiers.cs deleted file mode 100644 index 658c5f7c..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/ProperyIdentifiers.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Collections.Generic; - -namespace OpenMcdf.Extensions.OLEProperties -{ - public enum ContainerType - { - AppSpecific = 0, - SummaryInfo = 1, - DocumentSummaryInfo = 2, - UserDefinedProperties = 3, - GlobalInfo = 4, - ImageContents = 5, - ImageInfo = 6 - } - - public static class CommonIdentifiers - { - public static Dictionary PropertyIdentifiersSummaryInfo = new() - { - {0x00000001, "CodePageString" }, - {0x00000002, "PIDSI_TITLE" }, - {0x00000003, "PIDSI_SUBJECT" }, - {0x00000004, "PIDSI_AUTHOR" }, - {0x00000005, "PIDSI_KEYWORDS" }, - {0x00000006, "PIDSI_COMMENTS" }, - {0x00000007, "PIDSI_TEMPLATE" }, - {0x00000008, "PIDSI_LASTAUTHOR" }, - {0x00000009, "PIDSI_REVNUMBER" }, - {0x00000012, "PIDSI_APPNAME" }, - {0x0000000A, "PIDSI_EDITTIME" }, - {0x0000000B, "PIDSI_LASTPRINTED" }, - {0x0000000C, "PIDSI_CREATE_DTM" }, - {0x0000000D, "PIDSI_LASTSAVE_DTM" }, - {0x0000000E, "PIDSI_PAGECOUNT" }, - {0x0000000F, "PIDSI_WORDCOUNT" }, - {0x00000010, "PIDSI_CHARCOUNT" }, - {0x00000013, "PIDSI_DOC_SECURITY" } - }; - - public static Dictionary PropertyIdentifiersDocumentSummaryInfo = new() - { - {0x00000001, "CodePageString" }, - {0x00000002, "PIDDSI_CATEGORY" }, - {0x00000003, "PIDDSI_PRESFORMAT" }, - {0x00000004, "PIDDSI_BYTECOUNT" }, - {0x00000005, "PIDDSI_LINECOUNT" }, - {0x00000006, "PIDDSI_PARCOUNT" }, - {0x00000007, "PIDDSI_SLIDECOUNT" }, - {0x00000008, "PIDDSI_NOTECOUNT" }, - {0x00000009, "PIDDSI_HIDDENCOUNT" }, - {0x0000000A, "PIDDSI_MMCLIPCOUNT" }, - {0x0000000B, "PIDDSI_SCALE" }, - {0x0000000C, "PIDDSI_HEADINGPAIR" }, - {0x0000000D, "PIDDSI_DOCPARTS" }, - {0x0000000E, "PIDDSI_MANAGER" }, - {0x0000000F, "PIDDSI_COMPANY" }, - {0x00000010, "PIDDSI_LINKSDIRTY" } - }; - } - - public static class Extensions - { - public static string GetDescription(this uint identifier, ContainerType map, Dictionary customDict = null) - { - Dictionary nameDictionary = customDict; - - if (nameDictionary is null) - { - switch (map) - { - case ContainerType.SummaryInfo: - nameDictionary = CommonIdentifiers.PropertyIdentifiersSummaryInfo; - break; - case ContainerType.DocumentSummaryInfo: - nameDictionary = CommonIdentifiers.PropertyIdentifiersDocumentSummaryInfo; - break; - } - } - - if (nameDictionary?.TryGetValue(identifier, out string value) == true) - { - return value; - } - - return "0x" + identifier.ToString("x8"); - } - } -} \ No newline at end of file diff --git a/sources/OpenMcdf.Extensions/OLEProperties/TypedPropertyValue.cs b/sources/OpenMcdf.Extensions/OLEProperties/TypedPropertyValue.cs deleted file mode 100644 index febcdfd9..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/TypedPropertyValue.cs +++ /dev/null @@ -1,161 +0,0 @@ -using OpenMcdf.Extensions.OLEProperties.Interfaces; -using System.Collections.Generic; -using System.IO; - -namespace OpenMcdf.Extensions.OLEProperties -{ - internal abstract class TypedPropertyValue : ITypedPropertyValue - { - private readonly VTPropertyType _VTType; - - public PropertyType PropertyType => PropertyType.TypedPropertyValue; - - public VTPropertyType VTType => _VTType; - - protected object propertyValue; - - public TypedPropertyValue(VTPropertyType vtType, bool isVariant = false) - { - _VTType = vtType; - PropertyDimensions = CheckPropertyDimensions(vtType); - IsVariant = isVariant; - } - - public PropertyDimensions PropertyDimensions { get; } = PropertyDimensions.IsScalar; - - public bool IsVariant { get; } - - protected virtual bool NeedsPadding { get; set; } = true; - - private static PropertyDimensions CheckPropertyDimensions(VTPropertyType vtType) - { - if ((((ushort)vtType) & 0x1000) != 0) - return PropertyDimensions.IsVector; - else if ((((ushort)vtType) & 0x2000) != 0) - return PropertyDimensions.IsArray; - else - return PropertyDimensions.IsScalar; - } - - public virtual object Value - { - get => propertyValue; - - set => propertyValue = value; - } - - public abstract T ReadScalarValue(BinaryReader br); - - public void Read(BinaryReader br) - { - long currentPos = br.BaseStream.Position; - - switch (PropertyDimensions) - { - case PropertyDimensions.IsScalar: - { - propertyValue = ReadScalarValue(br); - int size = (int)(br.BaseStream.Position - currentPos); - - int m = size % 4; - - if (m > 0 && NeedsPadding) - br.ReadBytes(4 - m); // padding - } - - break; - - case PropertyDimensions.IsVector: - { - uint nItems = br.ReadUInt32(); - - List res = new List(); - - for (int i = 0; i < nItems; i++) - { - T s = ReadScalarValue(br); - - res.Add(s); - - // The padding in a vector can be per-item - int itemSize = (int)(br.BaseStream.Position - currentPos); - - int pad = itemSize % 4; - if (pad > 0 && NeedsPadding) - br.ReadBytes(4 - pad); // padding - } - - propertyValue = res; - int size = (int)(br.BaseStream.Position - currentPos); - - int m = size % 4; - if (m > 0 && NeedsPadding) - br.ReadBytes(4 - m); // padding - } - - break; - default: - break; - } - } - - public abstract void WriteScalarValue(BinaryWriter bw, T pValue); - - public void Write(BinaryWriter bw) - { - long currentPos = bw.BaseStream.Position; - int size; - int m; - switch (PropertyDimensions) - { - case PropertyDimensions.IsScalar: - - bw.Write((ushort)_VTType); - bw.Write((ushort)0); - - WriteScalarValue(bw, (T)propertyValue); - size = (int)(bw.BaseStream.Position - currentPos); - m = size % 4; - - if (m > 0 && NeedsPadding) - { - for (int i = 0; i < 4 - m; i++) // padding - bw.Write((byte)0); - } - - break; - - case PropertyDimensions.IsVector: - - bw.Write((ushort)_VTType); - bw.Write((ushort)0); - bw.Write((uint)((List)propertyValue).Count); - - for (int i = 0; i < ((List)propertyValue).Count; i++) - { - WriteScalarValue(bw, ((List)propertyValue)[i]); - - size = (int)(bw.BaseStream.Position - currentPos); - m = size % 4; - - if (m > 0 && NeedsPadding) - { - for (int q = 0; q < 4 - m; q++) // padding - bw.Write((byte)0); - } - } - - size = (int)(bw.BaseStream.Position - currentPos); - m = size % 4; - - if (m > 0 && NeedsPadding) - { - for (int i = 0; i < 4 - m; i++) // padding - bw.Write((byte)0); - } - - break; - } - } - } -} diff --git a/sources/OpenMcdf.Extensions/OLEProperties/VTPropertyType.cs b/sources/OpenMcdf.Extensions/OLEProperties/VTPropertyType.cs deleted file mode 100644 index 81c22b02..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/VTPropertyType.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace OpenMcdf.Extensions.OLEProperties -{ - public enum VTPropertyType : ushort - { - VT_EMPTY = 0x0000, - VT_NULL = 0x0001, - VT_I2 = 0x0002, - VT_I4 = 0x0003, - VT_R4 = 0x0004, - VT_R8 = 0x0005, - VT_CY = 0x0006, - VT_DATE = 0x0007, - VT_BSTR = 0x0008, - VT_ERROR = 0x000A, - VT_BOOL = 0x000B, - VT_DECIMAL = 0x000E, - VT_I1 = 0x0010, - VT_UI1 = 0x0011, - VT_UI2 = 0x0012, - VT_UI4 = 0x0013, - VT_I8 = 0x0014, // MUST be an 8-byte signed integer. - VT_UI8 = 0x0015, // MUST be an 8-byte unsigned integer. - VT_INT = 0x0016, // MUST be a 4-byte signed integer. - VT_UINT = 0x0017, // MUST be a 4-byte unsigned integer. - VT_LPSTR = 0x001E, // MUST be a CodePageString. - VT_LPWSTR = 0x001F, // MUST be a UnicodeString. - VT_FILETIME = 0x0040, // MUST be a FILETIME (Packet Version). - VT_BLOB = 0x0041, // MUST be a BLOB. - VT_STREAM = 0x0042, // MUST be an IndirectPropertyName. The storage representing the (non-simple) property set MUST have a stream element with this name. - VT_STORAGE = 0x0043, // MUST be an IndirectPropertyName. The storage representing the (non-simple) property set MUST have a storage element with this name. - VT_STREAMED_OBJECT = 0x0044, // MUST be an IndirectPropertyName. The storage representing the (non-simple) property set MUST have a stream element with this name. - VT_STORED_OBJECT = 0x0045, // MUST be an IndirectPropertyName. The storage representing the (non-simple) property set MUST have a storage element with this name. - VT_BLOB_OBJECT = 0x0046, //MUST be a BLOB. - VT_CF = 0x0047, //MUST be a ClipboardData. - VT_CLSID = 0x0048, //MUST be a GUID (Packet Version) - VT_VERSIONED_STREAM = 0x0049, // MUST be a versioned Stream, NOT allowed in simple property - VT_VECTOR_HEADER = 0x1000, //--- NOT NORMATIVE - VT_ARRAY_HEADER = 0x2000, //--- NOT NORMATIVE - VT_VARIANT_VECTOR = 0x000C, //--- NOT NORMATIVE - VT_VARIANT_ARRAY = 0x200C, //--- NOT NORMATIVE - } -} diff --git a/sources/OpenMcdf.Extensions/OLEProperties/VTVectorHeader.cs b/sources/OpenMcdf.Extensions/OLEProperties/VTVectorHeader.cs deleted file mode 100644 index cd2bfe62..00000000 --- a/sources/OpenMcdf.Extensions/OLEProperties/VTVectorHeader.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace OpenMcdf.Extensions.OLEProperties -{ -} diff --git a/sources/OpenMcdf.Extensions/OlePropertiesExtensions.cs b/sources/OpenMcdf.Extensions/OlePropertiesExtensions.cs deleted file mode 100644 index 585df834..00000000 --- a/sources/OpenMcdf.Extensions/OlePropertiesExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using OpenMcdf.Extensions.OLEProperties; - -namespace OpenMcdf.Extensions -{ - public static class OlePropertiesExtensions - { - /// - /// Returns Stream as an OLE Properties container (see [MS-OLEPS] document for specifications). - /// Stream is usually a SummaryInfo or a DocumentSummaryInfo compound stream. - /// Application specific properties stream are also supported. - /// Properties can be added, removed and modified. - /// - /// Compound Stream to be read as OLE properties container - /// Ole properties container to enumerate and manipulate properties - /// - /// This method currently has following limitations: - /// - only SIMPLE Property Sets are supported; - /// - Scalar, Vector and Variant Vector are supported; - /// - Array properties are NOT supported; - /// - Property Stream is re-created on save; - /// - public static OLEPropertiesContainer AsOLEPropertiesContainer(this CFStream cfStream) - { - return new OLEPropertiesContainer(cfStream); - } - } -} diff --git a/sources/OpenMcdf.Extensions/OpenMcdf.Extensions.csproj b/sources/OpenMcdf.Extensions/OpenMcdf.Extensions.csproj deleted file mode 100644 index 6bd44f1b..00000000 --- a/sources/OpenMcdf.Extensions/OpenMcdf.Extensions.csproj +++ /dev/null @@ -1,94 +0,0 @@ - - - netstandard2.0;net40 - Debug;Release - true - - true - - true - true - snupkg - true - OpenMcdf.snk - false - false - false - false - false - false - false - - - - - - true - portable - false - ..\..\bin\Debug\OpenMcdf.Extensions\ - DEBUG;TRACE - prompt - 4 - ..\..\bin\Debug\OpenMcdf.Extensions\OpenMcdf.Extensions.XML - - - portable - true - ..\..\bin\Release\OpenMcdf.Extensions\ - TRACE - prompt - 4 - ..\..\bin\Release\OpenMcdf.Extensions\OpenMcdf.Extensions.XML - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {DB748C1D-D71C-442B-832D-2E33BE816CBB} - Library - Properties - OpenMcdf.Extensions - OpenMcdf.Extensions - 512 - true - OpenMcdf.snk - - - true - true - 2.3.1.0 - MPL-2.0 - https://github.com/ironfede/openmcdf - - https://github.com/ironfede/openmcdf - OpenMcdf.Extensions - Extensions for OLE Properties and IO.Stream interface for OpenMcdf (Beta) - Federico Blaseotto, 2024 - icon.png - README.md - - - true - - - - - - - True - \ - - - True - \ - - - - - - 6.0.0 - - - \ No newline at end of file diff --git a/sources/OpenMcdf.Extensions/OpenMcdf.Extensions.csproj.user b/sources/OpenMcdf.Extensions/OpenMcdf.Extensions.csproj.user deleted file mode 100644 index 1efe784d..00000000 --- a/sources/OpenMcdf.Extensions/OpenMcdf.Extensions.csproj.user +++ /dev/null @@ -1,6 +0,0 @@ - - - - ProjectFiles - - \ No newline at end of file diff --git a/sources/OpenMcdf.Extensions/OpenMcdf.snk b/sources/OpenMcdf.Extensions/OpenMcdf.snk deleted file mode 100644 index b2990e73c74b1b002bd693d3d8008085b96f461f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097nwG6x-K;LO^j4j6cs`(%400Z5@yPQnZ7{$0%=kHi|-&%jj zIZo$C(^orbVlD(&H`PYI`!B&VSLuY?Q>Q)El2oao)5)5QriW~iFixpxnXCq=jeExP zwIDQlYjkbW{FAWhZ)4zsWDM5B0Ai$5KWtA3WU3fix^?x&19(Tr`ai`}{|vA3d;= zvaMECN!3mgvGE5t)=hH7N)+WzrP#-#HOb%u(+-Je%Npw`Uv^Y}ekuG=&$uUWso2C? z_`m=&4^8V#^8!SoI$f{Y;uM2Qdgc1+0wDpGERes_*oK=gF2S}Uz!p3<(}iN-sPo!x z - /// A wrapper class to present a as a . - /// - public class StreamDecorator : Stream - { - private readonly CFStream cfStream; - private long position; - - /// - /// Create a new for the specified . - /// - /// The being wrapped. - public StreamDecorator(CFStream cfstream) - { - cfStream = cfstream; - } - - /// - public override bool CanRead => true; - - /// - public override bool CanSeek => true; - - /// - public override bool CanWrite => true; - - /// - public override void Flush() - { - // nothing to do; - } - - /// - public override long Length => cfStream.Size; - - /// - public override long Position - { - get => position; - set => Seek(value, SeekOrigin.Begin); - } - - /// - public override int Read(byte[] buffer, int offset, int count) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer)); - - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset), "Offset must be a non-negative number"); - - if ((uint)count > buffer.Length - offset) - throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection"); - - if (position >= cfStream.Size) - return 0; - - int maxReadableLength = (int)Math.Min(int.MaxValue, Length - Position); - count = Math.Max(0, Math.Min(maxReadableLength, count)); - if (count == 0) - return 0; - - count = cfStream.Read(buffer, position, offset, count); - position += count; - return count; - } - - /// - public override long Seek(long offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - if (offset < 0) - throw new IOException("Seek before origin"); - position = offset; - break; - - case SeekOrigin.Current: - if (position + offset < 0) - throw new IOException("Seek before origin"); - position += offset; - break; - - case SeekOrigin.End: - if (Length - offset < 0) - throw new IOException("Seek before origin"); - position = Length - offset; - break; - - default: - throw new ArgumentException("Invalid seek origin", nameof(origin)); - } - - if (position > Length) - SetLength(position); - - return position; - } - - /// - public override void SetLength(long value) - { - cfStream.Resize(value); - } - - /// - public override void Write(byte[] buffer, int offset, int count) - { - cfStream.Write(buffer, position, offset, count); - position += count; - } - - /// - public override void Close() - { - // Do nothing - } - } -} diff --git a/sources/OpenMcdf/BinaryPrimitives.cs b/sources/OpenMcdf/BinaryPrimitives.cs deleted file mode 100644 index e73b4803..00000000 --- a/sources/OpenMcdf/BinaryPrimitives.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; - -namespace OpenMcdf -{ - /// - /// net45 compatible version of BinaryPrimitives - /// - internal static class BinaryPrimitives - { - public static int ReverseEndianness(int value) - { - return (int)ReverseEndianness((uint)value); - } - - public static uint ReverseEndianness(uint value) - { - uint num = value & 0xFF00FFu; - uint num2 = value & 0xFF00FF00u; - return ((num >> 8) | (num << 24)) + ((num2 << 8) | (num2 >> 24)); - } - - public static void WriteInt32LittleEndian(byte[] destination, int offset, int value) - { - // TODO: Use System.Memory for BinaryPrimitives in v3 - if (destination is null) - throw new ArgumentNullException(nameof(destination)); - if (offset + sizeof(int) > destination.Length) - throw new ArgumentOutOfRangeException(nameof(offset)); - - if (!BitConverter.IsLittleEndian) - value = ReverseEndianness(value); - - destination[offset + 0] = (byte)value; - destination[offset + 1] = (byte)(value >> 8); - destination[offset + 2] = (byte)(value >> 16); - destination[offset + 3] = (byte)(value >> 24); - } - } -} diff --git a/sources/OpenMcdf/CFException.cs b/sources/OpenMcdf/CFException.cs deleted file mode 100644 index 3624273b..00000000 --- a/sources/OpenMcdf/CFException.cs +++ /dev/null @@ -1,214 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The Original Code is OpenMCDF - Compound Document Format library. - * - * The Initial Developer of the Original Code is Federico Blaseotto.*/ - -using System; -using System.Runtime.Serialization; - -namespace OpenMcdf -{ - /// - /// OpenMCDF base exception. - /// - [Serializable] - public class CFException : Exception - { - public CFException() - : base() - { - } - - protected CFException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - public CFException(string message) - : base(message, null) - { - } - - public CFException(string message, Exception innerException) - : base(message, innerException) - { - } - } - - /// - /// Raised when a data setter/getter method is invoked - /// on a stream or storage object after the disposal of the owner - /// compound file object. - /// - [Serializable] - public class CFDisposedException : CFException - { - public CFDisposedException() - : base() - { - } - - protected CFDisposedException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - public CFDisposedException(string message) - : base(message, null) - { - } - - public CFDisposedException(string message, Exception innerException) - : base(message, innerException) - { - } - } - - /// - /// Raised when opening a file with invalid header - /// or not supported COM/OLE Structured storage version. - /// - [Serializable] - public class CFFileFormatException : CFException - { - public CFFileFormatException() - : base() - { - } - - protected CFFileFormatException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - public CFFileFormatException(string message) - : base(message, null) - { - } - - public CFFileFormatException(string message, Exception innerException) - : base(message, innerException) - { - } - } - - /// - /// Raised when a named stream or a storage object - /// are not found in a parent storage. - /// - [Serializable] - public class CFItemNotFound : CFException - { - protected CFItemNotFound(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - public CFItemNotFound() - : base("Entry not found") - { - } - - public CFItemNotFound(string message) - : base(message, null) - { - } - - public CFItemNotFound(string message, Exception innerException) - : base(message, innerException) - { - } - } - - /// - /// Raised when a method call is invalid for the current object state - /// - [Serializable] - public class CFInvalidOperation : CFException - { - public CFInvalidOperation() - : base() - { - } - - protected CFInvalidOperation(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - public CFInvalidOperation(string message) - : base(message, null) - { - } - - public CFInvalidOperation(string message, Exception innerException) - : base(message, innerException) - { - } - } - - /// - /// Raised when trying to add a duplicated CFItem - /// - /// - /// Items are compared by name as indicated by specs. - /// Two items with the same name CANNOT be added within - /// the same storage or sub-storage. - /// - [Serializable] - public class CFDuplicatedItemException : CFException - { - public CFDuplicatedItemException() - : base() - { - } - - protected CFDuplicatedItemException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - public CFDuplicatedItemException(string message) - : base(message, null) - { - } - - public CFDuplicatedItemException(string message, Exception innerException) - : base(message, innerException) - { - } - } - - /// - /// Raised when trying to load a Compound File with invalid, corrupted or mismatched fields (4.1 - specifications) - /// - /// - /// This exception is NOT raised when Compound file has been opened with NO_VALIDATION_EXCEPTION option. - /// - [Serializable] - public class CFCorruptedFileException : CFException - { - public CFCorruptedFileException() - : base() - { - } - - protected CFCorruptedFileException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - public CFCorruptedFileException(string message) - : base(message, null) - { - } - - public CFCorruptedFileException(string message, Exception innerException) - : base(message, innerException) - { - } - } -} diff --git a/sources/OpenMcdf/CFItem.cs b/sources/OpenMcdf/CFItem.cs deleted file mode 100644 index 85203a93..00000000 --- a/sources/OpenMcdf/CFItem.cs +++ /dev/null @@ -1,223 +0,0 @@ - -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The Original Code is OpenMCDF - Compound Document Format library. - * - * The Initial Developer of the Original Code is Federico Blaseotto.*/ - -using System; - -namespace OpenMcdf -{ - /// - /// Abstract base class for Structured Storage entities. - /// - /// - /// - /// - /// const String STORAGE_NAME = "report.xls"; - /// CompoundFile cf = new CompoundFile(STORAGE_NAME); - /// - /// FileStream output = new FileStream("LogEntries.txt", FileMode.Create); - /// TextWriter tw = new StreamWriter(output); - /// - /// // CFItem represents both storage and stream items - /// VisitedEntryAction va = delegate(CFItem item) - /// { - /// tw.WriteLine(item.Name); - /// }; - /// - /// cf.RootStorage.VisitEntries(va, true); - /// - /// tw.Close(); - /// - /// - /// - public abstract class CFItem : IComparable - { - protected CompoundFile CompoundFile { get; } - - protected void CheckDisposed() - { - if (CompoundFile.IsClosed) - throw new CFDisposedException("Owner Compound file has been closed and owned items have been invalidated"); - } - - protected CFItem() - { - } - - protected CFItem(CompoundFile compoundFile) - { - CompoundFile = compoundFile; - } - - #region IDirectoryEntry Members - - - internal IDirectoryEntry DirEntry { get; set; } - - internal int CompareTo(CFItem other) - { - return DirEntry.CompareTo(other.DirEntry); - } - - #endregion - - #region IComparable Members - - public int CompareTo(object obj) - { - return DirEntry.CompareTo(((CFItem)obj).DirEntry); - } - - #endregion - - public static bool operator ==(CFItem leftItem, CFItem rightItem) - { - // If both are null, or both are same instance, return true. - if (ReferenceEquals(leftItem, rightItem)) - { - return true; - } - - // If one is null, but not both, return false. - if ((leftItem is null) || (rightItem is null)) - { - return false; - } - - // Return true if the fields match: - return leftItem.CompareTo(rightItem) == 0; - } - - public static bool operator !=(CFItem leftItem, CFItem rightItem) - { - return !(leftItem == rightItem); - } - - public override bool Equals(object obj) - { - return CompareTo(obj) == 0; - } - - public override int GetHashCode() - { - return DirEntry.GetEntryName().GetHashCode(); - } - - /// - /// Get entity name - /// - public string Name - { - get - { - string n = DirEntry.GetEntryName(); - if (n != null && n.Length > 0) - { - return n.TrimEnd('\0'); - } - else - return string.Empty; - } - } - - /// - /// Size in bytes of the item. It has a valid value - /// only if entity is a stream, otherwise it is set to zero. - /// - public long Size => DirEntry.Size; - - /// - /// Return true if item is Storage - /// - /// - /// This check doesn't use reflection or runtime type information - /// and doesn't suffer related performance penalties. - /// - public bool IsStorage => DirEntry.StgType == StgType.StgStorage; - - /// - /// Return true if item is a Stream - /// - /// - /// This check doesn't use reflection or runtime type information - /// and doesn't suffer related performance penalties. - /// - public bool IsStream => DirEntry.StgType == StgType.StgStream; - - /// - /// Return true if item is the Root Storage - /// - /// - /// This check doesn't use reflection or runtime type information - /// and doesn't suffer related performance penalties. - /// - public bool IsRoot => DirEntry.StgType == StgType.StgRoot; - - /// - /// Get/Set the Creation Date of the current item - /// - public DateTime CreationDate - { - get => DateTime.FromFileTimeUtc(BitConverter.ToInt64(DirEntry.CreationDate, 0)); - - set - { - if (DirEntry.StgType is not StgType.StgStream and not StgType.StgRoot) - DirEntry.CreationDate = BitConverter.GetBytes(value.ToFileTimeUtc()); - else - throw new CFException("Creation Date can only be set on storage entries"); - } - } - - /// - /// Get/Set the Modify Date of the current item - /// - public DateTime ModifyDate - { - get => DateTime.FromFileTimeUtc(BitConverter.ToInt64(DirEntry.ModifyDate, 0)); - - set - { - if (DirEntry.StgType is not StgType.StgStream and not StgType.StgRoot) - DirEntry.ModifyDate = BitConverter.GetBytes(value.ToFileTimeUtc()); - else - throw new CFException("Modify Date can only be set on storage entries"); - } - } - - /// - /// Get/Set Object class Guid for Root and Storage entries. - /// - public Guid CLSID - { - get => DirEntry.StorageCLSID; - set - { - if (DirEntry.StgType != StgType.StgStream) - { - DirEntry.StorageCLSID = value; - } - else - throw new CFException("Object class GUID can only be set on Root and Storage entries"); - } - } - - int IComparable.CompareTo(CFItem other) - { - return DirEntry.CompareTo(other.DirEntry); - } - - public override string ToString() - { - if (DirEntry != null) - return "[" + DirEntry.LeftSibling + "," + DirEntry.SID + "," + DirEntry.RightSibling + "]" + " " + DirEntry.GetEntryName(); - else - return string.Empty; - } - } -} diff --git a/sources/OpenMcdf/CFStorage.cs b/sources/OpenMcdf/CFStorage.cs deleted file mode 100644 index 10db29c2..00000000 --- a/sources/OpenMcdf/CFStorage.cs +++ /dev/null @@ -1,547 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The Original Code is OpenMCDF - Compound Document Format library. - * - * The Initial Developer of the Original Code is Federico Blaseotto.*/ - -using RedBlackTree; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace OpenMcdf -{ - /// - /// Action to apply to visited items in the OLE structured storage - /// - /// Currently visited item - /// - /// - /// - /// //We assume that xls file should be a valid OLE compound file - /// const String STORAGE_NAME = "report.xls"; - /// CompoundFile cf = new CompoundFile(STORAGE_NAME); - /// - /// FileStream output = new FileStream("LogEntries.txt", FileMode.Create); - /// TextWriter tw = new StreamWriter(output); - /// - /// VisitedEntryAction va = delegate(CFItem item) - /// { - /// tw.WriteLine(item.Name); - /// }; - /// - /// cf.RootStorage.VisitEntries(va, true); - /// - /// tw.Close(); - /// - /// - /// - public delegate void VisitedEntryAction(CFItem item); - - /// - /// Storage entity that acts like a logic container for streams - /// or substorages in a compound file. - /// - public class CFStorage : CFItem - { - private RBTree children; - - internal RBTree Children - { - get - { - // Lazy loading of children tree. - children ??= LoadChildren(DirEntry); - return children; - } - } - - /// - /// Create a CFStorage using an existing directory (previously loaded). - /// - /// The Storage Owner - CompoundFile - /// An existing Directory Entry - internal CFStorage(CompoundFile compFile, IDirectoryEntry dirEntry) - : base(compFile) - { - if (dirEntry == null || dirEntry.SID < 0) - throw new CFException("Attempting to create a CFStorage using an uninitialized directory"); - - DirEntry = dirEntry; - } - - private RBTree LoadChildren(IDirectoryEntry directoryEntry) - { - RBTree childrenTree = CompoundFile.GetChildrenTree(directoryEntry); - DirEntry.Child = childrenTree.Root == null ? DirectoryEntry.NOSTREAM : ((IDirectoryEntry)childrenTree.Root).SID; - return childrenTree; - } - - /// - /// Create a new child stream inside the current storage - /// - /// The new stream name - /// The new stream reference - /// Raised when adding an item with the same name of an existing one - /// Raised when adding a stream to a closed compound file - /// Raised when adding a stream with null or empty name - /// - /// - /// - /// String filename = "A_NEW_COMPOUND_FILE_YOU_CAN_WRITE_TO.cfs"; - /// - /// CompoundFile cf = new CompoundFile(); - /// - /// CFStorage st = cf.RootStorage.AddStorage("MyStorage"); - /// CFStream sm = st.AddStream("MyStream"); - /// byte[] b = Helpers.GetBuffer(220, 0x0A); - /// sm.SetData(b); - /// - /// cf.Save(filename); - /// - /// - /// - public CFStream AddStream(string streamName) - { - CheckDisposed(); - - if (string.IsNullOrEmpty(streamName)) - throw new CFException("Stream name cannot be null or empty"); - - IDirectoryEntry dirEntry = DirectoryEntry.TryNew(streamName, StgType.StgStream, CompoundFile.Directories); - - // Add new Stream directory entry - //cfo = new CFStream(this.CompoundFile, streamName); - - try - { - // Add object to Siblings tree - Children.Insert(dirEntry); - - //... and set the root of the tree as new child of the current item directory entry - DirEntry.Child = (Children.Root as IDirectoryEntry).SID; - } - catch (RBTreeException) - { - dirEntry.Reset(); - - throw new CFDuplicatedItemException("An entry with name '" + streamName + "' is already present in storage '" + Name + "' "); - } - - return new CFStream(CompoundFile, dirEntry); - } - - bool Contains(string name, StgType type, out IDirectoryEntry directoryEntry) - { - IDirectoryEntry tmp = DirectoryEntry.Mock(name, type); - if (!Children.TryLookup(tmp, out IRBNode node) || node is not IDirectoryEntry de || de.StgType != type) - { - directoryEntry = null; - return false; - } - - directoryEntry = de; - return true; - } - - public bool ContainsStream(string streamName) => Contains(streamName, StgType.StgStream, out _); - - public bool ContainsStorage(string storageName) => Contains(storageName, StgType.StgStorage, out _); - - /// - /// Get a named stream contained in the current storage if existing. - /// - /// Name of the stream to look for - /// A stream reference if existing - /// Raised if trying to delete item from a closed compound file - /// Raised if item to delete is not found - /// - /// - /// String filename = "report.xls"; - /// - /// CompoundFile cf = new CompoundFile(filename); - /// CFStream foundStream = cf.RootStorage.GetStream("Workbook"); - /// - /// byte[] temp = foundStream.GetData(); - /// - /// Assert.IsNotNull(temp); - /// - /// cf.Close(); - /// - /// - public CFStream GetStream(string streamName) - { - CheckDisposed(); - - if (!Contains(streamName, StgType.StgStream, out IDirectoryEntry outDe)) - throw new CFItemNotFound($"Cannot find item [{streamName}] within the current storage"); - - return new CFStream(CompoundFile, outDe); - } - - /// - /// Get a named stream contained in the current storage if existing. - /// - /// Name of the stream to look for - /// Found if any - /// true if stream found, else false - /// - /// - /// String filename = "report.xls"; - /// - /// CompoundFile cf = new CompoundFile(filename); - /// bool b = cf.RootStorage.TryGetStream("Workbook",out CFStream foundStream); - /// - /// byte[] temp = foundStream.GetData(); - /// - /// Assert.IsNotNull(temp); - /// Assert.IsTrue(b); - /// - /// cf.Close(); - /// - /// - public bool TryGetStream(string streamName, out CFStream cfStream) - { - if (CompoundFile.IsClosed || !Contains(streamName, StgType.StgStream, out IDirectoryEntry directoryEntry)) - { - cfStream = null; - return false; - } - - cfStream = new CFStream(CompoundFile, directoryEntry); - return true; - } - - /// - /// Get a named stream contained in the current storage if existing. - /// - /// Name of the stream to look for - /// A stream reference if found, else null - /// Raised if trying to delete item from a closed compound file - /// - /// - /// String filename = "report.xls"; - /// - /// CompoundFile cf = new CompoundFile(filename); - /// CFStream foundStream = cf.RootStorage.TryGetStream("Workbook"); - /// - /// byte[] temp = foundStream.GetData(); - /// - /// Assert.IsNotNull(temp); - /// - /// cf.Close(); - /// - /// - [Obsolete("Please use TryGetStream(string, out cfStream) instead.")] - public CFStream TryGetStream(string streamName) - { - TryGetStream(streamName, out CFStream cfStream); - return cfStream; - } - - /// - /// Get a named storage contained in the current one if existing. - /// - /// Name of the storage to look for - /// A storage reference if existing. - /// Raised if trying to delete item from a closed compound file - /// Raised if item to delete is not found - /// - /// - /// - /// String FILENAME = "MultipleStorage2.cfs"; - /// CompoundFile cf = new CompoundFile(FILENAME, UpdateMode.ReadOnly, false, false); - /// - /// CFStorage st = cf.RootStorage.GetStorage("MyStorage"); - /// - /// Assert.IsNotNull(st); - /// cf.Close(); - /// - /// - public CFStorage GetStorage(string storageName) - { - CheckDisposed(); - - if (!Contains(storageName, StgType.StgStorage, out IDirectoryEntry outDe)) - throw new CFItemNotFound($"Cannot find item [{storageName}] within the current storage"); - - return new CFStorage(CompoundFile, outDe); - } - - /// - /// Get a named storage contained in the current one if existing. - /// - /// Name of the storage to look for - /// A storage reference if found else null - /// Raised if trying to delete item from a closed compound file - /// - /// - /// - /// String FILENAME = "MultipleStorage2.cfs"; - /// CompoundFile cf = new CompoundFile(FILENAME, UpdateMode.ReadOnly, false, false); - /// - /// CFStorage st = cf.RootStorage.TryGetStorage("MyStorage"); - /// - /// Assert.IsNotNull(st); - /// cf.Close(); - /// - /// - [Obsolete("Please use TryGetStorage(string, out cfStorage) instead.")] - public CFStorage TryGetStorage(string storageName) - { - TryGetStorage(storageName, out CFStorage cfStorage); - return cfStorage; - } - - /// - /// Get a named storage contained in the current one if existing. - /// - /// Name of the storage to look for - /// A storage reference if found else null - /// true if storage found, else false - /// - /// - /// - /// String FILENAME = "MultipleStorage2.cfs"; - /// CompoundFile cf = new CompoundFile(FILENAME, UpdateMode.ReadOnly, false, false); - /// - /// bool b = cf.RootStorage.TryGetStorage("MyStorage",out CFStorage st); - /// - /// Assert.IsNotNull(st); - /// Assert.IsTrue(b); - /// - /// cf.Close(); - /// - /// - public bool TryGetStorage(string storageName, out CFStorage cfStorage) - { - if (CompoundFile.IsClosed || !Contains(storageName, StgType.StgStorage, out IDirectoryEntry directoryEntry)) - { - cfStorage = null; - return false; - } - - cfStorage = new CFStorage(CompoundFile, directoryEntry); - return true; - } - - /// - /// Create new child storage directory inside the current storage. - /// - /// The new storage name - /// Reference to the new storage - /// Raised when adding an item with the same name of an existing one - /// Raised when adding a storage to a closed compound file - /// Raised when adding a storage with null or empty name - /// - /// - /// - /// String filename = "A_NEW_COMPOUND_FILE_YOU_CAN_WRITE_TO.cfs"; - /// - /// CompoundFile cf = new CompoundFile(); - /// - /// CFStorage st = cf.RootStorage.AddStorage("MyStorage"); - /// CFStream sm = st.AddStream("MyStream"); - /// byte[] b = Helpers.GetBuffer(220, 0x0A); - /// sm.SetData(b); - /// - /// cf.Save(filename); - /// - /// - /// - public CFStorage AddStorage(string storageName) - { - CheckDisposed(); - - if (string.IsNullOrEmpty(storageName)) - throw new CFException("Stream name cannot be null or empty"); - - // Add new Storage directory entry - IDirectoryEntry cfo - = DirectoryEntry.New(storageName, StgType.StgStorage, CompoundFile.Directories); - - //this.CompoundFile.InsertNewDirectoryEntry(cfo); - - try - { - // Add object to Siblings tree - Children.Insert(cfo); - } - catch (RBTreeDuplicatedItemException) - { - cfo.Reset(); - throw new CFDuplicatedItemException("An entry with name '" + storageName + "' is already present in storage '" + Name + "' "); - } - - IDirectoryEntry childrenRoot = Children.Root as IDirectoryEntry; - DirEntry.Child = childrenRoot.SID; - - return new CFStorage(CompoundFile, cfo); - } - - /// - /// Visit all entities contained in the storage applying a user provided action - /// - /// Raised when visiting items of a closed compound file - /// User action to apply to visited entities - /// Visiting recursion level. True means substorages are visited recursively, false indicates that only the direct children of this storage are visited - /// - /// - /// const String STORAGE_NAME = "report.xls"; - /// CompoundFile cf = new CompoundFile(STORAGE_NAME); - /// - /// FileStream output = new FileStream("LogEntries.txt", FileMode.Create); - /// TextWriter tw = new StreamWriter(output); - /// - /// VisitedEntryAction va = delegate(CFItem item) - /// { - /// tw.WriteLine(item.Name); - /// }; - /// - /// cf.RootStorage.VisitEntries(va, true); - /// - /// tw.Close(); - /// - /// - public void VisitEntries(Action action, bool recursive) - { - CheckDisposed(); - - if (action is null) - return; // TODO: Reorder and throw ArgumentNullException in v3 - - Stack stack = new(); - stack.Push(this); - - while (stack.Count > 0) - { - CFItem current = stack.Pop(); - if (current is CFStorage storage) - { - foreach (IDirectoryEntry de in storage.Children.Cast()) - { - CFItem item = de.StgType == StgType.StgStream ? new CFStream(CompoundFile, de) : new CFStorage(CompoundFile, de); - action(item); - if (recursive) - stack.Push(item); - } - } - } - } - - /// - /// Remove an entry from the current storage and compound file. - /// - /// The name of the entry in the current storage to delete - /// - /// - /// cf = new CompoundFile("A_FILE_YOU_CAN_CHANGE.cfs", UpdateMode.Update, true, false); - /// cf.RootStorage.Delete("AStream"); // AStream item is assumed to exist. - /// cf.Commit(true); - /// cf.Close(); - /// - /// - /// Raised if trying to delete item from a closed compound file - /// Raised if item to delete is not found - /// Raised if trying to delete root storage - public void Delete(string entryName) => DeleteCore(entryName, true); - - public void TryDelete(string entryName) => DeleteCore(entryName, false); - - bool DeleteCore(string entryName, bool throwOnError) - { - if (CompoundFile.IsClosed) - return throwOnError ? throw new CFDisposedException("Owner Compound file has been closed and owned items have been invalidated") : false; - - // Find entry to delete - IDirectoryEntry tmp = DirectoryEntry.Mock(entryName, StgType.StgInvalid); - Children.TryLookup(tmp, out IRBNode foundObj); - - if (foundObj == null) - return throwOnError ? throw new CFItemNotFound($"Entry named [{entryName}] was not found") : false; - - IDirectoryEntry directoryEntry = (IDirectoryEntry)foundObj; - DeleteCore(directoryEntry); - return true; - } - - void DeleteCore(IDirectoryEntry directoryEntry) - { - IRBNode altDel; - switch (directoryEntry.StgType) - { - case StgType.StgRoot: - throw new CFException("Root storage cannot be removed"); - - case StgType.StgStorage: - - CFStorage temp = new CFStorage(CompoundFile, directoryEntry); - - // This is a storage. we have to remove children items first - foreach (IDirectoryEntry de in temp.Children.Cast()) - { - temp.DeleteCore(de); - } - - // ...then we Remove storage item from children tree... - Children.Delete(directoryEntry, out altDel); - - // ...after which we need to rethread the root of siblings tree... - DirEntry.Child = Children.Root == null ? DirectoryEntry.NOSTREAM : ((IDirectoryEntry)Children.Root).SID; - - // ...and remove directory (storage) entry - - if (altDel != null) - { - directoryEntry = (IDirectoryEntry)altDel; - } - - directoryEntry.Reset(); - - break; - - case StgType.StgStream: - - // Free directory associated data stream. - CompoundFile.FreeAssociatedData(directoryEntry.SID); - - // Remove item from children tree - Children.Delete(directoryEntry, out altDel); - - // Rethread the root of siblings tree... - DirEntry.Child = Children.Root == null ? DirectoryEntry.NOSTREAM : ((IDirectoryEntry)Children.Root).SID; - - // Delete operation could possibly have cloned a directory, changing its SID. - // Invalidate the ACTUALLY deleted directory. - if (altDel != null) - { - directoryEntry = (IDirectoryEntry)altDel; - } - - directoryEntry.Reset(); - - break; - } - } - - /// - /// Rename a Stream or Storage item in the current storage - /// - /// The item old name to lookup - /// The new name to assign - public void RenameItem(string oldItemName, string newItemName) - { - IDirectoryEntry template = DirectoryEntry.Mock(oldItemName, StgType.StgInvalid); - if (Children.TryLookup(template, out IRBNode item)) - { - ((DirectoryEntry)item).SetEntryName(newItemName); - } - else throw new CFItemNotFound("Item " + oldItemName + " not found in Storage"); - - children = null; - children = LoadChildren(DirEntry); // Rethread - } - } -} diff --git a/sources/OpenMcdf/CFStream.cs b/sources/OpenMcdf/CFStream.cs deleted file mode 100644 index abf00f2f..00000000 --- a/sources/OpenMcdf/CFStream.cs +++ /dev/null @@ -1,234 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The Original Code is OpenMCDF - Compound Document Format library. - * - * The Initial Developer of the Original Code is Federico Blaseotto.*/ - -using System.IO; - -namespace OpenMcdf -{ - /// - /// OLE structured storage stream Object - /// It is contained inside a Storage object in a file-directory - /// relationship and indexed by its name. - /// - public class CFStream : CFItem - { - internal CFStream(CompoundFile compoundFile, IDirectoryEntry dirEntry) - : base(compoundFile) - { - if (dirEntry == null || dirEntry.SID < 0) - throw new CFException("Attempting to add a CFStream using an uninitialized directory"); - - DirEntry = dirEntry; - } - - /// - /// Set the data associated with the stream object. - /// - /// - /// - /// byte[] b = new byte[]{0x0,0x1,0x2,0x3}; - /// CompoundFile cf = new CompoundFile(); - /// CFStream myStream = cf.RootStorage.AddStream("MyStream"); - /// myStream.SetData(b); - /// - /// - /// Data bytes to write to this stream - /// Existing associated data will be lost after method invocation - public void SetData(byte[] data) - { - CheckDisposed(); - - CompoundFile.FreeData(this); - CompoundFile.WriteData(this, data); - } - - /// - /// Write a data buffer to a specific position into current CFStream object - /// - /// Data buffer to Write - /// Position into the stream object to start writing from - /// Current stream will be extended to receive data buffer over - /// its current size - public void Write(byte[] data, long position) - { - Write(data, position, 0, data.Length); - } - - /// - /// Write count bytes of a data buffer to a specific position into - /// the current CFStream object starting from the specified position. - /// - /// Data buffer to copy bytes from - /// Position into the stream object to start writing from - /// The zero-based byte offset in buffer at which to - /// begin copying bytes to the current CFStream. - /// The number of bytes to be written to the current CFStream - /// Current stream will be extended to receive data buffer over - /// its current size. - internal void Write(byte[] data, long position, int offset, int count) - { - CheckDisposed(); - CompoundFile.WriteData(this, data, position, offset, count); - } - - /// - /// Append the provided data to stream data. - /// - /// - /// - /// byte[] b = new byte[]{0x0,0x1,0x2,0x3}; - /// byte[] b2 = new byte[]{0x4,0x5,0x6,0x7}; - /// CompoundFile cf = new CompoundFile(); - /// CFStream myStream = cf.RootStorage.AddStream("MyStream"); - /// myStream.SetData(b); // here we could also have invoked .AppendData - /// myStream.AppendData(b2); - /// cf.Save("MyLargeStreamsFile.cfs); - /// cf.Close(); - /// - /// - /// Data bytes to append to this stream - /// - /// This method allows user to create stream with more than 2GB of data, - /// appending data to the end of existing ones. - /// Large streams (>2GB) are only supported by CFS version 4. - /// Append data can also be invoked on streams with no data in order - /// to simplify its use inside loops. - /// - public void Append(byte[] data) - { - CheckDisposed(); - if (Size > 0) - { - CompoundFile.AppendData(this, data); - } - else - { - CompoundFile.WriteData(this, data); - } - } - - /// - /// Get all the data associated with the stream object. - /// - /// - /// - /// CompoundFile cf2 = new CompoundFile("AFileName.cfs"); - /// CFStream st = cf2.RootStorage.GetStream("MyStream"); - /// byte[] buffer = st.ReadAll(); - /// - /// - /// Array of byte containing stream data - /// - /// Raised when the owner compound file has been closed. - /// - public byte[] GetData() - { - CheckDisposed(); - - return CompoundFile.GetData(DirEntry); - } - - /// - /// Read bytes associated with the stream object, starting from - /// the provided . Method returns the effective count of bytes - /// read. - /// - /// Array of bytes that will contain stream data - /// The zero-based byte position in the stream at which to begin reading - /// the data from. - /// The maximum number of bytes to be read from the current stream. - /// The count of bytes effectively read - /// Method may read a number of bytes lesser then the requested one. - /// - /// CompoundFile cf = null; - /// byte[] b = Helpers.GetBuffer(1024 * 2, 0xAA); //2MB buffer - /// CFStream item = cf.RootStorage.GetStream("AStream"); - /// - /// cf = new CompoundFile("$AFILENAME.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.Default); - /// item = cf.RootStorage.GetStream("AStream"); - /// - /// byte[] buffer = new byte[2048]; - /// item.Read(buffer, 0, 2048); - /// CollectionAssert.AreEqual(b, buffer)); - /// - /// - /// - /// Raised when the owner compound file has been closed. - /// - public int Read(byte[] buffer, long position, int count) - { - CheckDisposed(); - return CompoundFile.ReadData(DirEntry, position, buffer, 0, count); - } - - /// - /// Read bytes associated with the stream object, starting from - /// a provided . Method returns the effective count of bytes - /// read. - /// - /// Array of bytes that will contain stream data - /// The zero-based byte position in the stream at which to begin reading - /// the data from. - /// The zero-based byte offset in buffer at which to begin storing the data read from the current stream. - /// The maximum number of bytes to be read from the current stream. - /// The count of bytes effectively read - /// Method may read a number of bytes lesser then the requested one. - /// - /// CompoundFile cf = null; - /// byte[] b = Helpers.GetBuffer(1024 * 2, 0xAA); //2MB buffer - /// CFStream item = cf.RootStorage.GetStream("AStream"); - /// - /// cf = new CompoundFile("$AFILENAME.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.Default); - /// item = cf.RootStorage.GetStream("AStream"); - /// - /// byte[] buffer = new byte[2048]; - /// item.Read(buffer, 0, 2048); - /// CollectionAssert.AreEqual(b, buffer); - /// - /// - /// - /// Raised when the owner compound file has been closed. - /// - internal int Read(byte[] buffer, long position, int offset, int count) - { - CheckDisposed(); - return CompoundFile.ReadData(DirEntry, position, buffer, offset, count); - } - - /// - /// Copy data from an existing stream. - /// - /// A stream to read from - /// - /// Input stream will NOT be closed after method invocation. - /// Existing associated data will be deleted. - /// - public void CopyFrom(Stream input) - { - CheckDisposed(); - - if (input.CanSeek) - { - input.Seek(0, SeekOrigin.Begin); - } - - byte[] buffer = new byte[input.Length]; - input.ReadExactly(buffer, 0, buffer.Length); - SetData(buffer); - } - - /// - /// Resize stream padding with zero if enlarging, trimming data if reducing size. - /// - /// New length to assign to this stream - public void Resize(long length) - { - CompoundFile.SetStreamLength(this, length); - } - } -} diff --git a/sources/OpenMcdf/CompoundFile.cs b/sources/OpenMcdf/CompoundFile.cs deleted file mode 100644 index 9d4e2de1..00000000 --- a/sources/OpenMcdf/CompoundFile.cs +++ /dev/null @@ -1,2568 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The Original Code is OpenMCDF - Compound Document Format library. - * - * The Initial Developer of the Original Code is Federico Blaseotto.*/ - -#define FLAT_WRITE // No optimization on the number of write operations - -using RedBlackTree; -using System; -using System.Collections.Generic; -using System.IO; - -namespace OpenMcdf -{ - internal sealed class CFItemComparer : IComparer - { - public int Compare(CFItem x, CFItem y) - { - // X CompareTo Y : X > Y --> 1 ; X < Y --> -1 - return x.DirEntry.CompareTo(y.DirEntry); - - //Compare X < Y --> -1 - } - } - - /// - /// Configuration parameters for the compound files. - /// They can be OR-combined to configure - /// Compound file behavior. - /// All flags are NOT set by Default. - /// - [Flags] - public enum CFSConfiguration - { - /// - /// Sector Recycling turn off, - /// free sectors erasing off, - /// format validation exception raised - /// - Default = 1, - - /// - /// Sector recycling reduces data writing performances - /// but avoids space wasting in scenarios with frequently - /// data manipulation of the same streams. - /// - SectorRecycle = 2, - - /// - /// Free sectors are erased to avoid information leakage - /// - EraseFreeSectors = 4, - - /// - /// No exception is raised when a validation error occurs. - /// This can possibly lead to a security issue but gives - /// a chance to corrupted files to load. - /// - NoValidationException = 8, - - /// - /// If this flag is set true, - /// backing stream is kept open after CompoundFile disposal - /// - LeaveOpen = 16, - } - - /// - /// Binary File Format Version. Sector size is 512 byte for version 3, - /// 4096 for version 4 - /// - public enum CFSVersion : int - { - /// - /// Compound file version 3 - The default and most common version available. Sector size 512 bytes, 2GB max file size. - /// - Ver_3 = 3, - /// - /// Compound file version 4 - Sector size is 4096 bytes. Using this version could bring some compatibility problem with existing applications. - /// - Ver_4 = 4 - } - - /// - /// Update mode of the compound file. - /// Default is ReadOnly. - /// - public enum CFSUpdateMode - { - /// - /// ReadOnly update mode prevents overwriting - /// of the opened file. - /// Data changes are allowed but they have to be - /// persisted on a different file when required - /// using method - /// - ReadOnly, - - /// - /// Update mode allows subsequent data changing operations - /// to be persisted directly on the opened file or stream - /// using the Commit - /// method when required. Warning: this option may cause existing data loss if misused. - /// - Update - } - - /// - /// Standard Microsoft© Compound File implementation. - /// It is also known as OLE/COM structured storage - /// and contains a hierarchy of storage and stream objects providing - /// efficient storage of multiple kinds of documents in a single file. - /// Version 3 and 4 of specifications are supported. - /// - public class CompoundFile : IDisposable - { - /// - /// Get the configuration parameters of the CompoundFile object. - /// - public CFSConfiguration Configuration { get; private set; } - - /// - /// Returns the size of standard sectors switching on CFS version (3 or 4) - /// - /// Standard sector size - internal int SectorSize => 2 << (header.SectorShift - 1); - - /// - /// Number of DIFAT entries in the header - /// - private const int HEADER_DIFAT_ENTRIES_COUNT = 109; - - /// - /// Number of FAT entries in a DIFAT Sector - /// - private readonly int DIFAT_SECTOR_FAT_ENTRIES_COUNT = 127; - - /// - /// Sectors ID entries in a FAT Sector - /// - private readonly int FAT_SECTOR_ENTRIES_COUNT = 128; - - /// - /// Sector ID Size (int) - /// - private const int SIZE_OF_SID = 4; - - /// - /// Flag for sector recycling. - /// - private bool sectorRecycle; - - /// - /// Flag for unallocated sector zeroing out. - /// - private bool eraseFreeSectors; - - public bool ValidationExceptionEnabled { get; private set; } = true; - - private readonly CFSUpdateMode updateMode = CFSUpdateMode.ReadOnly; - - /// - /// Initial capacity of the flushing queue used - /// to optimize commit writing operations - /// - private const int FLUSHING_QUEUE_SIZE = 6000; - - /// - /// Maximum size of the flushing buffer used - /// to optimize commit writing operations - /// - private const int FLUSHING_BUFFER_MAX_SIZE = 1024 * 1024 * 16; - - private SectorCollection sectors = new(); - - /// - /// CompoundFile header - /// - private Header header; - - /// - /// Compound underlying stream. Null when new CF has been created. - /// - internal Stream sourceStream; - - /// - /// Create a blank, version 3 compound file. - /// Sector recycle is turned off to achieve the best reading/writing - /// performance in most common scenarios. - /// - /// - /// - /// - /// byte[] b = new byte[10000]; - /// for (int i = 0; i < 10000; i++) - /// { - /// b[i % 120] = (byte)i; - /// } - /// - /// CompoundFile cf = new CompoundFile(); - /// CFStream myStream = cf.RootStorage.AddStream("MyStream"); - /// - /// Assert.IsNotNull(myStream); - /// myStream.SetData(b); - /// cf.Save("MyCompoundFile.cfs"); - /// cf.Close(); - /// - /// - /// - public CompoundFile() : this(CFSVersion.Ver_3, CFSConfiguration.Default) { } - - void OnSizeLimitReached() - { - Sector rangeLockSector = new Sector(SectorSize, sourceStream); - sectors.Add(rangeLockSector); - - rangeLockSector.Type = SectorType.RangeLockSector; - - _transactionLockAdded = true; - _lockSectorId = rangeLockSector.Id; - } - - /// - /// Create a new, blank, compound file. - /// - /// Use a specific Compound File Version to set 512 or 4096 bytes sectors - /// Set configuration parameters for the new compound file - /// - /// - /// - /// byte[] b = new byte[10000]; - /// for (int i = 0; i < 10000; i++) - /// { - /// b[i % 120] = (byte)i; - /// } - /// - /// CompoundFile cf = new CompoundFile(CFSVersion.Ver_4, CFSConfiguration.Default); - /// CFStream myStream = cf.RootStorage.AddStream("MyStream"); - /// - /// Assert.IsNotNull(myStream); - /// myStream.SetData(b); - /// cf.Save("MyCompoundFile.cfs"); - /// cf.Close(); - /// - /// - /// - public CompoundFile(CFSVersion cfsVersion, CFSConfiguration configFlags) - { - SetConfigurationOptions(configFlags); - - header = new Header((ushort)cfsVersion); - - if (cfsVersion == CFSVersion.Ver_4) - sectors.OnVer3SizeLimitReached += new Ver3SizeLimitReached(OnSizeLimitReached); - - DIFAT_SECTOR_FAT_ENTRIES_COUNT = (SectorSize / 4) - 1; - FAT_SECTOR_ENTRIES_COUNT = SectorSize / 4; - - //Root -- - IDirectoryEntry rootDir = DirectoryEntry.New("Root Entry", StgType.StgRoot, directoryEntries); - rootDir.StgColor = StgColor.Black; - //this.InsertNewDirectoryEntry(rootDir); - - RootStorage = new CFStorage(this, rootDir); - } - - /// - /// Load an existing compound file. - /// - /// Compound file to read from - /// - /// - /// //A xls file should have a Workbook stream - /// String filename = "report.xls"; - /// - /// CompoundFile cf = new CompoundFile(filename); - /// CFStream foundStream = cf.RootStorage.GetStream("Workbook"); - /// - /// byte[] temp = foundStream.GetData(); - /// - /// Assert.IsNotNull(temp); - /// - /// cf.Close(); - /// - /// - /// - /// File will be open in read-only mode: it has to be saved - /// with a different filename. A wrapping implementation has to be provided - /// in order to remove/substitute an existing file. Version will be - /// automatically recognized from the file. Sector recycle is turned off - /// to achieve the best reading/writing performance in most common scenarios. - /// - public CompoundFile(string fileName) : this(fileName, CFSUpdateMode.ReadOnly, CFSConfiguration.Default) { } - - /// - /// Load an existing compound file. - /// - /// Compound file to read from - /// If true, recycle unused sectors - /// Select the update mode of the underlying data file - /// If true, overwrite with zeros unallocated sectors - /// - /// - /// String srcFilename = "data_YOU_CAN_CHANGE.xls"; - /// - /// CompoundFile cf = new CompoundFile(srcFilename, UpdateMode.Update, true, true); - /// - /// Random r = new Random(); - /// - /// byte[] buffer = GetBuffer(r.Next(3, 4095), 0x0A); - /// - /// cf.RootStorage.AddStream("MyStream").SetData(buffer); - /// - /// //This will persist data to the underlying media. - /// cf.Commit(); - /// cf.Close(); - /// - /// - /// - public CompoundFile(string fileName, CFSUpdateMode updateMode, CFSConfiguration configParameters) - { - SetConfigurationOptions(configParameters); - this.updateMode = updateMode; - - LoadFile(fileName); - - DIFAT_SECTOR_FAT_ENTRIES_COUNT = (SectorSize / 4) - 1; - FAT_SECTOR_ENTRIES_COUNT = SectorSize / 4; - } - - /// - /// Load an existing compound file. - /// - /// A stream containing a compound file to read - /// If true, recycle unused sectors - /// Select the update mode of the underlying data file - /// If true, overwrite with zeros unallocated sectors - /// - /// - /// - /// String filename = "reportREAD.xls"; - /// - /// FileStream fs = new FileStream(filename, FileMode.Open); - /// CompoundFile cf = new CompoundFile(fs, UpdateMode.ReadOnly, false, false); - /// CFStream foundStream = cf.RootStorage.GetStream("Workbook"); - /// - /// byte[] temp = foundStream.GetData(); - /// - /// Assert.IsNotNull(temp); - /// - /// cf.Close(); - /// - /// - /// - /// Raised when trying to open a non-seekable stream - /// Raised stream is null - public CompoundFile(Stream stream, CFSUpdateMode updateMode, CFSConfiguration configParameters) - { - SetConfigurationOptions(configParameters); - this.updateMode = updateMode; - - LoadStream(stream); - - DIFAT_SECTOR_FAT_ENTRIES_COUNT = (SectorSize / 4) - 1; - FAT_SECTOR_ENTRIES_COUNT = SectorSize / 4; - } - - /// - /// Load an existing compound file from a stream. - /// - /// Streamed compound file - /// - /// - /// - /// String filename = "reportREAD.xls"; - /// - /// FileStream fs = new FileStream(filename, FileMode.Open); - /// CompoundFile cf = new CompoundFile(fs); - /// CFStream foundStream = cf.RootStorage.GetStream("Workbook"); - /// - /// byte[] temp = foundStream.GetData(); - /// - /// Assert.IsNotNull(temp); - /// - /// cf.Close(); - /// - /// - /// - /// Raised when trying to open a non-seekable stream - /// Raised stream is null - public CompoundFile(Stream stream) : this(stream, CFSUpdateMode.ReadOnly, CFSConfiguration.Default) { } - - /// - /// Commit data changes since the previously commit operation - /// to the underlying supporting stream or file on the disk. - /// - /// - /// This method can be used - /// only if the supporting stream has been opened in - /// Update mode. - /// - public void Commit() - { - Commit(false); - } - -#if !FLAT_WRITE - private byte[] buffer = new byte[FLUSHING_BUFFER_MAX_SIZE]; - private Queue flushingQueue = new Queue(FLUSHING_QUEUE_SIZE); -#endif - - /// - /// Commit data changes since the previously commit operation - /// to the underlying supporting stream or file on the disk. - /// - /// If true, release loaded sectors to limit memory usage but reduces following read operations performance - /// - /// This method can be used only if - /// the supporting stream has been opened in - /// Update mode. - /// - public void Commit(bool releaseMemory) - { - if (IsClosed) - throw new CFDisposedException("Compound File closed: cannot commit data"); - - if (updateMode != CFSUpdateMode.Update) - throw new CFInvalidOperation("Cannot commit data in Read-Only update mode"); - - //try - //{ -#if !FLAT_WRITE - - int sId = -1; - int sCount = 0; - int bufOffset = 0; -#endif - int sSize = SectorSize; - - if (header.MajorVersion != (ushort)CFSVersion.Ver_3) - CheckForLockSector(); - - sourceStream.Seek(0, SeekOrigin.Begin); - sourceStream.Write(new byte[sSize], 0, sSize); - - CommitDirectory(); - - bool gap = true; - - for (int i = 0; i < sectors.Count; i++) - { -#if FLAT_WRITE - - //Note: - //Here sectors should not be loaded dynamically because - //if they are null it means that no change has involved them; - - Sector s = sectors[i]; - - if (s != null && s.DirtyFlag) - { - if (gap) - sourceStream.Seek(sSize + i * (long)sSize, SeekOrigin.Begin); - - sourceStream.Write(s.GetData(), 0, sSize); - sourceStream.Flush(); - s.DirtyFlag = false; - gap = false; - } - else - { - gap = true; - } - - if (s != null && releaseMemory) - { - s.ReleaseData(); - s = null; - sectors[i] = null; - } - -#else - - - Sector s = sectors[i] as Sector; - - - if (s != null && s.DirtyFlag && flushingQueue.Count < (int)(buffer.Length / sSize)) - { - //First of a block of contiguous sectors, mark id, start enqueuing - - if (gap) - { - sId = s.Id; - gap = false; - } - - flushingQueue.Enqueue(s); - - - } - else - { - //Found a gap, stop enqueuing, flush a write operation - - gap = true; - sCount = flushingQueue.Count; - - if (sCount == 0) continue; - - bufOffset = 0; - while (flushingQueue.Count > 0) - { - Sector r = flushingQueue.Dequeue(); - Buffer.BlockCopy(r.GetData(), 0, buffer, bufOffset, sSize); - r.DirtyFlag = false; - - if (releaseMemory) - { - r.ReleaseData(); - } - - bufOffset += sSize; - } - - sourceStream.Seek(((long)sSize + (long)sId * (long)sSize), SeekOrigin.Begin); - sourceStream.Write(buffer, 0, sCount * sSize); - - - - //Console.WriteLine("W - " + (int)(sCount * sSize )); - - } -#endif - } - -#if !FLAT_WRITE - sCount = flushingQueue.Count; - bufOffset = 0; - - while (flushingQueue.Count > 0) - { - Sector r = flushingQueue.Dequeue(); - Buffer.BlockCopy(r.GetData(), 0, buffer, bufOffset, sSize); - r.DirtyFlag = false; - - if (releaseMemory) - { - r.ReleaseData(); - r = null; - } - - bufOffset += sSize; - } - - if (sCount != 0) - { - sourceStream.Seek((long)sSize + (long)sId * (long)sSize, SeekOrigin.Begin); - sourceStream.Write(buffer, 0, sCount * sSize); - //Console.WriteLine("W - " + (int)(sCount * sSize)); - } - -#endif - - // Seek to beginning position and save header (first 512 or 4096 bytes) - sourceStream.Seek(0, SeekOrigin.Begin); - header.Write(sourceStream); - - sourceStream.SetLength((long)(sectors.Count + 1) * sSize); - sourceStream.Flush(); - - if (releaseMemory) - GC.Collect(); - - //} - //catch (Exception ex) - //{ - // throw new CFException("Internal error while committing data", ex); - //} - } - - /// - /// Set configuration parameters - /// - private void SetConfigurationOptions(CFSConfiguration configParameters) - { - Configuration = configParameters; - ValidationExceptionEnabled = !configParameters.HasFlag(CFSConfiguration.NoValidationException); - sectorRecycle = configParameters.HasFlag(CFSConfiguration.SectorRecycle); - eraseFreeSectors = configParameters.HasFlag(CFSConfiguration.EraseFreeSectors); - closeStream = !configParameters.HasFlag(CFSConfiguration.LeaveOpen); - } - - /// - /// Load compound file from an existing stream. - /// - /// Stream to load compound file from - private void Load(Stream stream) - { - try - { - header = new Header(); - directoryEntries = new List(); - - sourceStream = stream; - - header.Read(stream); - - if (!Configuration.HasFlag(CFSConfiguration.NoValidationException)) - { - header.ThrowIfInvalid(); - } - - int n_sector = Ceiling((stream.Length - SectorSize) / (double)SectorSize); - - if (stream.Length > 0x7FFFFF0) - _transactionLockAllocated = true; - - sectors = new SectorCollection(); - //sectors = new ArrayList(); - for (int i = 0; i < n_sector; i++) - { - sectors.Add(null); - } - - LoadDirectories(); - - RootStorage - = new CFStorage(this, directoryEntries[0]); - } - catch (Exception) - { - if (stream != null && closeStream) - stream.Close(); - - throw; - } - } - - private void LoadFile(string fileName) - { - FileAccess access = updateMode == CFSUpdateMode.ReadOnly ? FileAccess.Read : FileAccess.ReadWrite; - FileShare share = updateMode == CFSUpdateMode.ReadOnly ? FileShare.ReadWrite : FileShare.Read; - FileStream fs = new(fileName, FileMode.Open, access, share); - Load(fs); - } - - private void LoadStream(Stream stream) - { - if (stream == null) - throw new CFException("Stream parameter cannot be null"); - - if (!stream.CanSeek) - throw new CFException("Cannot load a non-seekable Stream"); - - stream.Seek(0, SeekOrigin.Begin); - - Load(stream); - } - - /// - /// Return true if this compound file has been - /// loaded from an existing file or stream - /// - public bool HasSourceStream => sourceStream != null; - - private void PersistMiniStreamToStream(List miniSectorChain) - { - List miniStream - = GetSectorChain(RootEntry.StartSect, SectorType.Normal); - - using StreamView miniStreamView - = new StreamView( - miniStream, - SectorSize, - RootStorage.Size, - null, - sourceStream); - - for (int i = 0; i < miniSectorChain.Count; i++) - { - Sector s = miniSectorChain[i]; - - if (s.Id == -1) - throw new CFException("Invalid minisector index"); - - // MiniStream sectors already allocated - miniStreamView.Seek(Sector.MINISECTOR_SIZE * s.Id, SeekOrigin.Begin); - miniStreamView.Write(s.GetData(), 0, Sector.MINISECTOR_SIZE); - } - } - - /// - /// Allocate space, setup sectors id and refresh header - /// for the new or updated mini sector chain. - /// - /// The new MINI sector chain - private void AllocateMiniSectorChain(List sectorChain) - { - List miniFAT - = GetSectorChain(header.FirstMiniFATSectorID, SectorType.Normal); - - List miniStream - = GetSectorChain(RootEntry.StartSect, SectorType.Normal); - - using StreamView miniFATView - = new StreamView( - miniFAT, - SectorSize, - header.MiniFATSectorsNumber * Sector.MINISECTOR_SIZE, - null, - sourceStream, - true); - - using StreamView miniStreamView - = new StreamView( - miniStream, - SectorSize, - RootStorage.Size, - null, - sourceStream); - - - - // Set updated/new sectors within the ministream - // We are writing data in a NORMAL Sector chain. - for (int i = 0; i < sectorChain.Count; i++) - { - Sector s = sectorChain[i]; - - if (s.Id == -1) - { - // Allocate, position ministream at the end of already allocated - // ministream's sectors - - miniStreamView.Seek(RootStorage.Size + Sector.MINISECTOR_SIZE, SeekOrigin.Begin); - //miniStreamView.Write(s.GetData(), 0, Sector.MINISECTOR_SIZE); - s.Id = (int)(miniStreamView.Position - Sector.MINISECTOR_SIZE) / Sector.MINISECTOR_SIZE; - - RootStorage.DirEntry.Size = miniStreamView.Length; - - - } - } - - - - // Update miniFAT - StreamRW miniFATStreamRW = new(miniFATView); - for (int i = 0; i < sectorChain.Count - 1; i++) - { - int currentId = sectorChain[i].Id; - int nextId = sectorChain[i + 1].Id; - miniFATStreamRW.Seek(currentId * 4, SeekOrigin.Begin); - miniFATStreamRW.Write(nextId); - - } - - // Write End of Chain in MiniFAT - miniFATStreamRW.Seek(sectorChain[sectorChain.Count - 1].Id * SIZE_OF_SID, SeekOrigin.Begin); - miniFATStreamRW.Write(Sector.ENDOFCHAIN); - - - // Update sector chains - AllocateSectorChain(miniStreamView.BaseSectorChain); - AllocateSectorChain(miniFATView.BaseSectorChain); - - //Update HEADER and root storage when ministream changes - if (miniFAT.Count > 0) - { - RootStorage.DirEntry.StartSect = miniStream[0].Id; - header.MiniFATSectorsNumber = (uint)miniFAT.Count; - header.FirstMiniFATSectorID = miniFAT[0].Id; - } - } - - internal void FreeData(CFStream stream) - { - if (stream.Size == 0) - return; - - List sectorChain; - if (stream.Size < header.MinSizeStandardStream) - { - sectorChain = GetSectorChain(stream.DirEntry.StartSect, SectorType.Mini); - FreeMiniChain(sectorChain); - } - else - { - sectorChain = GetSectorChain(stream.DirEntry.StartSect, SectorType.Normal); - FreeChain(sectorChain); - } - - stream.DirEntry.StartSect = Sector.ENDOFCHAIN; - stream.DirEntry.Size = 0; - } - - private void FreeChain(List sectorChain) - { - FreeChain(sectorChain, 0); - } - - private void FreeChain(List sectorChain, int nth_sector_to_remove) - { - List FAT - = GetSectorChain(-1, SectorType.FAT); - - using StreamView FATView - = new StreamView(FAT, SectorSize, FAT.Count * SectorSize, null, sourceStream); - - // Zeroes out sector data (if required)------------- - if (eraseFreeSectors) - { - for (int i = nth_sector_to_remove; i < sectorChain.Count; i++) - { - Sector s = sectorChain[i]; - s.ZeroData(); - } - } - - // Update FAT marking unallocated sectors ---------- - StreamRW streamRW = new StreamRW(FATView); - for (int i = nth_sector_to_remove; i < sectorChain.Count; i++) - { - int position = sectorChain[i].Id * 4; - streamRW.Seek(position, SeekOrigin.Begin); - streamRW.Write(Sector.FREESECT); - } - - // Write new end of chain if partial free ---------- - if (nth_sector_to_remove > 0 && sectorChain.Count > 0) - { - int position = sectorChain[nth_sector_to_remove - 1].Id * 4; - streamRW.Seek(position, SeekOrigin.Begin); - streamRW.Write(Sector.ENDOFCHAIN); - } - } - - private void FreeMiniChain(List sectorChain) - { - FreeMiniChain(sectorChain, 0); - } - - private void FreeMiniChain(List sectorChain, int nth_sector_to_remove) - { - List miniStream - = GetSectorChain(RootEntry.StartSect, SectorType.Normal); - - using StreamView miniStreamView - = new StreamView(miniStream, SectorSize, RootStorage.Size, null, sourceStream); - - // Set updated/new sectors within the ministream ---------- - if (eraseFreeSectors) - { - byte[] ZEROED_MINI_SECTOR = new byte[Sector.MINISECTOR_SIZE]; - for (int i = nth_sector_to_remove; i < sectorChain.Count; i++) - { - Sector s = sectorChain[i]; - if (s.Id != -1) - { - // Overwrite - miniStreamView.Seek(Sector.MINISECTOR_SIZE * s.Id, SeekOrigin.Begin); - miniStreamView.Write(ZEROED_MINI_SECTOR, 0, Sector.MINISECTOR_SIZE); - } - } - } - - // Update miniFAT --------------------------------------- - List miniFAT - = GetSectorChain(header.FirstMiniFATSectorID, SectorType.Normal); - - using StreamView miniFATView - = new StreamView(miniFAT, SectorSize, header.MiniFATSectorsNumber * Sector.MINISECTOR_SIZE, null, sourceStream); - - StreamRW miniFATStreamRW = new StreamRW(miniFATView); - for (int i = nth_sector_to_remove; i < sectorChain.Count; i++) - { - int position = sectorChain[i].Id * 4; - miniFATStreamRW.Seek(position, SeekOrigin.Begin); - miniFATStreamRW.Write(Sector.FREESECT); - } - - // Write End of Chain in MiniFAT --------------------------------------- - //miniFATView.Seek(sectorChain[(sectorChain.Count - 1) - nth_sector_to_remove].Id * SIZE_OF_SID, SeekOrigin.Begin); - //miniFATView.Write(BitConverter.GetBytes(Sector.ENDOFCHAIN), 0, 4); - - // Write End of Chain in MiniFAT --------------------------------------- - if (nth_sector_to_remove > 0 && sectorChain.Count > 0) - { - miniFATStreamRW.Seek(sectorChain[nth_sector_to_remove - 1].Id * 4, SeekOrigin.Begin); - miniFATStreamRW.Write(Sector.ENDOFCHAIN); - } - - // Update sector chains --------------------------------------- - AllocateSectorChain(miniStreamView.BaseSectorChain); - AllocateSectorChain(miniFATView.BaseSectorChain); - - // Update HEADER and root storage when ministream changes - if (miniFAT.Count > 0) - { - RootStorage.DirEntry.StartSect = miniStream[0].Id; - header.MiniFATSectorsNumber = (uint)miniFAT.Count; - header.FirstMiniFATSectorID = miniFAT[0].Id; - } - } - - /// - /// Allocate space, setup sectors id in the FAT and refresh header - /// for the new or updated sector chain (Normal or Mini sectors) - /// - /// The new or updated normal or mini sector chain - private void SetSectorChain(List sectorChain) - { - if (sectorChain == null || sectorChain.Count == 0) - return; - - SectorType _st = sectorChain[0].Type; - - if (_st == SectorType.Normal) - { - AllocateSectorChain(sectorChain); - } - else if (_st == SectorType.Mini) - { - AllocateMiniSectorChain(sectorChain); - } - } - - /// - /// Allocate space, setup sectors id and refresh header - /// for the new or updated sector chain. - /// - /// The new or updated generic sector chain - private void AllocateSectorChain(List sectorChain) - { - foreach (Sector s in sectorChain) - { - if (s.Id == -1) - { - sectors.Add(s); - s.Id = sectors.Count - 1; - } - } - - AllocateFATSectorChain(sectorChain); - } - - internal bool _transactionLockAdded; - internal int _lockSectorId = -1; - internal bool _transactionLockAllocated; - - /// - /// Check for transaction lock sector addition and mark it in the FAT. - /// - private void CheckForLockSector() - { - //If transaction lock has been added and not yet allocated in the FAT... - if (_transactionLockAdded && !_transactionLockAllocated) - { - using StreamView fatStream = new StreamView(GetFatSectorChain(), SectorSize, sourceStream); - - fatStream.Seek(_lockSectorId * 4, SeekOrigin.Begin); - fatStream.Write(BitConverter.GetBytes(Sector.ENDOFCHAIN), 0, 4); - - _transactionLockAllocated = true; - } - } - - /// - /// Allocate space, setup sectors id and refresh header - /// for the new or updated FAT sector chain. - /// - /// The new or updated generic sector chain - private void AllocateFATSectorChain(List sectorChain) - { - List fatSectors = GetSectorChain(-1, SectorType.FAT); - - using StreamView fatStream = - new StreamView( - fatSectors, - SectorSize, - header.FATSectorsNumber * SectorSize, - null, - sourceStream, - true); - - // Write FAT chain values -- - StreamRW fatStreamRW = new StreamRW(fatStream); - for (int i = 0; i < sectorChain.Count - 1; i++) - { - Sector sN = sectorChain[i + 1]; - Sector sC = sectorChain[i]; - - fatStreamRW.Seek(sC.Id * 4, SeekOrigin.Begin); - fatStreamRW.Write(sN.Id); - } - - fatStreamRW.Seek(sectorChain[sectorChain.Count - 1].Id * 4, SeekOrigin.Begin); - fatStreamRW.Write(Sector.ENDOFCHAIN); - - // Merge chain to CFS - AllocateDIFATSectorChain(fatStream.BaseSectorChain); - } - - /// - /// Setup the DIFAT sector chain - /// - /// A FAT sector chain - private void AllocateDIFATSectorChain(List FATsectorChain) - { - //Get initial DIFAT chain - List difatSectors = - GetSectorChain(-1, SectorType.DIFAT); - - // Get initial sector's count - header.FATSectorsNumber = FATsectorChain.Count; - - // Allocate Sectors - foreach (Sector s in FATsectorChain) - { - if (s.Id == -1) - { - sectors.Add(s); - s.Id = sectors.Count - 1; - s.Type = SectorType.FAT; - } - } - - // Sector count... - //int nCurrentSectors = sectors.Count; - - // Temp DIFAT count - //int nDIFATSectors = (int)header.DIFATSectorsNumber; - int nDIFATSectors = 0; - - if (FATsectorChain.Count > HEADER_DIFAT_ENTRIES_COUNT) - { - nDIFATSectors = Ceiling((double)(FATsectorChain.Count - HEADER_DIFAT_ENTRIES_COUNT) / DIFAT_SECTOR_FAT_ENTRIES_COUNT); - nDIFATSectors = LowSaturation(nDIFATSectors - (int)header.DIFATSectorsNumber); //required DIFAT - } - - - for (int i = 0; i < (nDIFATSectors - difatSectors.Count); i++) - { - Sector s = new Sector(SectorSize, sourceStream); - sectors.Add(s); - s.Id = sectors.Count - 1; - s.Type = SectorType.DIFAT; - difatSectors.Add(s); - } - - // ...sum with new required DIFAT sectors count - //nCurrentSectors += nDIFATSectors; - //header.FATSectorsNumber += nDIFATSectors; - - // ReCheck FAT bias - while (FATsectorChain.Count * FAT_SECTOR_ENTRIES_COUNT < sectors.Count) - { - Sector extraFATSector = new Sector(SectorSize, sourceStream); - sectors.Add(extraFATSector); - - extraFATSector.Id = sectors.Count - 1; - extraFATSector.Type = SectorType.FAT; - - FATsectorChain.Add(extraFATSector); - - //header.FATSectorsNumber++; - //nCurrentSectors++; - - //... so, adding a FAT sector may induce DIFAT sectors to increase by one - // and consequently this may induce ANOTHER FAT sector (TO-THINK: May this condition occur ?) - if (difatSectors.Count * DIFAT_SECTOR_FAT_ENTRIES_COUNT < (FATsectorChain.Count - HEADER_DIFAT_ENTRIES_COUNT)) - { - - Sector s = new Sector(SectorSize, sourceStream); - sectors.Add(s); - s.Type = SectorType.DIFAT; - s.Id = sectors.Count - 1; - difatSectors.Add(s); - - } - } - - using StreamView difatStream - = new StreamView(difatSectors, SectorSize, difatSectors.Count * SectorSize, null, sourceStream); - - StreamRW difatStreamRW = new(difatStream); - - // Write DIFAT Sectors (if required) - // Save room for the following chaining - for (int i = 0; i < FATsectorChain.Count; i++) - { - if (i < HEADER_DIFAT_ENTRIES_COUNT) - { - header.DIFAT[i] = FATsectorChain[i].Id; - } - else - { - // room for DIFAT chaining at the end of any DIFAT sector (4 bytes) - if (i != HEADER_DIFAT_ENTRIES_COUNT && (i - HEADER_DIFAT_ENTRIES_COUNT) % DIFAT_SECTOR_FAT_ENTRIES_COUNT == 0) - { - difatStreamRW.Write(0); - } - - difatStreamRW.Write(FATsectorChain[i].Id); - } - } - - // Allocate room for DIFAT sectors - for (int i = 0; i < difatStream.BaseSectorChain.Count; i++) - { - if (difatStream.BaseSectorChain[i].Id == -1) - { - sectors.Add(difatStream.BaseSectorChain[i]); - difatStream.BaseSectorChain[i].Id = sectors.Count - 1; - difatStream.BaseSectorChain[i].Type = SectorType.DIFAT; - } - } - - header.DIFATSectorsNumber = (uint)difatStream.BaseSectorChain.Count; - - // Chain first sector - if (difatStream.BaseSectorChain != null && difatStream.BaseSectorChain.Count > 0) - { - header.FirstDIFATSectorID = difatStream.BaseSectorChain[0].Id; - - // Update header information - header.DIFATSectorsNumber = (uint)difatStream.BaseSectorChain.Count; - - // Write chaining information at the end of DIFAT Sectors - for (int i = 0; i < difatStream.BaseSectorChain.Count - 1; i++) - { - BinaryPrimitives.WriteInt32LittleEndian( - difatStream.BaseSectorChain[i].GetData(), - SectorSize - sizeof(int), - difatStream.BaseSectorChain[i + 1].Id); - } - - BinaryPrimitives.WriteInt32LittleEndian( - difatStream.BaseSectorChain[difatStream.BaseSectorChain.Count - 1].GetData(), - SectorSize - sizeof(int), - Sector.ENDOFCHAIN); - } - else - header.FirstDIFATSectorID = Sector.ENDOFCHAIN; - - // Mark DIFAT Sectors in FAT - using StreamView fatSv = - new StreamView(FATsectorChain, SectorSize, FATsectorChain.Count * SectorSize, null, sourceStream); - - StreamRW streamRW = new(fatSv); - - for (int i = 0; i < difatStream.BaseSectorChain.Count; i++) - { - streamRW.Seek(difatStream.BaseSectorChain[i].Id * 4, SeekOrigin.Begin); - streamRW.Write(Sector.DIFSECT); - } - - for (int i = 0; i < fatSv.BaseSectorChain.Count; i++) - { - streamRW.Seek(fatSv.BaseSectorChain[i].Id * 4, SeekOrigin.Begin); - streamRW.Write(Sector.FATSECT); - } - - //fatSv.Seek(fatSv.BaseSectorChain[fatSv.BaseSectorChain.Count - 1].Id * 4, SeekOrigin.Begin); - //fatSv.Write(BitConverter.GetBytes(Sector.ENDOFCHAIN), 0, 4); - - header.FATSectorsNumber = FATsectorChain.Count; - header.DIFATSectorsNumber = (uint)difatSectors.Count; - } - - /// - /// Get the DIFAT Sector chain - /// - /// A list of DIFAT sectors - private List GetDifatSectorChain() - { - List result - = new List(); - HashSet processedSectors = new HashSet(); - - if (header.DIFATSectorsNumber != 0) - { - int validationCount = (int)header.DIFATSectorsNumber; - Sector s = sectors[header.FirstDIFATSectorID]; - - if (s == null) //Lazy loading - { - s = new Sector(SectorSize, sourceStream) - { - Type = SectorType.DIFAT, - Id = header.FirstDIFATSectorID - }; - sectors[header.FirstDIFATSectorID] = s; - } - - result.Add(s); - - while (true && validationCount >= 0) - { - int nextSecID = BitConverter.ToInt32(s.GetData(), SectorSize - 4); - EnsureUniqueSectorIndex(nextSecID, processedSectors); - - // Strictly speaking, the following condition is not correct from - // a specification point of view: - // only ENDOFCHAIN should break DIFAT chain but - // a lot of existing compound files use FREESECT as DIFAT chain termination - if (nextSecID is Sector.FREESECT or Sector.ENDOFCHAIN) break; - - validationCount--; - - if (validationCount < 0) - { - if (closeStream) - Close(); - - if (ValidationExceptionEnabled) - throw new CFCorruptedFileException("DIFAT sectors count mismatched. Corrupted compound file"); - } - - s = sectors[nextSecID]; - - if (s == null) - { - s = new Sector(SectorSize, sourceStream) - { - Id = nextSecID - }; - sectors[nextSecID] = s; - } - - result.Add(s); - } - } - - return result; - } - - private void EnsureUniqueSectorIndex(int nextSecID, HashSet processedSectors) - { - if (!ValidationExceptionEnabled) - { - return; - } - - if (!processedSectors.Add(nextSecID)) - { - throw new CFCorruptedFileException("The file is corrupted."); - } - } - - /// - /// Get the FAT sector chain - /// - /// List of FAT sectors - private List GetFatSectorChain() - { - int N_HEADER_FAT_ENTRY = 109; //Number of FAT sectors id in the header - - List result - = new List(); - List difatSectors = GetDifatSectorChain(); - - int idx = 0; - int nextSecID; - - // Read FAT entries from the header Fat entry array (max 109 entries) - while (idx < header.FATSectorsNumber && idx < N_HEADER_FAT_ENTRY) - { - nextSecID = header.DIFAT[idx]; - Sector s = sectors[nextSecID]; - - if (s == null) - { - s = new Sector(SectorSize, sourceStream) - { - Id = nextSecID, - Type = SectorType.FAT - }; - sectors[nextSecID] = s; - } - - result.Add(s); - - idx++; - } - - //Is there any DIFAT sector containing other FAT entries ? - if (difatSectors.Count > 0) - { - HashSet processedSectors = new HashSet(); - using StreamView difatStream - = new StreamView - ( - difatSectors, - SectorSize, - difatSectors.Count * SectorSize, - null, - sourceStream); - - StreamRW difatStreamRW = new(difatStream); - - int i = 0; - - while (result.Count < header.FATSectorsNumber) - { - nextSecID = difatStreamRW.ReadInt32(); - - EnsureUniqueSectorIndex(nextSecID, processedSectors); - - Sector s = sectors[nextSecID]; - - if (s == null) - { - s = new Sector(SectorSize, sourceStream) - { - Type = SectorType.FAT, - Id = nextSecID - }; - sectors[nextSecID] = s; //UUU - } - - result.Add(s); - - if (difatStream.Position == (SectorSize - 4 + i * SectorSize)) - { - // Skip DIFAT chain fields considering the possibility that the last FAT entry has been already read - if (difatStreamRW.ReadInt32() == Sector.ENDOFCHAIN) - break; - - i++; - continue; - } - } - } - - return result; - } - - /// - /// Get a standard sector chain - /// - /// First SecID of the required chain - /// A list of sectors - private List GetNormalSectorChain(int secID) - { - List result - = new List(); - - int nextSecID = secID; - - List fatSectors = GetFatSectorChain(); - HashSet processedSectors = new HashSet(); - - using StreamView fatStream - = new StreamView(fatSectors, SectorSize, fatSectors.Count * SectorSize, null, sourceStream); - StreamRW fatStreamRW = new(fatStream); - - while (true) - { - if (nextSecID == Sector.ENDOFCHAIN) break; - - if (nextSecID < 0) - throw new CFCorruptedFileException(string.Format("Next Sector ID reference is below zero. NextID : {0}", nextSecID)); - - if (nextSecID >= sectors.Count) - throw new CFCorruptedFileException(string.Format("Next Sector ID reference an out of range sector. NextID : {0} while sector count {1}", nextSecID, sectors.Count)); - - Sector s = sectors[nextSecID]; - if (s == null) - { - s = new Sector(SectorSize, sourceStream) - { - Id = nextSecID, - Type = SectorType.Normal - }; - sectors[nextSecID] = s; - } - - result.Add(s); - - fatStreamRW.Seek(nextSecID * 4, SeekOrigin.Begin); - int next = fatStreamRW.ReadInt32(); - - EnsureUniqueSectorIndex(next, processedSectors); - nextSecID = next; - } - - return result; - } - - /// - /// Get a mini sector chain - /// - /// First SecID of the required chain - /// A list of mini sectors (64 bytes) - private List GetMiniSectorChain(int secID) - { - List result - = new List(); - - if (secID != Sector.ENDOFCHAIN) - { - List miniFAT = GetNormalSectorChain(header.FirstMiniFATSectorID); - List miniStream = GetNormalSectorChain(RootEntry.StartSect); - - using StreamView miniFATView - = new StreamView(miniFAT, SectorSize, header.MiniFATSectorsNumber * SectorSize, null, sourceStream); - - using StreamView miniStreamView = - new StreamView(miniStream, SectorSize, RootStorage.Size, null, sourceStream); - - StreamRW miniFATReader = new StreamRW(miniFATView); - - int nextSecID = secID; - HashSet processedSectors = new HashSet(); - - while (true) - { - if (nextSecID == Sector.ENDOFCHAIN) - break; - - Sector ms = new Sector(Sector.MINISECTOR_SIZE, sourceStream) - { - Id = nextSecID, - Type = SectorType.Mini - }; - - miniStreamView.Seek(nextSecID * Sector.MINISECTOR_SIZE, SeekOrigin.Begin); - miniStreamView.ReadExactly(ms.GetData(), 0, Sector.MINISECTOR_SIZE); - - result.Add(ms); - - miniFATView.Seek(nextSecID * 4, SeekOrigin.Begin); - int next = miniFATReader.ReadInt32(); - - nextSecID = next; - EnsureUniqueSectorIndex(nextSecID, processedSectors); - } - } - - return result; - } - - /// - /// Get a sector chain from a compound file given the first sector ID - /// and the required sector type. - /// - /// First chain sector's id - /// Type of Sectors in the required chain (mini sectors, normal sectors or FAT) - /// A list of Sectors as the result of their concatenation - internal List GetSectorChain(int secID, SectorType chainType) - { - return chainType switch - { - SectorType.DIFAT => GetDifatSectorChain(), - SectorType.FAT => GetFatSectorChain(), - SectorType.Normal => GetNormalSectorChain(secID), - SectorType.Mini => GetMiniSectorChain(secID), - _ => throw new CFException("Unsupported chain type"), - }; - } - - /// - /// The entry point object that represents the - /// root of the structures tree to get or set storage or - /// stream data. - /// - /// - /// - /// - /// //Create a compound file - /// string FILENAME = "MyFileName.cfs"; - /// CompoundFile ncf = new CompoundFile(); - /// - /// CFStorage l1 = ncf.RootStorage.AddStorage("Storage Level 1"); - /// - /// l1.AddStream("l1ns1"); - /// l1.AddStream("l1ns2"); - /// l1.AddStream("l1ns3"); - /// CFStorage l2 = l1.AddStorage("Storage Level 2"); - /// l2.AddStream("l2ns1"); - /// l2.AddStream("l2ns2"); - /// - /// ncf.Save(FILENAME); - /// ncf.Close(); - /// - /// - public CFStorage RootStorage { get; private set; } - - public CFSVersion Version => (CFSVersion)header.MajorVersion; - - //internal class NodeFactory : IRBTreeDeserializer - //{ - - // public RBNode DeserizlizeFromValues() - // { - // RBNode node = new RBNode(value,(Color)value.DirEntry.StgColor, - // } - //} - - //void OnValueAssigned(RBNode node, CFItem from) - //{ - // if (from.DirEntry != null && from.DirEntry.LeftSibling != DirectoryEntry.NOSTREAM) - - // if (from.DirEntry != null && from.DirEntry.LeftSibling != DirectoryEntry.NOSTREAM) - // node.Value.DirEntry.LeftSibling = from.DirEntry.LeftSibling; - - // if (from.DirEntry != null && from.DirEntry.RightSibling != DirectoryEntry.NOSTREAM) - // node.Value.DirEntry.RightSibling = from.DirEntry.RightSibling; - //} - - internal RBTree GetChildrenTree(IDirectoryEntry entry) - { - RBTree bst = new(); - List levelSIDs = new List(); - LoadChildren(bst, entry.Child, levelSIDs); - return bst; - } - - private static void NullifyChildNodes(IDirectoryEntry de) - { - de.Parent = null; - de.Left = null; - de.Right = null; - } - - private void LoadChildren(RBTree bst, IDirectoryEntry de, List levelSIDs) - { - levelSIDs.Add(de.SID); - - if (de.StgType == StgType.StgInvalid) - { - if (ValidationExceptionEnabled) - throw new CFCorruptedFileException($"A Directory Entry has a valid reference to an Invalid Storage Type directory [{de.SID}]"); - return; - } - - if (!Enum.IsDefined(typeof(StgType), de.StgType)) - { - if (ValidationExceptionEnabled) - throw new CFCorruptedFileException("A Directory Entry has an invalid Storage Type"); - return; - } - - LoadChildren(bst, de.LeftSibling, levelSIDs); - LoadChildren(bst, de.RightSibling, levelSIDs); - NullifyChildNodes(de); - bst.Insert(de); - } - - private void LoadChildren(RBTree bst, int sid, List levelSIDs) - { - if (sid == DirectoryEntry.NOSTREAM) - return; - - // if this siblings id does not overflow current list - if (sid >= directoryEntries.Count) - { - if (ValidationExceptionEnabled) - throw new CFCorruptedFileException($"A Directory Entry references the non-existent sid number {sid}"); - return; - } - - if (levelSIDs.Contains(sid)) - throw new CFCorruptedFileException("Cyclic reference of directory item"); - - IDirectoryEntry de = directoryEntries[sid]; - LoadChildren(bst, de, levelSIDs); - } - - /// - /// Load directory entries from compound file. Header and FAT MUST be already loaded. - /// - private void LoadDirectories() - { - List directoryChain - = GetSectorChain(header.FirstDirectorySectorID, SectorType.Normal); - - if (!(directoryChain.Count > 0)) - throw new CFCorruptedFileException("Directory sector chain MUST contain at least 1 sector"); - - if (header.FirstDirectorySectorID == Sector.ENDOFCHAIN) - header.FirstDirectorySectorID = directoryChain[0].Id; - - using StreamView dirReader - = new StreamView(directoryChain, SectorSize, directoryChain.Count * SectorSize, null, sourceStream); - - StreamRW dirReaderRW = new(dirReader); - - while (dirReader.Position < directoryChain.Count * SectorSize) - { - IDirectoryEntry de = DirectoryEntry.New(string.Empty, StgType.StgInvalid, directoryEntries); - - // We are not inserting dirs. Do not use 'InsertNewDirectoryEntry' - de.Read(dirReaderRW, Version); - } - } - - /// - /// Commit directory entries change on the Current Source stream - /// - private void CommitDirectory() - { - const int DIRECTORY_SIZE = 128; - - List directorySectors - = GetSectorChain(header.FirstDirectorySectorID, SectorType.Normal); - - using StreamView sv = new StreamView(directorySectors, SectorSize, 0, null, sourceStream); - - StreamRW svRW = new(sv); - - foreach (IDirectoryEntry di in directoryEntries) - { - di.Write(svRW); - } - - int delta = directoryEntries.Count; - - while (delta % (SectorSize / DIRECTORY_SIZE) != 0) - { - IDirectoryEntry dummy = DirectoryEntry.New(string.Empty, StgType.StgInvalid, directoryEntries); - dummy.Write(svRW); - delta++; - } - - foreach (Sector s in directorySectors) - { - s.Type = SectorType.Directory; - } - - AllocateSectorChain(directorySectors); - - header.FirstDirectorySectorID = directorySectors[0].Id; - - //Version 4 supports directory sectors count - if (header.MajorVersion == 3) - { - header.DirectorySectorsNumber = 0; - } - else - { - header.DirectorySectorsNumber = directorySectors.Count; - } - } - - /// - /// Saves the in-memory image of Compound File opened in ReadOnly mode to a file. - /// - /// File name to write the compound file to - /// Raised if destination file is not seekable - /// Raised if destination file is the current file - public void SaveAs(string fileName) - { - if (IsClosed) - throw new CFException("Compound File closed: cannot save data"); - - try - { - bool raiseSaveFileEx = false; - - if (this.HasSourceStream && this.sourceStream != null && this.sourceStream is FileStream stream) - { - if (Path.IsPathRooted(fileName)) - { - //Debug.WriteLine("Path is rooted"); - //Debug.WriteLine("Filename:"+ fileName); - //Debug.WriteLine("Stream name:"+ stream.Name); - //Debug.WriteLine("Stream name equals filename? :" + (stream.Name == fileName)); - - if (stream.Name == fileName) - { - //Debug.WriteLine("-> Filename equals stream name"); - - raiseSaveFileEx = true; - } - } - else - { - //Debug.WriteLine("Path is NOT rooted"); - //Debug.WriteLine("Filename:"+ fileName); - //Debug.WriteLine("Filename modified:"+ (Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + fileName)); - //Debug.WriteLine("Directory name:"+ Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)); - //Debug.WriteLine("Stream name:"+ stream.Name); - //Debug.WriteLine("Stream name equals filename? :" + (stream.Name == (Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + fileName))); - - if (stream.Name == (Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + fileName)) - { - //Debug.WriteLine("-> Filename equals stream name:"); - - raiseSaveFileEx = true; - } - } - } - - if (raiseSaveFileEx) - { - throw new CFInvalidOperation("Cannot overwrite current backing file. Compound File should be opened in UpdateMode and Commit() method should be called to persist changes"); - } - - using FileStream fs = new(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); - Save(fs); - } - catch (Exception ex) - { - throw new CFException("Error saving file [" + fileName + "]", ex); - } - finally - { - sourceStream?.Close(); - } - } - - /// - /// Saves the in-memory image of Compound File to a file. - /// - /// File name to write the compound file to - /// Raised if destination file is not seekable - /// Raised if destination file is the current file - [Obsolete("Use SaveAs method")] - public void Save(string fileName) - { - SaveAs(fileName); - } - - /// - /// Saves the in-memory image of Compound File to a stream. - /// - /// - /// Destination Stream must be seekable. Uncommitted data will be persisted to the destination stream. - /// - /// The stream to save compound File to - /// Raised if destination stream is not seekable - /// Raised if Compound File Storage has been already disposed - /// - /// - /// MemoryStream ms = new MemoryStream(size); - /// - /// CompoundFile cf = new CompoundFile(); - /// CFStorage st = cf.RootStorage.AddStorage("MyStorage"); - /// CFStream sm = st.AddStream("MyStream"); - /// - /// byte[] b = new byte[]{0x00,0x01,0x02,0x03}; - /// - /// sm.SetData(b); - /// cf.Save(ms); - /// cf.Close(); - /// - /// - public void Save(Stream stream) - { - if (IsClosed) - throw new CFDisposedException("Compound File closed: cannot save data"); - - if (!stream.CanSeek) - throw new CFException("Cannot save on a non-seekable stream"); - - CheckForLockSector(); - int sSize = SectorSize; - - try - { - if (this.HasSourceStream && this.sourceStream != null && this.sourceStream is FileStream && stream is FileStream otherStream) - { - if (((FileStream)this.sourceStream).Name == otherStream.Name) - { - throw new CFInvalidOperation("Cannot overwrite current backing file. Compound File should be opened in UpdateMode and Commit() method should be called to persist changes"); - } - } - - stream.Write(new byte[sSize], 0, sSize); - - CommitDirectory(); - - for (int i = 0; i < sectors.Count; i++) - { - Sector s = sectors[i]; - - // Load source (unmodified) sectors - // Here we have to ignore "Dirty flag" of - // sectors because we are NOT modifying the source - // in a differential way but ALL sectors need to be - // persisted on the destination stream - s ??= new Sector(sSize, sourceStream) - { - Id = i - }; - - stream.Write(s.GetData(), 0, sSize); - - //s.ReleaseData(); - } - - stream.Seek(0, SeekOrigin.Begin); - header.Write(stream); - } - catch (Exception ex) - { - sourceStream?.Close(); - throw new CFException("Internal error while saving compound file to stream ", ex); - } - } - - /// - /// Scan FAT o miniFAT for free sectors to reuse. - /// - /// Type of sector to look for - /// A Queue of available sectors or minisectors already allocated - internal Queue FindFreeSectors(SectorType sType) - { - Queue freeList = new Queue(); - - if (sType == SectorType.Normal) - { - List FatChain = GetSectorChain(-1, SectorType.FAT); - using StreamView fatStream = new StreamView(FatChain, SectorSize, header.FATSectorsNumber * SectorSize, null, sourceStream); - StreamRW fatStreamRW = new(fatStream); - - int idx = 0; - - while (idx < sectors.Count) - { - int id = fatStreamRW.ReadInt32(); - - if (id == Sector.FREESECT) - { - if (sectors[idx] == null) - { - Sector s = new Sector(SectorSize, sourceStream) - { - Id = idx - }; - sectors[idx] = s; - } - - freeList.Enqueue(sectors[idx]); - } - - idx++; - } - } - else - { - List miniFAT - = GetSectorChain(header.FirstMiniFATSectorID, SectorType.Normal); - - using StreamView miniFATView - = new StreamView(miniFAT, SectorSize, header.MiniFATSectorsNumber * SectorSize, null, sourceStream); - - StreamRW miniFATStreamRW = new(miniFATView); - - List miniStream - = GetSectorChain(RootEntry.StartSect, SectorType.Normal); - - using StreamView miniStreamView - = new StreamView(miniStream, SectorSize, RootStorage.Size, null, sourceStream); - - int idx = 0; - - int nMinisectors = (int)(miniStreamView.Length / Sector.MINISECTOR_SIZE); - - while (idx < nMinisectors) - { - //AssureLength(miniStreamView, (int)miniFATView.Length); - - int nextId = miniFATStreamRW.ReadInt32(); - - if (nextId == Sector.FREESECT) - { - Sector ms = new Sector(Sector.MINISECTOR_SIZE, sourceStream) - { - Id = idx, - Type = SectorType.Mini - }; - - miniStreamView.Seek(ms.Id * Sector.MINISECTOR_SIZE, SeekOrigin.Begin); - miniStreamView.ReadExactly(ms.GetData(), 0, Sector.MINISECTOR_SIZE); - - freeList.Enqueue(ms); - } - - idx++; - } - } - - return freeList; - } - - /// - /// INTERNAL DEVELOPMENT. DO NOT CALL. - /// - /// - internal void AppendData(CFItem cfItem, byte[] buffer) - { - WriteData(cfItem, cfItem.Size, buffer); - } - - /// - /// Resize stream length - /// - /// - /// - internal void SetStreamLength(CFItem cfItem, long length) - { - if (cfItem.Size == length) - return; - - SectorType newSectorType = SectorType.Normal; - int newSectorSize = SectorSize; - - if (length < header.MinSizeStandardStream) - { - newSectorType = SectorType.Mini; - newSectorSize = Sector.MINISECTOR_SIZE; - } - - SectorType oldSectorType = SectorType.Normal; - int oldSectorSize = SectorSize; - - if (cfItem.Size < header.MinSizeStandardStream) - { - oldSectorType = SectorType.Mini; - oldSectorSize = Sector.MINISECTOR_SIZE; - } - - long oldSize = cfItem.Size; - - // Get Sector chain and delta size induced by client - List sectorChain = GetSectorChain(cfItem.DirEntry.StartSect, oldSectorType); - long delta = length - cfItem.Size; - - // Check for transition ministream -> stream: - // Only in this case we need to free old sectors, - // otherwise they will be overwritten. - - bool transitionToMini = false; - bool transitionToNormal = false; - List oldChain = null; - - if (cfItem.DirEntry.StartSect != Sector.ENDOFCHAIN) - { - if ( - (length < header.MinSizeStandardStream && cfItem.DirEntry.Size >= header.MinSizeStandardStream) - || (length >= header.MinSizeStandardStream && cfItem.DirEntry.Size < header.MinSizeStandardStream)) - { - if (cfItem.DirEntry.Size < header.MinSizeStandardStream) - { - transitionToNormal = true; - oldChain = sectorChain; - } - else - { - transitionToMini = true; - oldChain = sectorChain; - } - - // No transition caused by size change - } - } - - Queue freeList = null; - if (!transitionToMini && !transitionToNormal) //############ NO TRANSITION - { - if (delta > 0) // Enlarging stream... - { - if (sectorRecycle) - freeList = FindFreeSectors(newSectorType); // Collect available free sectors - - using StreamView sv = new(sectorChain, newSectorSize, length, freeList, sourceStream); - - //Set up destination chain - SetSectorChain(sectorChain); - } - else if (delta < 0) // Reducing size... - { - int nSec = (int)Math.Floor((double)Math.Abs(delta) / newSectorSize); //number of sectors to mark as free - - int startFreeSector = sectorChain.Count - nSec; // start sector to free - - if (newSectorSize == Sector.MINISECTOR_SIZE) - FreeMiniChain(sectorChain, startFreeSector); - else - FreeChain(sectorChain, startFreeSector); - } - - if (sectorChain.Count > 0) - { - cfItem.DirEntry.StartSect = sectorChain[0].Id; - cfItem.DirEntry.Size = length; - } - else - { - cfItem.DirEntry.StartSect = Sector.ENDOFCHAIN; - cfItem.DirEntry.Size = 0; - } - } - else if (transitionToMini) //############## TRANSITION TO MINISTREAM - { - // Transition Normal chain -> Mini chain - - // Collect available MINI free sectors - - if (sectorRecycle) - freeList = FindFreeSectors(SectorType.Mini); - - using StreamView sv = new(oldChain, oldSectorSize, oldSize, null, sourceStream); - - // Reset start sector and size of dir entry - cfItem.DirEntry.StartSect = Sector.ENDOFCHAIN; - cfItem.DirEntry.Size = 0; - - List newChain = GetMiniSectorChain(Sector.ENDOFCHAIN); - using StreamView destSv = new(newChain, Sector.MINISECTOR_SIZE, length, freeList, sourceStream); - - // Buffered trimmed copy from old (larger) to new (smaller) - int cnt = 4096 < length ? 4096 : (int)length; - - byte[] buf = new byte[4096]; - long toRead = length; - - //Copy old to new chain - while (toRead > cnt) - { - cnt = sv.Read(buf, 0, cnt); - toRead -= cnt; - destSv.Write(buf, 0, cnt); - } - - sv.ReadExactly(buf, 0, (int)toRead); - destSv.Write(buf, 0, (int)toRead); - - //Free old chain - FreeChain(oldChain); - - //Set up destination chain - AllocateMiniSectorChain(destSv.BaseSectorChain); - - // Persist to normal stream - PersistMiniStreamToStream(destSv.BaseSectorChain); - - //Update dir item - if (destSv.BaseSectorChain.Count > 0) - { - cfItem.DirEntry.StartSect = destSv.BaseSectorChain[0].Id; - cfItem.DirEntry.Size = length; - } - else - { - cfItem.DirEntry.StartSect = Sector.ENDOFCHAIN; - cfItem.DirEntry.Size = 0; - } - } - else if (transitionToNormal) //############## TRANSITION TO NORMAL STREAM - { - // Transition Mini chain -> Normal chain - - if (sectorRecycle) - freeList = FindFreeSectors(SectorType.Normal); // Collect available Normal free sectors - - using StreamView sv = new(oldChain, oldSectorSize, oldSize, null, sourceStream); - - List newChain = GetNormalSectorChain(Sector.ENDOFCHAIN); - using StreamView destSv = new StreamView(newChain, SectorSize, length, freeList, sourceStream); - - int cnt = 256 < length ? 256 : (int)length; - - byte[] buf = new byte[256]; - long toRead = Math.Min(length, cfItem.Size); - - //Copy old to new chain - while (toRead > cnt) - { - cnt = sv.Read(buf, 0, cnt); - toRead -= cnt; - destSv.Write(buf, 0, cnt); - } - - sv.ReadExactly(buf, 0, (int)toRead); - destSv.Write(buf, 0, (int)toRead); - - //Free old mini chain - int oldChainCount = oldChain.Count; - FreeMiniChain(oldChain); - - //Set up normal destination chain - AllocateSectorChain(destSv.BaseSectorChain); - - //Update dir item - if (destSv.BaseSectorChain.Count > 0) - { - cfItem.DirEntry.StartSect = destSv.BaseSectorChain[0].Id; - cfItem.DirEntry.Size = length; - } - else - { - cfItem.DirEntry.StartSect = Sector.ENDOFCHAIN; - cfItem.DirEntry.Size = 0; - } - } - } - - internal void WriteData(CFItem cfItem, long position, byte[] buffer) - { - WriteData(cfItem, buffer, position, 0, buffer.Length); - } - - internal void WriteData(CFItem cfItem, byte[] buffer, long position, int offset, int count) - { - if (buffer == null) - throw new CFInvalidOperation("Parameter [buffer] cannot be null"); - - if (cfItem.DirEntry == null) - throw new CFException("Internal error [cfItem.DirEntry] cannot be null"); - - if (buffer.Length == 0) return; - - // Get delta size induced by client - long delta = position + count - cfItem.Size < 0 ? 0 : position + count - cfItem.Size; - long newLength = cfItem.Size + delta; - - SetStreamLength(cfItem, newLength); - - // Calculate NEW sectors SIZE - SectorType _st = SectorType.Normal; - int _sectorSize = SectorSize; - - if (cfItem.Size < header.MinSizeStandardStream) - { - _st = SectorType.Mini; - _sectorSize = Sector.MINISECTOR_SIZE; - } - - List sectorChain = GetSectorChain(cfItem.DirEntry.StartSect, _st); - using StreamView sv = new StreamView(sectorChain, _sectorSize, newLength, null, sourceStream); - - sv.Seek(position, SeekOrigin.Begin); - sv.Write(buffer, offset, count); - - if (cfItem.Size < header.MinSizeStandardStream) - { - PersistMiniStreamToStream(sv.BaseSectorChain); - //SetSectorChain(sv.BaseSectorChain); - } - } - - internal void WriteData(CFItem cfItem, byte[] buffer) - { - WriteData(cfItem, 0, buffer); - } - - /// - /// Check file size limit ( 2GB for version 3 ) - /// - private void CheckFileLength() - { - throw new NotImplementedException(); - } - - internal int ReadData(IDirectoryEntry de, long position, byte[] buffer, int offset, int count) - { - count = (int)Math.Min(buffer.Length - offset, (long)count); - - SectorType sectorType = de.Size < header.MinSizeStandardStream ? SectorType.Mini : SectorType.Normal; - List chain = GetSectorChain(de.StartSect, sectorType); - int sectorSize = sectorType == SectorType.Mini ? Sector.MINISECTOR_SIZE : SectorSize; - using StreamView sView = new(chain, sectorSize, de.Size, null, sourceStream); - sView.Seek(position, SeekOrigin.Begin); - int result = sView.Read(buffer, offset, count); - return result; - } - - internal byte[] GetData(IDirectoryEntry de) - { - byte[] result = new byte[(int)de.Size]; - ReadData(de, 0, result, 0, result.Length); - return result; - } - - public byte[] GetDataBySID(int sid) - { - if (sid < 0) - return null; - - if (IsClosed) - throw new CFDisposedException("Compound File closed: cannot access data"); - - try - { - IDirectoryEntry de = directoryEntries[sid]; - return GetData(de); - } - catch - { - throw new CFException("Cannot get data for SID"); - } - } - - public Guid getGuidBySID(int sid) - { - if (IsClosed) - throw new CFDisposedException("Compound File closed: cannot access data"); - if (sid < 0) - throw new CFException("Invalid SID"); - IDirectoryEntry de = directoryEntries[sid]; - return de.StorageCLSID; - } - - public Guid getGuidForStream(int sid) - { - if (IsClosed) - throw new CFDisposedException("Compound File closed: cannot access data"); - if (sid < 0) - throw new CFException("Invalid SID"); - Guid g = Guid.Empty; - //find first storage containing a non-zero CLSID before SID in directory structure - for (int i = sid - 1; i >= 0; i--) - { - if (directoryEntries[i].StorageCLSID != g && directoryEntries[i].StgType == StgType.StgStorage) - { - return directoryEntries[i].StorageCLSID; - } - } - - return g; - } - - private static int Ceiling(double d) - { - return (int)Math.Ceiling(d); - } - - private static int LowSaturation(int i) - { - return i > 0 ? i : 0; - } - - - internal void FreeAssociatedData(int sid) - { - // Clear the associated stream (or ministream) if required - if (directoryEntries[sid].Size > 0) //thanks to Mark Bosold for this ! - { - if (directoryEntries[sid].Size < header.MinSizeStandardStream) - { - List miniChain - = GetSectorChain(directoryEntries[sid].StartSect, SectorType.Mini); - FreeMiniChain(miniChain); - } - else - { - List chain - = GetSectorChain(directoryEntries[sid].StartSect, SectorType.Normal); - FreeChain(chain); - } - } - } - - /// - /// Close the Compound File object CompoundFile and - /// free all associated resources (e.g. open file handle and allocated memory). - /// - /// When the Close method is called, - /// all the associated stream and storage objects are invalidated: - /// any operation invoked on them will produce a CFDisposedException. - /// - /// - /// - /// - /// const String FILENAME = "CompoundFile.cfs"; - /// CompoundFile cf = new CompoundFile(FILENAME); - /// - /// CFStorage st = cf.RootStorage.GetStorage("MyStorage"); - /// cf.Close(); - /// - /// try - /// { - /// byte[] temp = st.GetStream("MyStream").GetData(); - /// - /// // The following line will fail because back-end object has been closed - /// Assert.Fail("Stream without media"); - /// } - /// catch (Exception ex) - /// { - /// Assert.IsTrue(ex is CFDisposedException); - /// } - /// - /// - public void Close() => CloseCore(true); - - private bool closeStream = true; - - [Obsolete("Use flag LeaveOpen in CompoundFile constructor")] - public void Close(bool closeStream) => CloseCore(closeStream); - - private void CloseCore(bool closeStream) - { - this.closeStream = closeStream; - ((IDisposable)this).Dispose(); - } - - #region IDisposable Members - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - private readonly object lockObject = new(); - - /// - /// When called from user code, release all resources, otherwise, in the case runtime called it, - /// only unmanaged resources are released. - /// - /// If true, method has been called from User code, if false it's been called from .net runtime - protected virtual void Dispose(bool disposing) - { - try - { - if (!IsClosed) - { - lock (lockObject) - { - if (disposing) - { - // Call from user code... - - if (sectors != null) - { - sectors.Clear(); - sectors = null; - } - - RootStorage = null; // Some problem releasing resources... - header = null; - directoryEntries.Clear(); - directoryEntries = null; - //this.lockObject = null; -#if !FLAT_WRITE - this.buffer = null; -#endif - } - - if (sourceStream != null && closeStream && !Configuration.HasFlag(CFSConfiguration.LeaveOpen)) - sourceStream.Close(); - } - } - } - finally - { - IsClosed = true; - } - } - - internal bool IsClosed { get; private set; } - - private List directoryEntries - = new(); - - internal IList Directories => directoryEntries; - - //internal List DirectoryEntries - //{ - // get { return directoryEntries; } - //} - - internal IDirectoryEntry RootEntry => directoryEntries[0]; - - private List FindDirectoryEntries(string entryName) - { - List result = new List(); - - foreach (IDirectoryEntry d in directoryEntries) - { - if (d.StgType != StgType.StgInvalid && d.GetEntryName() == entryName) - result.Add(d); - } - - return result; - } - - /// - /// Get a list of all entries with a given name contained in the document. - /// - /// Name of entries to retrieve - /// A list of name-matching entries - /// This function is aimed to speed up entity lookup in - /// flat-structure files (only one or little more known entries) - /// without the performance penalty related to entities hierarchy constraints. - /// There is no implied hierarchy in the returned list. - /// - public IList GetAllNamedEntries(string entryName) - { - IList r = FindDirectoryEntries(entryName); - List result = new List(); - - foreach (IDirectoryEntry id in r) - { - if (id.StgType != StgType.StgInvalid && id.GetEntryName() == entryName) - { - CFItem i = id.StgType == StgType.StgStorage ? new CFStorage(this, id) : new CFStream(this, id); - result.Add(i); - } - } - - return result; - } - - public int GetNumDirectories() - { - if (IsClosed) - throw new CFDisposedException("Compound File closed: cannot access data"); - return directoryEntries.Count; - } - - public string GetNameDirEntry(int id) - { - if (IsClosed) - throw new CFDisposedException("Compound File closed: cannot access data"); - if (id < 0) - throw new CFException("Invalid Storage ID"); - return directoryEntries[id].Name; - } - - public StgType GetStorageType(int id) - { - if (IsClosed) - throw new CFDisposedException("Compound File closed: cannot access data"); - if (id < 0) - throw new CFException("Invalid Storage ID"); - return directoryEntries[id].StgType; - } - - /// - /// Compress free space by removing unallocated sectors from compound file - /// effectively reducing stream or file size. - /// - /// - /// Current implementation supports compression only for ver. 3 compound files. - /// - /// - /// - /// - /// //This code has been extracted from unit test - /// - /// String FILENAME = "MultipleStorage3.cfs"; - /// - /// FileInfo srcFile = new FileInfo(FILENAME); - /// - /// File.Copy(FILENAME, "MultipleStorage_Deleted_Compress.cfs", true); - /// - /// CompoundFile cf = new CompoundFile("MultipleStorage_Deleted_Compress.cfs", UpdateMode.Update, true, true); - /// - /// CFStorage st = cf.RootStorage.GetStorage("MyStorage"); - /// st = st.GetStorage("AnotherStorage"); - /// - /// Assert.IsNotNull(st); - /// st.Delete("Another2Stream"); //17Kb - /// cf.Commit(); - /// cf.Close(); - /// - /// CompoundFile.ShrinkCompoundFile("MultipleStorage_Deleted_Compress.cfs"); - /// - /// FileInfo dstFile = new FileInfo("MultipleStorage_Deleted_Compress.cfs"); - /// - /// Assert.IsTrue(srcFile.Length > dstFile.Length); - /// - /// - /// - public static void ShrinkCompoundFile(Stream s) - { - using CompoundFile cf = new(s, CFSUpdateMode.ReadOnly, CFSConfiguration.LeaveOpen); - if (cf.header.MajorVersion != (ushort)CFSVersion.Ver_3) - throw new CFException("Current implementation of free space compression does not support version 4 of Compound File Format"); - - using MemoryStream tmpMS = new((int)cf.sourceStream.Length); // This could be a problem for v4 - - using (CompoundFile tempCF = new((CFSVersion)cf.header.MajorVersion, cf.Configuration)) - { - tempCF.RootStorage.CLSID = cf.RootStorage.CLSID; - DoCompression(cf.RootStorage, tempCF.RootStorage); - tempCF.Save(tmpMS); - } - - // If we were based on a writable stream, we update - // the stream and do reload from the compressed one... - s.Seek(0, SeekOrigin.Begin); - tmpMS.WriteTo(s); - - s.Seek(0, SeekOrigin.Begin); - s.SetLength(tmpMS.Length); - } - - /// - /// Remove unallocated sectors from compound file in order to reduce its size. - /// - /// - /// Current implementation supports compression only for ver. 3 compound files. - /// - /// - /// - /// - /// //This code has been extracted from unit test - /// - /// String FILENAME = "MultipleStorage3.cfs"; - /// - /// FileInfo srcFile = new FileInfo(FILENAME); - /// - /// File.Copy(FILENAME, "MultipleStorage_Deleted_Compress.cfs", true); - /// - /// CompoundFile cf = new CompoundFile("MultipleStorage_Deleted_Compress.cfs", UpdateMode.Update, true, true); - /// - /// CFStorage st = cf.RootStorage.GetStorage("MyStorage"); - /// st = st.GetStorage("AnotherStorage"); - /// - /// Assert.IsNotNull(st); - /// st.Delete("Another2Stream"); //17Kb - /// cf.Commit(); - /// cf.Close(); - /// - /// CompoundFile.ShrinkCompoundFile("MultipleStorage_Deleted_Compress.cfs"); - /// - /// FileInfo dstFile = new FileInfo("MultipleStorage_Deleted_Compress.cfs"); - /// - /// Assert.IsTrue(srcFile.Length > dstFile.Length); - /// - /// - /// - public static void ShrinkCompoundFile(string fileName) - { - using FileStream fs = new(fileName, FileMode.Open, FileAccess.ReadWrite); - ShrinkCompoundFile(fs); - } - - /// - /// Recursively clones valid structures, avoiding to copy free sectors. - /// - /// Current source storage to clone - /// Current cloned destination storage - private static void DoCompression(CFStorage currSrcStorage, CFStorage currDstStorage) - { - void va(CFItem item) - { - if (item.IsStream) - { - CFStream itemAsStream = item as CFStream; - CFStream st = currDstStorage.AddStream(itemAsStream.Name); - st.SetData(itemAsStream.GetData()); - } - else if (item.IsStorage) - { - CFStorage itemAsStorage = item as CFStorage; - CFStorage strg = currDstStorage.AddStorage(itemAsStorage.Name); - strg.CLSID = itemAsStorage.CLSID; - DoCompression(itemAsStorage, strg); // recursion, one level deeper - } - } - - currSrcStorage.VisitEntries(va, false); - } - } -} diff --git a/sources/OpenMcdf/DirectoryEntry.cs b/sources/OpenMcdf/DirectoryEntry.cs deleted file mode 100644 index 3cea8928..00000000 --- a/sources/OpenMcdf/DirectoryEntry.cs +++ /dev/null @@ -1,445 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The Original Code is OpenMCDF - Compound Document Format library. - * - * The Initial Developer of the Original Code is Federico Blaseotto.*/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace OpenMcdf -{ - public enum StgType : int - { - StgInvalid = 0, - StgStorage = 1, - StgStream = 2, - StgLockbytes = 3, - StgProperty = 4, - StgRoot = 5 - } - - public enum StgColor : int - { - Red = 0, - Black = 1 - } - - internal sealed class DirectoryEntry : IDirectoryEntry - { - internal const int THIS_IS_GREATER = 1; - internal const int OTHER_IS_GREATER = -1; - internal const int NOSTREAM = unchecked((int)0xFFFFFFFF); - internal const int ZERO = 0; - internal const int EntryNameLength = 64; - - private readonly IList dirRepository; - - public int SID { get; set; } = -1; - - private DirectoryEntry(string name, StgType stgType, IList dirRepository) - { - this.dirRepository = dirRepository; - - StgType = stgType; - - if (stgType == StgType.StgStorage) - { - CreationDate = BitConverter.GetBytes(DateTime.Now.ToFileTimeUtc()); - StartSect = ZERO; - } - - if (stgType == StgType.StgInvalid) - { - StartSect = ZERO; - } - - if (name != string.Empty) - { - SetEntryName(name); - } - } - - public byte[] EntryName { get; private set; } = new byte[EntryNameLength]; - - public string GetEntryName() - { - if (EntryName != null && EntryName.Length > 0) - { - return Encoding.Unicode.GetString(EntryName).Remove((nameLength - 1) / 2); - } - else - return string.Empty; - } - - public void SetEntryName(string entryName) - { - if (entryName == string.Empty) - { - Array.Clear(EntryName, 0, EntryName.Length); - nameLength = 0; - } - else - { - if ( - entryName.Contains(@"\") || - entryName.Contains(@"/") || - entryName.Contains(@":") || - entryName.Contains(@"!")) - throw new CFException("Invalid character in entry: the characters '\\', '/', ':','!' cannot be used in entry name"); - - if (Encoding.Unicode.GetByteCount(entryName) + 2 > EntryNameLength) - throw new CFException($"Encoded entry name exceeds maximum length of ({EntryNameLength} bytes)"); - - Array.Clear(EntryName, 0, EntryName.Length); - int localNameLength = Encoding.Unicode.GetBytes(entryName, 0, entryName.Length, EntryName, 0); - nameLength = (ushort)(localNameLength + 2); - } - } - - private ushort nameLength; - - public ushort NameLength - { - get => nameLength; - set => throw new NotImplementedException(); - } - - public StgType StgType { get; set; } = StgType.StgInvalid; - - public StgColor StgColor { get; set; } = StgColor.Red; - - public int LeftSibling { get; set; } = NOSTREAM; - - public int RightSibling { get; set; } = NOSTREAM; - - public int Child { get; set; } = NOSTREAM; - - private Guid storageCLSID - = Guid.Empty; - - public Guid StorageCLSID - { - get => storageCLSID; - set => storageCLSID = value; - } - - public int StateBits { get; set; } - - public byte[] CreationDate { get; set; } = new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - - public byte[] ModifyDate { get; set; } = new byte[8] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - - public int StartSect { get; set; } = Sector.ENDOFCHAIN; - - public long Size { get; set; } - - public int CompareTo(object obj) - { - if (obj is not IDirectoryEntry otherDir) - throw new CFException("Invalid casting: compared object does not implement IDirectorEntry interface"); - - if (NameLength > otherDir.NameLength) - { - return THIS_IS_GREATER; - } - else if (NameLength < otherDir.NameLength) - { - return OTHER_IS_GREATER; - } - else - { - string thisName = Encoding.Unicode.GetString(EntryName, 0, NameLength); - string otherName = Encoding.Unicode.GetString(otherDir.EntryName, 0, otherDir.NameLength); - - for (int z = 0; z < thisName.Length; z++) - { - char thisChar = char.ToUpperInvariant(thisName[z]); - char otherChar = char.ToUpperInvariant(otherName[z]); - - if (thisChar > otherChar) - return THIS_IS_GREATER; - else if (thisChar < otherChar) - return OTHER_IS_GREATER; - } - - return 0; - } - - // return String.Compare(Encoding.Unicode.GetString(this.EntryName).ToUpper(), Encoding.Unicode.GetString(other.EntryName).ToUpper()); - } - - public override bool Equals(object obj) - { - return CompareTo(obj) == 0; - } - - /// - /// FNV hash, short for Fowler/Noll/Vo - /// - /// - /// (not warranted) unique hash for byte array - private static ulong fnv_hash(byte[] buffer) - { - ulong h = 2166136261; - int i; - - for (i = 0; i < buffer.Length; i++) - h = (h * 16777619) ^ buffer[i]; - - return h; - } - - public override int GetHashCode() - { - return (int)fnv_hash(EntryName); - } - - public void Write(StreamRW streamRW) - { - streamRW.Write(EntryName); - streamRW.Write(nameLength); - streamRW.Write((byte)StgType); - streamRW.Write((byte)StgColor); - streamRW.Write(LeftSibling); - streamRW.Write(RightSibling); - streamRW.Write(Child); - streamRW.Write(storageCLSID); - streamRW.Write(StateBits); - streamRW.Write(CreationDate); - streamRW.Write(ModifyDate); - streamRW.Write(StartSect); - streamRW.Write(Size); - } - - //public Byte[] ToByteArray() - //{ - // MemoryStream ms - // = new MemoryStream(128); - - // BinaryWriter bw = new BinaryWriter(ms); - - // byte[] paddedName = new byte[64]; - // Array.Copy(entryName, paddedName, entryName.Length); - - // bw.Write(paddedName); - // bw.Write(nameLength); - // bw.Write((byte)stgType); - // bw.Write((byte)stgColor); - // bw.Write(leftSibling); - // bw.Write(rightSibling); - // bw.Write(child); - // bw.Write(storageCLSID.ToByteArray()); - // bw.Write(stateBits); - // bw.Write(creationDate); - // bw.Write(modifyDate); - // bw.Write(startSetc); - // bw.Write(size); - - // return ms.ToArray(); - //} - - public void Read(StreamRW streamRW, CFSVersion ver = CFSVersion.Ver_3) - { - streamRW.ReadBytes(EntryName); - nameLength = streamRW.ReadUInt16(); - StgType = (StgType)streamRW.ReadByte(); - //rw.ReadByte();//Ignore color, only black tree - StgColor = (StgColor)streamRW.ReadByte(); - LeftSibling = streamRW.ReadInt32(); - RightSibling = streamRW.ReadInt32(); - Child = streamRW.ReadInt32(); - - // Thanks to bugaccount (BugTrack id 3519554) - if (StgType == StgType.StgInvalid) - { - LeftSibling = NOSTREAM; - RightSibling = NOSTREAM; - Child = NOSTREAM; - } - - storageCLSID = streamRW.ReadGuid(); - StateBits = streamRW.ReadInt32(); - streamRW.ReadBytes(CreationDate); - streamRW.ReadBytes(ModifyDate); - StartSect = streamRW.ReadInt32(); - - if (ver == CFSVersion.Ver_3) - { - // avoid dirty read for version 3 files (max size: 32bit integer) - // where most significant bits are not initialized to zero - - Size = streamRW.ReadInt32(); - streamRW.Seek(4, SeekOrigin.Current); // discard most significant 4 (possibly) dirty bytes - } - else - { - Size = streamRW.ReadInt64(); - } - } - - public string Name => GetEntryName(); - - public RedBlackTree.IRBNode Left - { - get - { - if (LeftSibling == NOSTREAM) - return null; - - return dirRepository[LeftSibling]; - } - set - { - LeftSibling = value != null ? ((IDirectoryEntry)value).SID : NOSTREAM; - - if (LeftSibling != NOSTREAM) - dirRepository[LeftSibling].Parent = this; - } - } - - public RedBlackTree.IRBNode Right - { - get - { - if (RightSibling == NOSTREAM) - return null; - - return dirRepository[RightSibling]; - } - set - { - RightSibling = value != null ? ((IDirectoryEntry)value).SID : NOSTREAM; - - if (RightSibling != NOSTREAM) - dirRepository[RightSibling].Parent = this; - } - } - - public RedBlackTree.Color Color - { - get => (RedBlackTree.Color)StgColor; - set => StgColor = (StgColor)value; - } - - private IDirectoryEntry parent; - - public RedBlackTree.IRBNode Parent - { - get => parent; - set => parent = value as IDirectoryEntry; - } - - public RedBlackTree.IRBNode Grandparent() - { - return parent != null ? parent.Parent : null; - } - - public RedBlackTree.IRBNode Sibling() - { - if (this == Parent.Left) - return Parent.Right; - else - return Parent.Left; - } - - public RedBlackTree.IRBNode Uncle() - { - return parent != null ? Parent.Sibling() : null; - } - - internal static IDirectoryEntry New(string name, StgType stgType, IList dirRepository) - { - DirectoryEntry de; - if (dirRepository != null) - { - de = new DirectoryEntry(name, stgType, dirRepository); - // No invalid directory entry found - dirRepository.Add(de); - de.SID = dirRepository.Count - 1; - } - else - throw new ArgumentNullException(nameof(dirRepository), "Directory repository cannot be null in New() method"); - - return de; - } - - internal static IDirectoryEntry Mock(string name, StgType stgType) - { - DirectoryEntry de = new DirectoryEntry(name, stgType, null); - - return de; - } - - internal static IDirectoryEntry TryNew(string name, StgType stgType, IList dirRepository) - { - DirectoryEntry de = new DirectoryEntry(name, stgType, dirRepository); - - // If we are not adding an invalid dirEntry as - // in a normal loading from file (invalid dirs MAY pad a sector) - if (de != null) - { - // Find first available invalid slot (if any) to reuse it - for (int i = 0; i < dirRepository.Count; i++) - { - if (dirRepository[i].StgType == StgType.StgInvalid) - { - dirRepository[i] = de; - de.SID = i; - return de; - } - } - } - - // No invalid directory entry found - dirRepository.Add(de); - de.SID = dirRepository.Count - 1; - - return de; - } - - public override string ToString() - { - return Name + " [" + SID + "]" + (StgType == StgType.StgStream ? "Stream" : "Storage"); - } - - public void AssignValueTo(RedBlackTree.IRBNode other) - { - DirectoryEntry d = other as DirectoryEntry; - d.SetEntryName(GetEntryName()); - CreationDate.CopyTo(d.CreationDate, 0); - ModifyDate.CopyTo(d.ModifyDate, 0); - d.Size = Size; - d.StartSect = StartSect; - d.StateBits = StateBits; - d.StgType = StgType; - d.storageCLSID = storageCLSID; - d.Child = Child; - } - - /// - /// Reset a directory entry setting it to StgInvalid in the Directory. - /// - public void Reset() - { - // TODO: Delete IDirectoryEntry interface and use as DirectoryEntry - // member instead for improved performance from devirtualization - SetEntryName(string.Empty); - Left = null; - Right = null; - Parent = null; - StgType = StgType.StgInvalid; - StartSect = ZERO; - StorageCLSID = Guid.Empty; - Size = 0; - StateBits = 0; - StgColor = StgColor.Red; - Array.Clear(CreationDate, 0, CreationDate.Length); - Array.Clear(ModifyDate, 0, ModifyDate.Length); - } - } -} diff --git a/sources/OpenMcdf/Header.cs b/sources/OpenMcdf/Header.cs deleted file mode 100644 index a67dd655..00000000 --- a/sources/OpenMcdf/Header.cs +++ /dev/null @@ -1,195 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The Original Code is OpenMCDF - Compound Document Format library. - * - * The Initial Developer of the Original Code is Federico Blaseotto.*/ - -using System.IO; - -namespace OpenMcdf -{ - internal sealed class Header - { - static readonly byte[] ZeroHead = new byte[3584]; - - public byte[] HeaderSignature { get; private set; } = new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; - - public byte[] CLSID { get; private set; } = new byte[16]; - - public ushort MinorVersion { get; private set; } = 0x003E; - - public ushort MajorVersion { get; private set; } = 0x0003; - - public ushort ByteOrder { get; private set; } = 0xFFFE; - - public ushort SectorShift { get; private set; } = 9; - - public ushort MiniSectorShift { get; private set; } = 6; - - public byte[] Unused { get; private set; } = new byte[6]; - - public int DirectorySectorsNumber { get; set; } - - public int FATSectorsNumber { get; set; } - - public int FirstDirectorySectorID { get; set; } = Sector.ENDOFCHAIN; - - public uint Unused2 { get; private set; } - - public uint MinSizeStandardStream { get; set; } = 4096; - - /// - /// This integer field contains the starting sector number for the mini FAT - /// - public int FirstMiniFATSectorID { get; set; } = unchecked((int)0xFFFFFFFE); - - public uint MiniFATSectorsNumber { get; set; } - - public int FirstDIFATSectorID { get; set; } = Sector.ENDOFCHAIN; - - public uint DIFATSectorsNumber { get; set; } - - public int[] DIFAT { get; } = new int[109]; - - public Header() - : this(3) - { - } - - public Header(ushort version) - { - switch (version) - { - case 3: - MajorVersion = 3; - SectorShift = 0x0009; - break; - - case 4: - MajorVersion = 4; - SectorShift = 0x000C; - break; - - default: - throw new CFException("Invalid Compound File Format version"); - } - - for (int i = 0; i < 109; i++) - { - DIFAT[i] = Sector.FREESECT; - } - } - - public void Write(Stream stream) - { - StreamRW rw = new StreamRW(stream); - - rw.Write(HeaderSignature); - rw.Write(CLSID); - rw.Write(MinorVersion); - rw.Write(MajorVersion); - rw.Write(ByteOrder); - rw.Write(SectorShift); - rw.Write(MiniSectorShift); - rw.Write(Unused); - rw.Write(DirectorySectorsNumber); - rw.Write(FATSectorsNumber); - rw.Write(FirstDirectorySectorID); - rw.Write(Unused2); - rw.Write(MinSizeStandardStream); - rw.Write(FirstMiniFATSectorID); - rw.Write(MiniFATSectorsNumber); - rw.Write(FirstDIFATSectorID); - rw.Write(DIFATSectorsNumber); - - foreach (int i in DIFAT) - { - rw.Write(i); - } - - if (MajorVersion == 4) - { - rw.Write(ZeroHead); - } - } - - public void Read(Stream stream) - { - StreamRW rw = new StreamRW(stream); - - rw.ReadBytes(HeaderSignature); - CheckSignature(); - rw.ReadBytes(CLSID); - MinorVersion = rw.ReadUInt16(); - MajorVersion = rw.ReadUInt16(); - CheckVersion(); - ByteOrder = rw.ReadUInt16(); - SectorShift = rw.ReadUInt16(); - MiniSectorShift = rw.ReadUInt16(); - rw.ReadBytes(Unused); - DirectorySectorsNumber = rw.ReadInt32(); - FATSectorsNumber = rw.ReadInt32(); - FirstDirectorySectorID = rw.ReadInt32(); - Unused2 = rw.ReadUInt32(); - MinSizeStandardStream = rw.ReadUInt32(); - FirstMiniFATSectorID = rw.ReadInt32(); - MiniFATSectorsNumber = rw.ReadUInt32(); - FirstDIFATSectorID = rw.ReadInt32(); - DIFATSectorsNumber = rw.ReadUInt32(); - - for (int i = 0; i < DIFAT.Length; i++) - { - DIFAT[i] = rw.ReadInt32(); - } - } - - private void CheckVersion() - { - if (MajorVersion is not 3 and not 4) - throw new CFFileFormatException("Unsupported Binary File Format version: OpenMcdf only supports Compound Files with major version equal to 3 or 4 "); - } - - /// - /// Structured Storage signature - /// - private static readonly byte[] OLE_CFS_SIGNATURE = new byte[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }; - - private void CheckSignature() - { - for (int i = 0; i < HeaderSignature.Length; i++) - { - if (HeaderSignature[i] != OLE_CFS_SIGNATURE[i]) - throw new CFFileFormatException("Invalid OLE structured storage file"); - } - } - - /// - /// Validate header values specified in [MS-CFB] document - /// - /// If one of the validation checks fails a CFCorruptedFileException exception will be thrown - internal void ThrowIfInvalid() - { - if (MiniSectorShift != 6) - { - throw new CFCorruptedFileException("Mini sector Shift MUST be 0x06"); - } - - if ((MajorVersion == 0x0003 && SectorShift != 9) || (MajorVersion == 0x0004 && SectorShift != 0x000c)) - { - throw new CFCorruptedFileException("Sector Shift MUST be 0x0009 for Major Version 3 and 0x000c for Major Version 4"); - } - - if (MinSizeStandardStream != 4096) - { - throw new CFCorruptedFileException("Mini Stream Cut off size MUST be 4096 byte"); - } - - if (ByteOrder != 0xFFFE) - { - throw new CFCorruptedFileException("Byte order MUST be little endian (0xFFFE)"); - } - } - } -} diff --git a/sources/OpenMcdf/IDirectoryEntry.cs b/sources/OpenMcdf/IDirectoryEntry.cs deleted file mode 100644 index 3d843fc4..00000000 --- a/sources/OpenMcdf/IDirectoryEntry.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The Original Code is OpenMCDF - Compound Document Format library. - * - * The Initial Developer of the Original Code is Federico Blaseotto.*/ - -using RedBlackTree; -using System; - -namespace OpenMcdf -{ - internal interface IDirectoryEntry : IComparable, IRBNode - { - int Child { get; set; } - byte[] CreationDate { get; set; } - byte[] EntryName { get; } - string GetEntryName(); - int LeftSibling { get; set; } - byte[] ModifyDate { get; set; } - string Name { get; } - ushort NameLength { get; set; } - void Read(StreamRW streamRW, CFSVersion ver = CFSVersion.Ver_3); - int RightSibling { get; set; } - void SetEntryName(string entryName); - int SID { get; set; } - long Size { get; set; } - int StartSect { get; set; } - int StateBits { get; set; } - StgColor StgColor { get; set; } - StgType StgType { get; set; } - Guid StorageCLSID { get; set; } - void Write(StreamRW stream); - void Reset(); - } -} diff --git a/sources/OpenMcdf/OpenMcdf.csproj b/sources/OpenMcdf/OpenMcdf.csproj deleted file mode 100644 index ab456d2b..00000000 --- a/sources/OpenMcdf/OpenMcdf.csproj +++ /dev/null @@ -1,115 +0,0 @@ - - - netstandard2.0;net40 - Debug;Release - true - - true - - true - true - snupkg - true - OpenMcdf.snk - false - false - false - false - false - false - false - - - true - portable - false - ..\..\bin\Debug\OpenMcdf\ - DEBUG;TRACE - prompt - 4 - ..\..\bin\Debug\OpenMcdf\OpenMcdf.xml - 1591,1592,1573,1571,1570,1572 - AllRules.ruleset - - - portable - true - ..\..\bin\Release\OpenMcdf\ - TRACE - prompt - 4 - ..\..\bin\Release\OpenMcdf\OpenMcdf.xml - 1591,1592,1573,1571,1570,1572 - AllRules.ruleset - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {56E15D4A-8A37-4C7C-BB44-FD59AFF220C1} - Library - Properties - OpenMcdf - OpenMcdf - - 512 - - 3.5 - - http://localhost/OpenMcdf/ - true - Web - true - Foreground - 7 - Days - false - false - true - 0 - 2.2.1.9 - true - false - true - - true - OpenMcdf.snk - - - true - 2.3.1.0 - ironfede - https://github.com/ironfede/openmcdf - - https://github.com/ironfede/openmcdf - Compound file, c#, structured storage - OpenMcdf is a 100% .net / C# component that allows developers to manipulate Microsoft Compound Document File Format for OLE structured storage. -It supports read/write operations on streams and storages and traversal of structures tree. - Copyright © 2010-2024, Federico Blaseotto - README.md - Bug fixing (high priority for #119, issue with stream resizing) and enhancements in extensions - OpenMcdf - icon.png - MPL-2.0 - true - - - true - - - - - - - True - \ - - - True - \ - - - - - \ No newline at end of file diff --git a/sources/OpenMcdf/OpenMcdf.snk b/sources/OpenMcdf/OpenMcdf.snk deleted file mode 100644 index b2990e73c74b1b002bd693d3d8008085b96f461f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097nwG6x-K;LO^j4j6cs`(%400Z5@yPQnZ7{$0%=kHi|-&%jj zIZo$C(^orbVlD(&H`PYI`!B&VSLuY?Q>Q)El2oao)5)5QriW~iFixpxnXCq=jeExP zwIDQlYjkbW{FAWhZ)4zsWDM5B0Ai$5KWtA3WU3fix^?x&19(Tr`ai`}{|vA3d;= zvaMECN!3mgvGE5t)=hH7N)+WzrP#-#HOb%u(+-Je%Npw`Uv^Y}ekuG=&$uUWso2C? z_`m=&4^8V#^8!SoI$f{Y;uM2Qdgc1+0wDpGERes_*oK=gF2S}Uz!p3<(}iN-sPo!x z - - - - - - - - - - - - - - - - - - - - - - - gAAEAAAQIBQIAgEUgEIBYgDg0GEAwUAAABAAAAGQgCA= - Header.cs - - - - - - - - - - - AAAAAAAAAAAAAEYAAAQEIBAAAAEABAAAAAACAAAAAAA= - CFStorage.cs - - - - - - AAAAAAACAAAAQAAgGkoAIABgAAAAAAQACAAAAAGIACA= - StreamView.cs - - - - - - QIQAwEAAkAAAgMQAgIDAgAQkEADAAKwgAAAsIAHEAAA= - DirectoryEntry.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - oTUGgNQVUmMEQBB0AARISKAAEAMEAmRARs0BQeeQFCg= - CompoundFile.cs - - - - - - - - - - - - - - - - - - - - - - - - - - QAAEAAAABAAAgAUggAAAgAQAAAAAAIAAAQACCAAAAiQ= - CFItem.cs - - - - - - - - - - AAAAAAABEAAAACAEAAAAAAAAAAAAAAAAAAAAAAAAAAA= - CFStream.cs - - - - - - AGIAAEAAAAAAABAQDAAABAQAAAQAAAQAGEAAggBSIAA= - SectorCollection.cs - - - - - - - - - - - QAAAQAAAEAAAgMAAAAAAgAQkAAAAACwAAAAAIAHEAAA= - IDirectoryEntry.cs - - - - \ No newline at end of file diff --git a/sources/OpenMcdf/Properties/AssemblyInfo.cs b/sources/OpenMcdf/Properties/AssemblyInfo.cs deleted file mode 100644 index 69e35248..00000000 --- a/sources/OpenMcdf/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("OpenMcdf")] -[assembly: AssemblyDescription("OpenMcdf is a 100% .net / C# component that allows developers to manipulate Microsoft Compound Document File Format for OLE structured storage. It supports read/write operations on streams and storages and traversal of structures tree.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Federico Blaseotto")] -[assembly: AssemblyProduct("OpenMcdf 2.3")] -[assembly: AssemblyCopyright("Copyright © 2010-2024, Federico Blaseotto")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("ffc13791-ddf0-4d14-bd64-575aba119190")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.3.1.0")] -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("OpenMcdf.Tests,PublicKey=002400000480000094000000060200000024000052534131000400000100010085b50cbc1e40df696f8c30eaafc59a01e22303cb038fc832289b2c393f908a65c9aaa0d28026a47c6e5f85cc236f0735bea17236dbaaf91fea0003ddc1bb9c4cd318c5b855e7ef5877df5a7fc8394ee747d3573b69622e045837d546befb2fc13257e984db53a73dd59254a9a1d3c99a8ca6876c91304ea96899ac06a88d7bc6")] -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("OpenMcdf.Test,PublicKey=002400000480000094000000060200000024000052534131000400000100010085b50cbc1e40df696f8c30eaafc59a01e22303cb038fc832289b2c393f908a65c9aaa0d28026a47c6e5f85cc236f0735bea17236dbaaf91fea0003ddc1bb9c4cd318c5b855e7ef5877df5a7fc8394ee747d3573b69622e045837d546befb2fc13257e984db53a73dd59254a9a1d3c99a8ca6876c91304ea96899ac06a88d7bc6")] -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("OpenMcdf.Extensions,PublicKey=002400000480000094000000060200000024000052534131000400000100010085b50cbc1e40df696f8c30eaafc59a01e22303cb038fc832289b2c393f908a65c9aaa0d28026a47c6e5f85cc236f0735bea17236dbaaf91fea0003ddc1bb9c4cd318c5b855e7ef5877df5a7fc8394ee747d3573b69622e045837d546befb2fc13257e984db53a73dd59254a9a1d3c99a8ca6876c91304ea96899ac06a88d7bc6")] \ No newline at end of file diff --git a/sources/OpenMcdf/RBTree/IRBNode.cs b/sources/OpenMcdf/RBTree/IRBNode.cs deleted file mode 100644 index 4e141674..00000000 --- a/sources/OpenMcdf/RBTree/IRBNode.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; - -namespace RedBlackTree -{ - public enum Color - { - RED = 0, - BLACK = 1 - } - - /// - /// Red Black Node interface - /// - // TODO: Make concrete class in v3 - public interface IRBNode : IComparable - { - IRBNode Left { get; set; } - - IRBNode Right { get; set; } - - Color Color { get; set; } - - IRBNode Parent { get; set; } - - IRBNode Grandparent(); - - IRBNode Sibling(); - - IRBNode Uncle(); - - void AssignValueTo(IRBNode other); - } -} diff --git a/sources/OpenMcdf/RBTree/RBTree.cs b/sources/OpenMcdf/RBTree/RBTree.cs deleted file mode 100644 index 340ac882..00000000 --- a/sources/OpenMcdf/RBTree/RBTree.cs +++ /dev/null @@ -1,438 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; - -// ------------------------------------------------------------- -// This is a porting from java code, under MIT license of | -// the beautiful Red-Black Tree implementation you can find at | -// http://en.literateprograms.org/Red-black_tree_(Java)#chunk | -// Many Thanks to original Implementors. | -// ------------------------------------------------------------- - -namespace RedBlackTree -{ - // TODO: Remove in v3 - public class RBTreeException : Exception - { - public RBTreeException(string msg) - : base(msg) - { - } - } - - // TODO: Use ArgumentException in v3 - public class RBTreeDuplicatedItemException : RBTreeException - { - public RBTreeDuplicatedItemException(string msg) - : base(msg) - { - } - } - - // TODO: Make internal and seal in v3 - public partial class RBTree : IEnumerable - { - private static Color NodeColor(IRBNode n) => n == null ? Color.BLACK : n.Color; - - public IRBNode Root { get; set; } - - public RBTree() - { - } - - public RBTree(IRBNode root) - { - Root = root; - } - - private IRBNode LookupNode(IRBNode template) - { - IRBNode n = Root; - - while (n != null) - { - int compResult = template.CompareTo(n); - if (compResult == 0) - return n; - n = compResult < 0 ? n.Left : n.Right; - } - - return n; - } - - public bool TryLookup(IRBNode template, out IRBNode val) - { - IRBNode n = LookupNode(template); - - switch (n) - { - case null: - val = null; - return false; - default: - val = n; - return true; - } - } - - private void ReplaceNode(IRBNode oldn, IRBNode newn) - { - if (oldn.Parent == null) - { - Root = newn; - } - else - { - if (oldn == oldn.Parent.Left) - oldn.Parent.Left = newn; - else - oldn.Parent.Right = newn; - } - - if (newn != null) - { - newn.Parent = oldn.Parent; - } - } - - private void RotateLeft(IRBNode n) - { - IRBNode r = n.Right; - ReplaceNode(n, r); - n.Right = r.Left; - if (r.Left != null) - { - r.Left.Parent = n; - } - - r.Left = n; - n.Parent = r; - } - - private void RotateRight(IRBNode n) - { - IRBNode l = n.Left; - ReplaceNode(n, l); - n.Left = l.Right; - - if (l.Right != null) - { - l.Right.Parent = n; - } - - l.Right = n; - n.Parent = l; - } - - public void Insert(IRBNode newNode) - { - newNode.Color = Color.RED; - IRBNode insertedNode = newNode; - - if (Root == null) - { - Root = insertedNode; - } - else - { - IRBNode n = Root; - while (true) - { - int compResult = newNode.CompareTo(n); - if (compResult == 0) - { - throw new RBTreeDuplicatedItemException("RBNode " + newNode.ToString() + " already present in tree"); - } - else if (compResult < 0) - { - if (n.Left == null) - { - n.Left = insertedNode; - - break; - } - else - { - n = n.Left; - } - } - else - { - if (n.Right == null) - { - n.Right = insertedNode; - - break; - } - else - { - n = n.Right; - } - } - } - - insertedNode.Parent = n; - } - - InsertCase1(insertedNode); - } - - private void InsertCase1(IRBNode n) - { - if (n.Parent == null) - n.Color = Color.BLACK; - else - InsertCase2(n); - } - - private void InsertCase2(IRBNode n) - { - if (NodeColor(n.Parent) == Color.BLACK) - return; // Tree is still valid - - InsertCase3(n); - } - - private void InsertCase3(IRBNode n) - { - if (NodeColor(n.Uncle()) == Color.RED) - { - n.Parent.Color = Color.BLACK; - n.Uncle().Color = Color.BLACK; - n.Grandparent().Color = Color.RED; - InsertCase1(n.Grandparent()); - } - else - { - InsertCase4(n); - } - } - - private void InsertCase4(IRBNode n) - { - if (n == n.Parent.Right && n.Parent == n.Grandparent().Left) - { - RotateLeft(n.Parent); - n = n.Left; - } - else if (n == n.Parent.Left && n.Parent == n.Grandparent().Right) - { - RotateRight(n.Parent); - n = n.Right; - } - - InsertCase5(n); - } - - private void InsertCase5(IRBNode n) - { - n.Parent.Color = Color.BLACK; - n.Grandparent().Color = Color.RED; - if (n == n.Parent.Left && n.Parent == n.Grandparent().Left) - { - RotateRight(n.Grandparent()); - } - else - { - //assert n == n.parent.right && n.parent == n.grandparent().right; - RotateLeft(n.Grandparent()); - } - } - - private static IRBNode MaximumNode(IRBNode n) - { - //assert n != null; - while (n.Right != null) - { - n = n.Right; - } - - return n; - } - - public void Delete(IRBNode template, out IRBNode deletedAlt) - { - deletedAlt = null; - IRBNode n = LookupNode(template); - if (n == null) - return; // Key not found, do nothing - - if (n.Left != null && n.Right != null) - { - // Copy key/value from predecessor and then delete it instead - IRBNode pred = MaximumNode(n.Left); - pred.AssignValueTo(n); - n = pred; - deletedAlt = pred; - } - - //assert n.left == null || n.right == null; - IRBNode child = n.Right ?? n.Left; - if (NodeColor(n) == Color.BLACK) - { - n.Color = NodeColor(child); - DeleteCase1(n); - } - - ReplaceNode(n, child); - - if (NodeColor(Root) == Color.RED) - { - Root.Color = Color.BLACK; - } - } - - private void DeleteCase1(IRBNode n) - { - if (n.Parent == null) - return; - - DeleteCase2(n); - } - - private void DeleteCase2(IRBNode n) - { - if (NodeColor(n.Sibling()) == Color.RED) - { - n.Parent.Color = Color.RED; - n.Sibling().Color = Color.BLACK; - if (n == n.Parent.Left) - RotateLeft(n.Parent); - else - RotateRight(n.Parent); - } - - DeleteCase3(n); - } - - private void DeleteCase3(IRBNode n) - { - if (NodeColor(n.Parent) == Color.BLACK && - NodeColor(n.Sibling()) == Color.BLACK && - NodeColor(n.Sibling().Left) == Color.BLACK && - NodeColor(n.Sibling().Right) == Color.BLACK) - { - n.Sibling().Color = Color.RED; - DeleteCase1(n.Parent); - } - else - { - DeleteCase4(n); - } - } - - private void DeleteCase4(IRBNode n) - { - if (NodeColor(n.Parent) == Color.RED && - NodeColor(n.Sibling()) == Color.BLACK && - NodeColor(n.Sibling().Left) == Color.BLACK && - NodeColor(n.Sibling().Right) == Color.BLACK) - { - n.Sibling().Color = Color.RED; - n.Parent.Color = Color.BLACK; - } - else - { - DeleteCase5(n); - } - } - - private void DeleteCase5(IRBNode n) - { - if (n == n.Parent.Left && - NodeColor(n.Sibling()) == Color.BLACK && - NodeColor(n.Sibling().Left) == Color.RED && - NodeColor(n.Sibling().Right) == Color.BLACK) - { - n.Sibling().Color = Color.RED; - n.Sibling().Left.Color = Color.BLACK; - RotateRight(n.Sibling()); - } - else if (n == n.Parent.Right && - NodeColor(n.Sibling()) == Color.BLACK && - NodeColor(n.Sibling().Right) == Color.RED && - NodeColor(n.Sibling().Left) == Color.BLACK) - { - n.Sibling().Color = Color.RED; - n.Sibling().Right.Color = Color.BLACK; - RotateLeft(n.Sibling()); - } - - DeleteCase6(n); - } - - private void DeleteCase6(IRBNode n) - { - n.Sibling().Color = NodeColor(n.Parent); - n.Parent.Color = Color.BLACK; - if (n == n.Parent.Left) - { - //assert nodeColor(n.sibling().right) == Color.RED; - n.Sibling().Right.Color = Color.BLACK; - RotateLeft(n.Parent); - } - else - { - //assert nodeColor(n.sibling().left) == Color.RED; - n.Sibling().Left.Color = Color.BLACK; - RotateRight(n.Parent); - } - } - - public void VisitTree(Action action) - { - if (action is null) - throw new ArgumentNullException(nameof(action)); - - // IN Order visit - if (Root != null) - VisitNode(action, Root); - } - - private static void VisitNode(Action action, IRBNode node) - { - if (node.Left != null) - VisitNode(action, node.Left); - - action.Invoke(node); - - if (node.Right != null) - VisitNode(action, node.Right); - } - - public IEnumerator GetEnumerator() => new RBTreeEnumerator(this); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - public void Print() - { - if (Root is null) - Trace.WriteLine(""); - else - Print(Root); - } - - private static void Print(IRBNode n) - { - if (n.Left != null) - { - Trace.Indent(); - Print(n.Left); - Trace.Unindent(); - } - - if (n.Color == Color.BLACK) - Trace.WriteLine($" {n} "); - else - Trace.WriteLine($"<{n}>"); - - if (n.Right != null) - { - Trace.Indent(); - Print(n.Right); - Trace.Unindent(); - } - } - } -} diff --git a/sources/OpenMcdf/RBTree/RBTreeEnumerator.cs b/sources/OpenMcdf/RBTree/RBTreeEnumerator.cs deleted file mode 100644 index 478757d6..00000000 --- a/sources/OpenMcdf/RBTree/RBTreeEnumerator.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections; -using System.Collections.Generic; - -namespace RedBlackTree -{ - public partial class RBTree - { - // TODO: Make internal in v3 (can seal in v2 since constructor is internal) - public sealed class RBTreeEnumerator : IEnumerator - { - private readonly IRBNode root; - private readonly Stack stack = new(); - - internal RBTreeEnumerator(RBTree tree) - { - root = tree.Root; - PushLeft(root); - } - - public void Dispose() - { - } - - public IRBNode Current { get; private set; } - - object IEnumerator.Current => Current; - - public bool MoveNext() - { - if (stack.Count == 0) - return false; - - Current = stack.Pop(); - PushLeft(Current.Right); - return true; - } - - public void Reset() - { - Current = null; - stack.Clear(); - PushLeft(root); - } - - private void PushLeft(IRBNode node) - { - while (node is not null) - { - stack.Push(node); - node = node.Left; - } - } - } - } -} diff --git a/sources/OpenMcdf/Sector.cs b/sources/OpenMcdf/Sector.cs deleted file mode 100644 index 838fa79d..00000000 --- a/sources/OpenMcdf/Sector.cs +++ /dev/null @@ -1,124 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The Original Code is OpenMCDF - Compound Document Format library. - * - * The Initial Developer of the Original Code is Federico Blaseotto.*/ - -using System; -using System.IO; - -namespace OpenMcdf -{ - internal enum SectorType - { - Normal, - Mini, - FAT, - DIFAT, - RangeLockSector, - Directory - } - - internal sealed class Sector - { - public const int MINISECTOR_SIZE = 64; - - public const int FREESECT = unchecked((int)0xFFFFFFFF); - public const int ENDOFCHAIN = unchecked((int)0xFFFFFFFE); - public const int FATSECT = unchecked((int)0xFFFFFFFD); - public const int DIFSECT = unchecked((int)0xFFFFFFFC); - - public bool DirtyFlag { get; set; } - - public bool IsStreamed => (stream != null && Size != MINISECTOR_SIZE) && (Id * Size) + Size < stream.Length; - - private readonly Stream stream; - - public Sector(int size, Stream stream) - { - Size = size; - this.stream = stream; - } - - public Sector(int size) - { - Size = size; - data = null; - stream = null; - } - - internal SectorType Type { get; set; } - - public int Id { get; set; } = -1; - - public int Size { get; private set; } - - private byte[] data; - - public byte[] GetData() - { - if (data == null) - { - data = new byte[Size]; - - if (IsStreamed) - { - long position = Size + Id * (long)Size; - // Enlarge the stream if necessary and possible - long endPosition = position + Size; - if (endPosition > stream.Length) - { - if (!stream.CanWrite) - return data; - stream.SetLength(endPosition); - } - stream.Seek(position, SeekOrigin.Begin); - stream.ReadExactly(data, 0, Size); - } - } - - return data; - } - - //public void SetSectorData(byte[] b) - //{ - // this.data = b; - //} - - //public void FillData(byte b) - //{ - // if (data != null) - // { - // for (int i = 0; i < data.Length; i++) - // { - // data[i] = b; - // } - // } - //} - - public void ZeroData() - { - if (data is null) - data = new byte[Size]; - else - Array.Clear(data, 0, data.Length); - DirtyFlag = true; - } - - public void InitFATData() - { - data ??= new byte[Size]; - for (int i = 0; i < Size; i++) - data[i] = 0xFF; - - DirtyFlag = true; - } - - internal void ReleaseData() - { - data = null; - } - } -} diff --git a/sources/OpenMcdf/SectorCollection.cs b/sources/OpenMcdf/SectorCollection.cs deleted file mode 100644 index a1f3e2f9..00000000 --- a/sources/OpenMcdf/SectorCollection.cs +++ /dev/null @@ -1,161 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The Original Code is OpenMCDF - Compound Document Format library. - * - * The Initial Developer of the Original Code is Federico Blaseotto.*/ - -using System; -using System.Collections; -using System.Collections.Generic; - -namespace OpenMcdf -{ - /// - /// Action to implement when transaction support - sector - /// has to be written to the underlying stream (see specs). - /// - public delegate void Ver3SizeLimitReached(); - - /// - /// Ad-hoc Heap Friendly sector collection to avoid using - /// large array that may create some problem to GC collection - /// (see http://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/ ) - /// - internal sealed class SectorCollection : IList - { - private const int MAX_SECTOR_V4_COUNT_LOCK_RANGE = 0x7FFFFF00; - private const int SLICE_SIZE = 4096; - - public event Ver3SizeLimitReached OnVer3SizeLimitReached; - - private readonly List> largeArraySlices = new(); - private bool sizeLimitReached; - - public SectorCollection() - { - } - - private void DoCheckSizeLimitReached() - { - if (!sizeLimitReached && (Count - 1 > MAX_SECTOR_V4_COUNT_LOCK_RANGE)) - { - sizeLimitReached = true; - OnVer3SizeLimitReached?.Invoke(); - } - } - - #region IList Members - - public int IndexOf(Sector item) - { - throw new NotImplementedException(); - } - - public void Insert(int index, Sector item) - { - throw new NotImplementedException(); - } - - public void RemoveAt(int index) - { - throw new NotImplementedException(); - } - - public Sector this[int index] - { - get - { - if (index <= -1 || index >= Count) - throw new CFException("Argument Out of Range, possibly corrupted file", new ArgumentOutOfRangeException(nameof(index), index, "Argument out of range")); - - int itemIndex = Math.DivRem(index, SLICE_SIZE, out int itemOffset); - return largeArraySlices[itemIndex][itemOffset]; - } - - set - { - if (index <= -1 || index >= Count) - throw new ArgumentOutOfRangeException(nameof(index), index, "Argument out of range"); - - int itemIndex = Math.DivRem(index, SLICE_SIZE, out int itemOffset); - largeArraySlices[itemIndex][itemOffset] = value; - } - } - - #endregion - - #region ICollection Members - - public void Add(Sector item) - { - DoCheckSizeLimitReached(); - - int itemIndex = Count / SLICE_SIZE; - if (itemIndex < largeArraySlices.Count) - { - largeArraySlices[itemIndex].Add(item); - } - else - { - List ar = new(SLICE_SIZE) - { - item - }; - largeArraySlices.Add(ar); - } - - Count++; - } - - public void Clear() - { - largeArraySlices.Clear(); - Count = 0; - } - - public bool Contains(Sector item) - { - throw new NotImplementedException(); - } - - public void CopyTo(Sector[] array, int arrayIndex) - { - throw new NotImplementedException(); - } - - public int Count { get; private set; } - - public bool IsReadOnly => false; - - public bool Remove(Sector item) - { - throw new NotImplementedException(); - } - - #endregion - - #region IEnumerable Members - - public IEnumerator GetEnumerator() - { - for (int i = 0; i < largeArraySlices.Count; i++) - { - List slice = largeArraySlices[i]; - for (int j = 0; j < slice.Count; j++) - { - yield return slice[j]; - } - } - } - - #endregion - - #region IEnumerable Members - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - #endregion - } -} diff --git a/sources/OpenMcdf/StreamExtensions.cs b/sources/OpenMcdf/StreamExtensions.cs deleted file mode 100644 index e80f2534..00000000 --- a/sources/OpenMcdf/StreamExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.IO; - -namespace OpenMcdf -{ - internal static class StreamExtensions - { - public static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count) - { - if (stream is null) - throw new ArgumentNullException(nameof(stream)); - - int totalRead = 0; - do - { - int read = stream.Read(buffer, offset + totalRead, count - totalRead); - if (read == 0) - throw new EndOfStreamException(); - - totalRead += read; - } while (totalRead < count); - } - } -} diff --git a/sources/OpenMcdf/StreamRW.cs b/sources/OpenMcdf/StreamRW.cs deleted file mode 100644 index ba181ad9..00000000 --- a/sources/OpenMcdf/StreamRW.cs +++ /dev/null @@ -1,137 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The Original Code is OpenMCDF - Compound Document Format library. - * - * The Initial Developer of the Original Code is Federico Blaseotto.*/ - -using System; -using System.IO; - -namespace OpenMcdf -{ - internal sealed class StreamRW - { - private readonly byte[] buffer = new byte[16]; - private readonly Stream stream; - - public StreamRW(Stream stream) - { - this.stream = stream; - } - - public long Seek(long count, SeekOrigin origin) - { - return stream.Seek(count, origin); - } - - public byte ReadByte() - { - stream.ReadExactly(buffer, 0, sizeof(byte)); - return buffer[0]; - } - - public ushort ReadUInt16() - { - stream.ReadExactly(buffer, 0, sizeof(ushort)); - return (ushort)(buffer[0] | (buffer[1] << 8)); - } - - public int ReadInt32() - { - stream.ReadExactly(buffer, 0, sizeof(int)); - return buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); - } - - public uint ReadUInt32() - { - stream.ReadExactly(buffer, 0, sizeof(uint)); - return (uint)(buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24)); - } - - public long ReadInt64() - { - stream.ReadExactly(buffer, 0, sizeof(long)); - uint ls = (uint)(buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24)); - uint ms = (uint)((buffer[4]) | (buffer[5] << 8) | (buffer[6] << 16) | (buffer[7] << 24)); - return (long)(((ulong)ms << 32) | ls); - } - - public ulong ReadUInt64() - { - stream.ReadExactly(buffer, 0, sizeof(ulong)); - return (ulong)(buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24) | (buffer[4] << 32) | (buffer[5] << 40) | (buffer[6] << 48) | (buffer[7] << 56)); - } - - public void ReadBytes(byte[] result) - { - stream.ReadExactly(result, 0, result.Length); - } - - public Guid ReadGuid() - { - stream.ReadExactly(buffer, 0, 16); - return new Guid(buffer); - } - - public void Write(byte b) - { - buffer[0] = b; - stream.Write(buffer, 0, 1); - } - - public void Write(ushort value) - { - buffer[0] = (byte)value; - buffer[1] = (byte)(value >> 8); - - stream.Write(buffer, 0, 2); - } - - public void Write(int value) - { - buffer[0] = (byte)value; - buffer[1] = (byte)(value >> 8); - buffer[2] = (byte)(value >> 16); - buffer[3] = (byte)(value >> 24); - - stream.Write(buffer, 0, 4); - } - - public void Write(long value) - { - buffer[0] = (byte)value; - buffer[1] = (byte)(value >> 8); - buffer[2] = (byte)(value >> 16); - buffer[3] = (byte)(value >> 24); - buffer[4] = (byte)(value >> 32); - buffer[5] = (byte)(value >> 40); - buffer[6] = (byte)(value >> 48); - buffer[7] = (byte)(value >> 56); - - stream.Write(buffer, 0, 8); - } - - public void Write(uint value) - { - buffer[0] = (byte)value; - buffer[1] = (byte)(value >> 8); - buffer[2] = (byte)(value >> 16); - buffer[3] = (byte)(value >> 24); - - stream.Write(buffer, 0, 4); - } - - public void Write(byte[] value) - { - stream.Write(value, 0, value.Length); - } - - public void Write(Guid value) - { - var data = value.ToByteArray(); - Write(data); - } - } -} diff --git a/sources/OpenMcdf/StreamView.cs b/sources/OpenMcdf/StreamView.cs deleted file mode 100644 index 8a303c8f..00000000 --- a/sources/OpenMcdf/StreamView.cs +++ /dev/null @@ -1,328 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The Original Code is OpenMCDF - Compound Document Format library. - * - * The Initial Developer of the Original Code is Federico Blaseotto.*/ - -using System; -using System.Collections.Generic; -using System.IO; - -namespace OpenMcdf -{ - /// - /// Stream decorator for a Sector or miniSector chain - /// - internal sealed class StreamView : Stream - { - private readonly int sectorSize; - - private long position; - private readonly Stream stream; - private readonly bool isFatStream; - private readonly List freeSectors = new(); - public IEnumerable FreeSectors => freeSectors; - - public StreamView(List sectorChain, int sectorSize, Stream stream) - { - if (sectorChain == null) - throw new CFException("Sector Chain cannot be null"); - - if (sectorSize <= 0) - throw new CFException("Sector size must be greater than zero"); - - BaseSectorChain = sectorChain; - this.sectorSize = sectorSize; - this.stream = stream; - } - - public StreamView(List sectorChain, int sectorSize, long length, Queue availableSectors, Stream stream, bool isFatStream = false) - : this(sectorChain, sectorSize, stream) - { - this.isFatStream = isFatStream; - AdjustLength(length, availableSectors); - } - - public List BaseSectorChain { get; } - - public override bool CanRead => true; - - public override bool CanSeek => true; - - public override bool CanWrite => true; - - public override void Flush() - { - } - - private long length; - - public override long Length => length; - - public override long Position - { - get => position; - set => Seek(value, SeekOrigin.Begin); - } - - public override int Read(byte[] buffer, int offset, int count) - { - if (buffer is null) - throw new ArgumentNullException(nameof(buffer)); - - if (offset < 0) - throw new ArgumentOutOfRangeException(nameof(offset), "Offset must be a non-negative number"); - - if ((uint)count > buffer.Length - offset) - throw new ArgumentException("Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection"); - - int nRead = 0; - - // Don't try to read more bytes than this stream contains. - long intMax = Math.Min(int.MaxValue, length); - count = Math.Min((int)intMax, count); - - // Ensure read request greater then stream length, when position is not 0, return only the limited and correct number of bytes - count = (int)Math.Min(length - position, count); - - if (BaseSectorChain != null && BaseSectorChain.Count > 0) - { - // First sector - int secIndex = (int)(position / sectorSize); - - // Bytes to read count is the min between request count - // and sector border - - int nToRead = Math.Min( - BaseSectorChain[0].Size - ((int)position % sectorSize), - count); - - if (secIndex < BaseSectorChain.Count) - { - Buffer.BlockCopy( - BaseSectorChain[secIndex].GetData(), - (int)(position % sectorSize), - buffer, - offset, - nToRead); - } - - nRead += nToRead; - - secIndex++; - - // Central sectors - while (nRead < (count - sectorSize)) - { - nToRead = sectorSize; - - Buffer.BlockCopy( - BaseSectorChain[secIndex].GetData(), - 0, - buffer, - offset + nRead, - nToRead); - - nRead += nToRead; - secIndex++; - } - - // Last sector - nToRead = count - nRead; - - if (nToRead != 0) - { - if (secIndex > BaseSectorChain.Count) throw new CFCorruptedFileException("The file is probably corrupted."); - - Buffer.BlockCopy( - BaseSectorChain[secIndex].GetData(), - 0, - buffer, - offset + nRead, - nToRead); - - nRead += nToRead; - } - - position += nRead; - - return nRead; - } - else - return 0; - } - - public override long Seek(long offset, SeekOrigin origin) - { - switch (origin) - { - case SeekOrigin.Begin: - if (offset < 0) - throw new IOException("Seek before origin"); - position = offset; - break; - - case SeekOrigin.Current: - if (position + offset < 0) - throw new IOException("Seek before origin"); - position += offset; - break; - - case SeekOrigin.End: - if (Length - offset < 0) - throw new IOException("Seek before origin"); - position = Length - offset; - break; - - default: - throw new ArgumentException(nameof(origin), "Invalid seek origin"); - } - - if (position > length) - AdjustLength(position); - - return position; - } - - private void AdjustLength(long value) - { - AdjustLength(value, null); - } - - private void AdjustLength(long value, Queue availableSectors) - { - length = value; - - long delta = value - (BaseSectorChain.Count * (long)sectorSize); - - if (delta > 0) - { - // Enlargement required - - int nSec = (int)Math.Ceiling((double)delta / sectorSize); - - while (nSec > 0) - { - Sector t; - if (availableSectors == null || availableSectors.Count == 0) - { - t = new Sector(sectorSize, stream); - - if (sectorSize == Sector.MINISECTOR_SIZE) - t.Type = SectorType.Mini; - } - else - { - t = availableSectors.Dequeue(); - } - - if (isFatStream) - { - t.InitFATData(); - } - - BaseSectorChain.Add(t); - nSec--; - } - - //if (((int)delta % sectorSize) != 0) - //{ - // Sector t = new Sector(sectorSize); - // sectorChain.Add(t); - //} - } - - //else - //{ - // // FREE Sectors - // delta = Math.Abs(delta); - - // int nSec = (int)Math.Floor(((double)delta / sectorSize)); - - // while (nSec > 0) - // { - // freeSectors.Add(sectorChain[sectorChain.Count - 1]); - // sectorChain.RemoveAt(sectorChain.Count - 1); - // nSec--; - // } - //} - } - - public override void SetLength(long value) - { - AdjustLength(value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - int byteWritten = 0; - - // Assure length - if ((position + count) > length) - AdjustLength(position + count); - - if (BaseSectorChain != null) - { - // First sector - int secOffset = (int)(position / sectorSize); - int secShift = (int)(position % sectorSize); - - int roundByteWritten = Math.Min(sectorSize - (int)(position % sectorSize), count); - - if (secOffset < BaseSectorChain.Count) - { - Buffer.BlockCopy( - buffer, - offset, - BaseSectorChain[secOffset].GetData(), - secShift, - roundByteWritten); - - BaseSectorChain[secOffset].DirtyFlag = true; - } - - byteWritten += roundByteWritten; - offset += roundByteWritten; - secOffset++; - - // Central sectors - while (byteWritten < (count - sectorSize)) - { - roundByteWritten = sectorSize; - - Buffer.BlockCopy( - buffer, - offset, - BaseSectorChain[secOffset].GetData(), - 0, - roundByteWritten); - - BaseSectorChain[secOffset].DirtyFlag = true; - - byteWritten += roundByteWritten; - offset += roundByteWritten; - secOffset++; - } - - // Last sector - roundByteWritten = count - byteWritten; - - if (roundByteWritten != 0) - { - Buffer.BlockCopy( - buffer, - offset, - BaseSectorChain[secOffset].GetData(), - 0, - roundByteWritten); - - BaseSectorChain[secOffset].DirtyFlag = true; - } - - position += count; - } - } - } -} diff --git a/sources/SharedAssemblyInfo.cs b/sources/SharedAssemblyInfo.cs deleted file mode 100644 index 858966ce..00000000 --- a/sources/SharedAssemblyInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyCompany("-")] -[assembly: AssemblyProduct("OpenMcdf 2.1")] -[assembly: AssemblyCopyright("Copyright © 2010-2011, Federico Blaseotto")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyVersion("2.1.6.28924")] - diff --git a/sources/Structured Storage Explorer/InputBox.cs b/sources/Structured Storage Explorer/InputBox.cs deleted file mode 100644 index f088942b..00000000 --- a/sources/Structured Storage Explorer/InputBox.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Windows.Forms; -using System.Drawing; - -namespace StructuredStorageExplorer -{ - class Utils - { - public static DialogResult InputBox(string title, string promptText, ref string value) - { - Form form = new Form(); - Label label = new Label(); - TextBox textBox = new TextBox(); - Button buttonOk = new Button(); - Button buttonCancel = new Button(); - - form.Text = title; - label.Text = promptText; - textBox.Text = value; - - buttonOk.Text = "OK"; - buttonCancel.Text = "Cancel"; - buttonOk.DialogResult = DialogResult.OK; - buttonCancel.DialogResult = DialogResult.Cancel; - - label.SetBounds(9, 20, 372, 13); - textBox.SetBounds(12, 36, 372, 20); - buttonOk.SetBounds(228, 72, 75, 23); - buttonCancel.SetBounds(309, 72, 75, 23); - - label.AutoSize = true; - textBox.Anchor = textBox.Anchor | AnchorStyles.Right; - buttonOk.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; - buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; - - form.ClientSize = new Size(396, 107); - form.Controls.AddRange(new Control[] { label, textBox, buttonOk, buttonCancel }); - form.ClientSize = new Size(Math.Max(300, label.Right + 10), form.ClientSize.Height); - form.FormBorderStyle = FormBorderStyle.FixedDialog; - form.StartPosition = FormStartPosition.CenterScreen; - form.MinimizeBox = false; - form.MaximizeBox = false; - form.AcceptButton = buttonOk; - form.CancelButton = buttonCancel; - - DialogResult dialogResult = form.ShowDialog(); - value = textBox.Text; - return dialogResult; - } - } -} diff --git a/sources/Structured Storage Explorer/MainForm.Designer.cs b/sources/Structured Storage Explorer/MainForm.Designer.cs deleted file mode 100644 index a20ed86b..00000000 --- a/sources/Structured Storage Explorer/MainForm.Designer.cs +++ /dev/null @@ -1,466 +0,0 @@ -namespace StructuredStorageExplorer -{ - partial class MainForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.components = new System.ComponentModel.Container(); - this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog(); - this.treeView1 = new System.Windows.Forms.TreeView(); - this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); - this.importDataStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); - this.exportDataToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.addStorageStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); - this.addStreamToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.saveFileDialog1 = new System.Windows.Forms.SaveFileDialog(); - this.menuStrip1 = new System.Windows.Forms.MenuStrip(); - this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.openFileMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.newStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); - this.closeStripMenuItem1 = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); - this.updateCurrentFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.saveAsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.preferencesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.openDataFileDialog = new System.Windows.Forms.OpenFileDialog(); - this.statusStrip1 = new System.Windows.Forms.StatusStrip(); - this.fileNameLabel = new System.Windows.Forms.ToolStripStatusLabel(); - this.splitContainer1 = new System.Windows.Forms.SplitContainer(); - this.propertyGrid1 = new System.Windows.Forms.PropertyGrid(); - this.splitContainer2 = new System.Windows.Forms.SplitContainer(); - this.tabControl1 = new System.Windows.Forms.TabControl(); - this.tabPage1 = new System.Windows.Forms.TabPage(); - this.hexEditor = new Be.Windows.Forms.HexBox(); - this.tabPage2 = new System.Windows.Forms.TabPage(); - this.splitContainer3 = new System.Windows.Forms.SplitContainer(); - this.dgvOLEProps = new System.Windows.Forms.DataGridView(); - this.dgvUserDefinedProperties = new System.Windows.Forms.DataGridView(); - this.contextMenuStrip1.SuspendLayout(); - this.menuStrip1.SuspendLayout(); - this.statusStrip1.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); - this.splitContainer1.Panel1.SuspendLayout(); - this.splitContainer1.Panel2.SuspendLayout(); - this.splitContainer1.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); - this.splitContainer2.Panel1.SuspendLayout(); - this.splitContainer2.Panel2.SuspendLayout(); - this.splitContainer2.SuspendLayout(); - this.tabControl1.SuspendLayout(); - this.tabPage1.SuspendLayout(); - this.tabPage2.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).BeginInit(); - this.splitContainer3.Panel1.SuspendLayout(); - this.splitContainer3.Panel2.SuspendLayout(); - this.splitContainer3.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.dgvOLEProps)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.dgvUserDefinedProperties)).BeginInit(); - this.SuspendLayout(); - // - // openFileDialog1 - // - this.openFileDialog1.Filter = "Office files (*.xls *.doc *.ppt)|*.xls;*.doc;*.ppt|Thumbs db files (Thumbs.db)|*." + - "db|MSI Setup files (*.msi)|*.msi|Advanced Authoring Format (*.aaf)|*.aaf|All fi" + - "les (*.*)|*.*"; - this.openFileDialog1.Title = "Open OLE Structured Storage file"; - // - // treeView1 - // - this.treeView1.ContextMenuStrip = this.contextMenuStrip1; - this.treeView1.Dock = System.Windows.Forms.DockStyle.Fill; - this.treeView1.HideSelection = false; - this.treeView1.Location = new System.Drawing.Point(0, 0); - this.treeView1.Name = "treeView1"; - this.treeView1.Size = new System.Drawing.Size(281, 201); - this.treeView1.TabIndex = 4; - this.treeView1.MouseUp += new System.Windows.Forms.MouseEventHandler(this.treeView1_MouseUp); - // - // contextMenuStrip1 - // - this.contextMenuStrip1.ImageScalingSize = new System.Drawing.Size(20, 20); - this.contextMenuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.importDataStripMenuItem1, - this.exportDataToolStripMenuItem, - this.addStorageStripMenuItem1, - this.addStreamToolStripMenuItem, - this.removeToolStripMenuItem}); - this.contextMenuStrip1.Name = "contextMenuStrip1"; - this.contextMenuStrip1.Size = new System.Drawing.Size(148, 114); - this.contextMenuStrip1.Opening += new System.ComponentModel.CancelEventHandler(this.contextMenuStrip1_Opening); - // - // importDataStripMenuItem1 - // - this.importDataStripMenuItem1.Name = "importDataStripMenuItem1"; - this.importDataStripMenuItem1.Size = new System.Drawing.Size(147, 22); - this.importDataStripMenuItem1.Text = "Import data..."; - this.importDataStripMenuItem1.Click += new System.EventHandler(this.importDataStripMenuItem1_Click); - // - // exportDataToolStripMenuItem - // - this.exportDataToolStripMenuItem.Name = "exportDataToolStripMenuItem"; - this.exportDataToolStripMenuItem.Size = new System.Drawing.Size(147, 22); - this.exportDataToolStripMenuItem.Text = "Export data..."; - this.exportDataToolStripMenuItem.Click += new System.EventHandler(this.exportDataToolStripMenuItem_Click); - // - // addStorageStripMenuItem1 - // - this.addStorageStripMenuItem1.Name = "addStorageStripMenuItem1"; - this.addStorageStripMenuItem1.Size = new System.Drawing.Size(147, 22); - this.addStorageStripMenuItem1.Text = "Add storage..."; - this.addStorageStripMenuItem1.Click += new System.EventHandler(this.addStorageStripMenuItem1_Click); - // - // addStreamToolStripMenuItem - // - this.addStreamToolStripMenuItem.Name = "addStreamToolStripMenuItem"; - this.addStreamToolStripMenuItem.Size = new System.Drawing.Size(147, 22); - this.addStreamToolStripMenuItem.Text = "Add stream..."; - this.addStreamToolStripMenuItem.Click += new System.EventHandler(this.addStreamToolStripMenuItem_Click); - // - // removeToolStripMenuItem - // - this.removeToolStripMenuItem.Name = "removeToolStripMenuItem"; - this.removeToolStripMenuItem.Size = new System.Drawing.Size(147, 22); - this.removeToolStripMenuItem.Text = "Remove"; - this.removeToolStripMenuItem.Click += new System.EventHandler(this.removeToolStripMenuItem_Click); - // - // saveFileDialog1 - // - this.saveFileDialog1.DefaultExt = "*.bin"; - this.saveFileDialog1.Filter = "Exported data files (*.bin)|*.bin|All files (*.*)|*.*"; - // - // menuStrip1 - // - this.menuStrip1.ImageScalingSize = new System.Drawing.Size(20, 20); - this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.fileToolStripMenuItem, - this.editToolStripMenuItem}); - this.menuStrip1.Location = new System.Drawing.Point(0, 0); - this.menuStrip1.Name = "menuStrip1"; - this.menuStrip1.Padding = new System.Windows.Forms.Padding(6, 1, 0, 1); - this.menuStrip1.Size = new System.Drawing.Size(853, 24); - this.menuStrip1.TabIndex = 5; - this.menuStrip1.Text = "menuStrip1"; - // - // fileToolStripMenuItem - // - this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.openFileMenuItem, - this.newStripMenuItem1, - this.closeStripMenuItem1, - this.toolStripSeparator2, - this.updateCurrentFileToolStripMenuItem, - this.saveAsToolStripMenuItem}); - this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; - this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 22); - this.fileToolStripMenuItem.Text = "File"; - // - // openFileMenuItem - // - this.openFileMenuItem.Image = global::StructuredStorageExplorer.Properties.Resources.folder; - this.openFileMenuItem.Name = "openFileMenuItem"; - this.openFileMenuItem.Size = new System.Drawing.Size(187, 26); - this.openFileMenuItem.Text = "Open..."; - this.openFileMenuItem.Click += new System.EventHandler(this.openFileMenuItem_Click); - // - // newStripMenuItem1 - // - this.newStripMenuItem1.Image = global::StructuredStorageExplorer.Properties.Resources.page_white; - this.newStripMenuItem1.Name = "newStripMenuItem1"; - this.newStripMenuItem1.Size = new System.Drawing.Size(187, 26); - this.newStripMenuItem1.Text = "New Compound File"; - this.newStripMenuItem1.Click += new System.EventHandler(this.newStripMenuItem1_Click); - // - // closeStripMenuItem1 - // - this.closeStripMenuItem1.Name = "closeStripMenuItem1"; - this.closeStripMenuItem1.Size = new System.Drawing.Size(187, 26); - this.closeStripMenuItem1.Text = "Close file"; - this.closeStripMenuItem1.Click += new System.EventHandler(this.closeStripMenuItem1_Click); - // - // toolStripSeparator2 - // - this.toolStripSeparator2.Name = "toolStripSeparator2"; - this.toolStripSeparator2.Size = new System.Drawing.Size(184, 6); - // - // updateCurrentFileToolStripMenuItem - // - this.updateCurrentFileToolStripMenuItem.Image = global::StructuredStorageExplorer.Properties.Resources.disk; - this.updateCurrentFileToolStripMenuItem.Name = "updateCurrentFileToolStripMenuItem"; - this.updateCurrentFileToolStripMenuItem.Size = new System.Drawing.Size(187, 26); - this.updateCurrentFileToolStripMenuItem.Text = "Save"; - this.updateCurrentFileToolStripMenuItem.Click += new System.EventHandler(this.updateCurrentFileToolStripMenuItem_Click); - // - // saveAsToolStripMenuItem - // - this.saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem"; - this.saveAsToolStripMenuItem.Size = new System.Drawing.Size(187, 26); - this.saveAsToolStripMenuItem.Text = "Save As..."; - this.saveAsToolStripMenuItem.Click += new System.EventHandler(this.saveAsToolStripMenuItem_Click); - // - // editToolStripMenuItem - // - this.editToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.preferencesToolStripMenuItem}); - this.editToolStripMenuItem.Name = "editToolStripMenuItem"; - this.editToolStripMenuItem.Size = new System.Drawing.Size(39, 22); - this.editToolStripMenuItem.Text = "Edit"; - // - // preferencesToolStripMenuItem - // - this.preferencesToolStripMenuItem.Name = "preferencesToolStripMenuItem"; - this.preferencesToolStripMenuItem.Size = new System.Drawing.Size(135, 22); - this.preferencesToolStripMenuItem.Text = "Preferences"; - this.preferencesToolStripMenuItem.Click += new System.EventHandler(this.preferencesToolStripMenuItem_Click); - // - // statusStrip1 - // - this.statusStrip1.ImageScalingSize = new System.Drawing.Size(20, 20); - this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.fileNameLabel}); - this.statusStrip1.Location = new System.Drawing.Point(0, 436); - this.statusStrip1.Name = "statusStrip1"; - this.statusStrip1.Size = new System.Drawing.Size(853, 22); - this.statusStrip1.TabIndex = 6; - this.statusStrip1.Text = "statusStrip1"; - // - // fileNameLabel - // - this.fileNameLabel.Name = "fileNameLabel"; - this.fileNameLabel.Size = new System.Drawing.Size(0, 17); - // - // splitContainer1 - // - this.splitContainer1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; - this.splitContainer1.Location = new System.Drawing.Point(0, 0); - this.splitContainer1.Name = "splitContainer1"; - this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; - // - // splitContainer1.Panel1 - // - this.splitContainer1.Panel1.Controls.Add(this.treeView1); - // - // splitContainer1.Panel2 - // - this.splitContainer1.Panel2.Controls.Add(this.propertyGrid1); - this.splitContainer1.Size = new System.Drawing.Size(283, 412); - this.splitContainer1.SplitterDistance = 203; - this.splitContainer1.TabIndex = 5; - // - // propertyGrid1 - // - this.propertyGrid1.Dock = System.Windows.Forms.DockStyle.Fill; - this.propertyGrid1.Location = new System.Drawing.Point(0, 0); - this.propertyGrid1.Name = "propertyGrid1"; - this.propertyGrid1.Size = new System.Drawing.Size(281, 203); - this.propertyGrid1.TabIndex = 0; - this.propertyGrid1.ToolbarVisible = false; - // - // splitContainer2 - // - this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; - this.splitContainer2.Location = new System.Drawing.Point(0, 24); - this.splitContainer2.Name = "splitContainer2"; - // - // splitContainer2.Panel1 - // - this.splitContainer2.Panel1.Controls.Add(this.splitContainer1); - // - // splitContainer2.Panel2 - // - this.splitContainer2.Panel2.Controls.Add(this.tabControl1); - this.splitContainer2.Size = new System.Drawing.Size(853, 412); - this.splitContainer2.SplitterDistance = 283; - this.splitContainer2.TabIndex = 7; - // - // tabControl1 - // - this.tabControl1.Controls.Add(this.tabPage1); - this.tabControl1.Controls.Add(this.tabPage2); - this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; - this.tabControl1.Location = new System.Drawing.Point(0, 0); - this.tabControl1.Name = "tabControl1"; - this.tabControl1.SelectedIndex = 0; - this.tabControl1.Size = new System.Drawing.Size(566, 412); - this.tabControl1.TabIndex = 1; - // - // tabPage1 - // - this.tabPage1.Controls.Add(this.hexEditor); - this.tabPage1.Location = new System.Drawing.Point(4, 22); - this.tabPage1.Name = "tabPage1"; - this.tabPage1.Padding = new System.Windows.Forms.Padding(3, 3, 3, 3); - this.tabPage1.Size = new System.Drawing.Size(558, 386); - this.tabPage1.TabIndex = 0; - this.tabPage1.Text = "Raw Data"; - this.tabPage1.UseVisualStyleBackColor = true; - // - // hexEditor - // - this.hexEditor.BackColor = System.Drawing.Color.WhiteSmoke; - this.hexEditor.Dock = System.Windows.Forms.DockStyle.Fill; - this.hexEditor.Font = new System.Drawing.Font("Courier New", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.hexEditor.InfoForeColor = System.Drawing.Color.Empty; - this.hexEditor.LineInfoVisible = true; - this.hexEditor.Location = new System.Drawing.Point(3, 3); - this.hexEditor.Name = "hexEditor"; - this.hexEditor.ShadowSelectionColor = System.Drawing.Color.FromArgb(((int)(((byte)(100)))), ((int)(((byte)(60)))), ((int)(((byte)(188)))), ((int)(((byte)(255))))); - this.hexEditor.Size = new System.Drawing.Size(552, 380); - this.hexEditor.StringViewVisible = true; - this.hexEditor.TabIndex = 0; - this.hexEditor.UseFixedBytesPerLine = true; - this.hexEditor.VScrollBarVisible = true; - // - // tabPage2 - // - this.tabPage2.Controls.Add(this.splitContainer3); - this.tabPage2.Location = new System.Drawing.Point(4, 22); - this.tabPage2.Name = "tabPage2"; - this.tabPage2.Padding = new System.Windows.Forms.Padding(3, 3, 3, 3); - this.tabPage2.Size = new System.Drawing.Size(558, 396); - this.tabPage2.TabIndex = 1; - this.tabPage2.Text = "OLE Properties"; - this.tabPage2.UseVisualStyleBackColor = true; - // - // splitContainer3 - // - this.splitContainer3.Dock = System.Windows.Forms.DockStyle.Fill; - this.splitContainer3.Location = new System.Drawing.Point(3, 3); - this.splitContainer3.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3); - this.splitContainer3.Name = "splitContainer3"; - this.splitContainer3.Orientation = System.Windows.Forms.Orientation.Horizontal; - // - // splitContainer3.Panel1 - // - this.splitContainer3.Panel1.Controls.Add(this.dgvOLEProps); - // - // splitContainer3.Panel2 - // - this.splitContainer3.Panel2.Controls.Add(this.dgvUserDefinedProperties); - this.splitContainer3.Size = new System.Drawing.Size(552, 390); - this.splitContainer3.SplitterDistance = 195; - this.splitContainer3.SplitterWidth = 3; - this.splitContainer3.TabIndex = 2; - // - // dgvOLEProps - // - this.dgvOLEProps.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.dgvOLEProps.Dock = System.Windows.Forms.DockStyle.Fill; - this.dgvOLEProps.Location = new System.Drawing.Point(0, 0); - this.dgvOLEProps.Name = "dgvOLEProps"; - this.dgvOLEProps.RowHeadersWidth = 62; - this.dgvOLEProps.Size = new System.Drawing.Size(552, 195); - this.dgvOLEProps.TabIndex = 0; - // - // dgvUserDefinedProperties - // - this.dgvUserDefinedProperties.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.dgvUserDefinedProperties.Dock = System.Windows.Forms.DockStyle.Fill; - this.dgvUserDefinedProperties.Location = new System.Drawing.Point(0, 0); - this.dgvUserDefinedProperties.Name = "dgvUserDefinedProperties"; - this.dgvUserDefinedProperties.RowHeadersWidth = 62; - this.dgvUserDefinedProperties.Size = new System.Drawing.Size(552, 192); - this.dgvUserDefinedProperties.TabIndex = 1; - // - // MainForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(853, 458); - this.Controls.Add(this.splitContainer2); - this.Controls.Add(this.statusStrip1); - this.Controls.Add(this.menuStrip1); - this.MainMenuStrip = this.menuStrip1; - this.Name = "MainForm"; - this.Text = "Structured Storage eXplorer"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing); - this.contextMenuStrip1.ResumeLayout(false); - this.menuStrip1.ResumeLayout(false); - this.menuStrip1.PerformLayout(); - this.statusStrip1.ResumeLayout(false); - this.statusStrip1.PerformLayout(); - this.splitContainer1.Panel1.ResumeLayout(false); - this.splitContainer1.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); - this.splitContainer1.ResumeLayout(false); - this.splitContainer2.Panel1.ResumeLayout(false); - this.splitContainer2.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); - this.splitContainer2.ResumeLayout(false); - this.tabControl1.ResumeLayout(false); - this.tabPage1.ResumeLayout(false); - this.tabPage2.ResumeLayout(false); - this.splitContainer3.Panel1.ResumeLayout(false); - this.splitContainer3.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).EndInit(); - this.splitContainer3.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.dgvOLEProps)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.dgvUserDefinedProperties)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.OpenFileDialog openFileDialog1; - private System.Windows.Forms.TreeView treeView1; - private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; - private System.Windows.Forms.ToolStripMenuItem exportDataToolStripMenuItem; - private System.Windows.Forms.SaveFileDialog saveFileDialog1; - private System.Windows.Forms.ToolStripMenuItem removeToolStripMenuItem; - private System.Windows.Forms.MenuStrip menuStrip1; - private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem saveAsToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem updateCurrentFileToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem addStreamToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem importDataStripMenuItem1; - private System.Windows.Forms.OpenFileDialog openDataFileDialog; - private System.Windows.Forms.ToolStripMenuItem addStorageStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem newStripMenuItem1; - private System.Windows.Forms.ToolStripMenuItem openFileMenuItem; - private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; - private System.Windows.Forms.StatusStrip statusStrip1; - private System.Windows.Forms.ToolStripStatusLabel fileNameLabel; - private System.Windows.Forms.SplitContainer splitContainer1; - private System.Windows.Forms.PropertyGrid propertyGrid1; - private System.Windows.Forms.SplitContainer splitContainer2; - private Be.Windows.Forms.HexBox hexEditor; - private System.Windows.Forms.ToolStripMenuItem closeStripMenuItem1; - private System.Windows.Forms.TabControl tabControl1; - private System.Windows.Forms.TabPage tabPage1; - private System.Windows.Forms.TabPage tabPage2; - private System.Windows.Forms.DataGridView dgvOLEProps; - private System.Windows.Forms.DataGridView dgvUserDefinedProperties; - private System.Windows.Forms.SplitContainer splitContainer3; - private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem preferencesToolStripMenuItem; - } -} - diff --git a/sources/Structured Storage Explorer/MainForm.cs b/sources/Structured Storage Explorer/MainForm.cs deleted file mode 100644 index 718d04cf..00000000 --- a/sources/Structured Storage Explorer/MainForm.cs +++ /dev/null @@ -1,502 +0,0 @@ -#define OLE_PROPERTY - -using OpenMcdf; -using OpenMcdf.Extensions; -using OpenMcdf.Extensions.OLEProperties; -using StructuredStorageExplorer.Properties; -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Globalization; -using System.IO; -using System.Windows.Forms; - -// Author Federico Blaseotto - -namespace StructuredStorageExplorer -{ - /// - /// Sample Structured Storage viewer to - /// demonstrate use of OpenMCDF - /// - public partial class MainForm : Form - { - private CompoundFile cf; - private FileStream fs; - - public MainForm() - { - InitializeComponent(); - -#if !OLE_PROPERTY - tabControl1.TabPages.Remove(tabPage2); -#endif - - //Load images for icons from resx - Image folderImage = (Image)Resources.ResourceManager.GetObject("storage"); - Image streamImage = (Image)Resources.ResourceManager.GetObject("stream"); - //Image olePropsImage = (Image)Properties.Resources.ResourceManager.GetObject("oleprops"); - - treeView1.ImageList = new ImageList(); - treeView1.ImageList.Images.Add(folderImage); - treeView1.ImageList.Images.Add(streamImage); - //treeView1.ImageList.Images.Add(olePropsImage); - - saveAsToolStripMenuItem.Enabled = false; - updateCurrentFileToolStripMenuItem.Enabled = false; - } - - private void OpenFile() - { - if (!string.IsNullOrEmpty(openFileDialog1.FileName)) - { - CloseCurrentFile(); - - treeView1.Nodes.Clear(); - fileNameLabel.Text = openFileDialog1.FileName; - LoadFile(openFileDialog1.FileName, true); - canUpdate = true; - saveAsToolStripMenuItem.Enabled = true; - updateCurrentFileToolStripMenuItem.Enabled = true; - } - } - - private void CloseCurrentFile() - { - cf?.Close(); - cf = null; - - fs?.Close(); - fs = null; - - treeView1.Nodes.Clear(); - fileNameLabel.Text = string.Empty; - saveAsToolStripMenuItem.Enabled = false; - updateCurrentFileToolStripMenuItem.Enabled = false; - - propertyGrid1.SelectedObject = null; - hexEditor.ByteProvider = null; - -#if OLE_PROPERTY - dgvUserDefinedProperties.DataSource = null; - dgvOLEProps.DataSource = null; -#endif - } - - private bool canUpdate; - - private void CreateNewFile() - { - CloseCurrentFile(); - - cf = new CompoundFile(); - canUpdate = false; - saveAsToolStripMenuItem.Enabled = true; - - updateCurrentFileToolStripMenuItem.Enabled = false; - - RefreshTree(); - } - - private void RefreshTree() - { - treeView1.Nodes.Clear(); - TreeNode root = treeView1.Nodes.Add("Root Entry", "Root"); - root.ImageIndex = 0; - root.Tag = cf.RootStorage; - - // Recursive function to get all storage and streams - AddNodes(root, cf.RootStorage); - } - - private void LoadFile(string fileName, bool enableCommit) - { - fs = new FileStream( - fileName, - FileMode.Open, - enableCommit ? - FileAccess.ReadWrite - : FileAccess.Read); - - try - { - cf?.Close(); - cf = null; - - CFSConfiguration cfg = CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors; - - if (!Settings.Default.EnableValidation) - cfg |= CFSConfiguration.NoValidationException; - - // Load file - cf = enableCommit ? new CompoundFile(fs, CFSUpdateMode.Update, cfg) : new CompoundFile(fs); - - RefreshTree(); - } - catch (Exception ex) - { - cf?.Close(); - cf = null; - - fs?.Close(); - fs = null; - - treeView1.Nodes.Clear(); - fileNameLabel.Text = string.Empty; - MessageBox.Show("Internal error: " + ex.Message, "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - /// - /// Recursive addition of tree nodes foreach child of current item in the storage - /// - /// Current TreeNode - /// Current storage associated with node - private static void AddNodes(TreeNode node, CFStorage storage) - { - void va(CFItem item) - { - TreeNode childNode = node.Nodes.Add( - item.Name, - item.Name + (item.IsStream ? " (" + item.Size + " bytes )" : "")); - - childNode.Tag = item; - - if (item is CFStorage subStorage) - { - // Storage - childNode.ImageIndex = 0; - childNode.SelectedImageIndex = 0; - - // Recursion into the storage - AddNodes(childNode, subStorage); - } - else - { - // Stream - childNode.ImageIndex = 1; - childNode.SelectedImageIndex = 1; - } - } - - // Visit NON-recursively (first level only) - storage.VisitEntries(va, false); - } - - private void exportDataToolStripMenuItem_Click(object sender, EventArgs e) - { - // No export if storage - if (treeView1.SelectedNode == null - || treeView1.SelectedNode.Tag is not CFStream stream) - { - MessageBox.Show("Only stream data can be exported", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); - return; - } - - // A lot of stream and storage have only non-printable characters. - // We need to sanitize filename. - - string sanitizedFileName = string.Empty; - - foreach (char c in stream.Name) - { - UnicodeCategory category = char.GetUnicodeCategory(c); - if (category is UnicodeCategory.LetterNumber or UnicodeCategory.LowercaseLetter or UnicodeCategory.UppercaseLetter) - sanitizedFileName += c; - } - - if (string.IsNullOrEmpty(sanitizedFileName)) - { - sanitizedFileName = "tempFileName"; - } - - saveFileDialog1.FileName = $"{sanitizedFileName}.bin"; - - if (saveFileDialog1.ShowDialog() == DialogResult.OK) - { - try - { - using FileStream fs = new(saveFileDialog1.FileName, FileMode.CreateNew, FileAccess.ReadWrite); - fs.Write(stream.GetData(), 0, (int)stream.Size); - } - catch (Exception ex) - { - treeView1.Nodes.Clear(); - MessageBox.Show($"Internal error: {ex.Message}", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - } - - private void removeToolStripMenuItem_Click(object sender, EventArgs e) - { - TreeNode n = treeView1.SelectedNode; - if (n?.Parent?.Tag is CFStorage storage) - storage.Delete(n.Name); - - RefreshTree(); - } - - private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) - { - saveFileDialog1.FilterIndex = 2; - if (saveFileDialog1.ShowDialog() == DialogResult.OK) - { - cf.SaveAs(saveFileDialog1.FileName); - } - } - - private void updateCurrentFileToolStripMenuItem_Click(object sender, EventArgs e) - { - if (canUpdate) - { - if (hexEditor.ByteProvider != null && hexEditor.ByteProvider.HasChanges()) - hexEditor.ByteProvider.ApplyChanges(); - cf.Commit(); - } - else - { - MessageBox.Show("Cannot update a compound document that is not based on a stream or on a file", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - private void addStreamToolStripMenuItem_Click(object sender, EventArgs e) - { - string streamName = string.Empty; - - if (Utils.InputBox("Add stream", "Insert stream name", ref streamName) == DialogResult.OK - && treeView1.SelectedNode.Tag is CFStorage storage) - { - try - { - storage.AddStream(streamName); - } - catch (CFDuplicatedItemException) - { - MessageBox.Show("Cannot insert a duplicated item", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - RefreshTree(); - } - } - - private void addStorageStripMenuItem1_Click(object sender, EventArgs e) - { - string storageName = string.Empty; - - if (Utils.InputBox("Add storage", "Insert storage name", ref storageName) == DialogResult.OK - && treeView1.SelectedNode.Tag is CFStorage storage) - { - try - { - storage.AddStorage(storageName); - } - catch (CFDuplicatedItemException) - { - MessageBox.Show("Cannot insert a duplicated item", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - RefreshTree(); - } - } - - private void importDataStripMenuItem1_Click(object sender, EventArgs e) - { - if (openDataFileDialog.ShowDialog() == DialogResult.OK - && treeView1.SelectedNode.Tag is CFStream stream) - { - using FileStream f = new(openDataFileDialog.FileName, FileMode.Open, FileAccess.Read, FileShare.Read); - byte[] data = new byte[f.Length]; - f.Read(data, 0, (int)f.Length); - f.Flush(); - f.Close(); - stream.SetData(data); - - RefreshTree(); - } - } - - private void MainForm_FormClosing(object sender, FormClosingEventArgs e) - { - cf?.Close(); - cf = null; - } - - private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) - { - } - - private void newStripMenuItem1_Click(object sender, EventArgs e) - { - CreateNewFile(); - } - - private void openFileMenuItem_Click(object sender, EventArgs e) - { - if (openFileDialog1.ShowDialog() == DialogResult.OK) - { - try - { - OpenFile(); - } - catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or CFException) - { - MessageBox.Show($"Cannot open file: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - } - - private void treeView1_MouseUp(object sender, MouseEventArgs e) - { - TreeNode n = treeView1.GetNodeAt(e.X, e.Y); - if (n is null) - { - addStorageStripMenuItem1.Enabled = true; - addStreamToolStripMenuItem.Enabled = true; - importDataStripMenuItem1.Enabled = false; - exportDataToolStripMenuItem.Enabled = false; - removeToolStripMenuItem.Enabled = false; - propertyGrid1.SelectedObject = null; - return; - } - - // Get the node under the mouse cursor. - // We intercept both left and right mouse clicks - // and set the selected TreeNode according. - try - { - if (hexEditor.ByteProvider != null && hexEditor.ByteProvider.HasChanges()) - { - if (MessageBox.Show("Do you want to save pending changes ?", "Save changes", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) - { - hexEditor.ByteProvider.ApplyChanges(); - } - } - - treeView1.SelectedNode = n; - - // The tag property contains the underlying CFItem. - //CFItem target = (CFItem)n.Tag; - - if (n.Tag is CFStream stream) - { - addStorageStripMenuItem1.Enabled = false; - addStreamToolStripMenuItem.Enabled = false; - importDataStripMenuItem1.Enabled = true; - exportDataToolStripMenuItem.Enabled = true; - -#if OLE_PROPERTY - dgvUserDefinedProperties.DataSource = null; - dgvOLEProps.DataSource = null; - - if (stream.Name is "\u0005SummaryInformation" or "\u0005DocumentSummaryInformation") - { - OLEPropertiesContainer c = stream.AsOLEPropertiesContainer(); - - DataTable ds = new DataTable(); - ds.Columns.Add("Name", typeof(string)); - ds.Columns.Add("Type", typeof(string)); - ds.Columns.Add("Value", typeof(string)); - - foreach (OLEProperty p in c.Properties) - { - if (p.Value is not byte[] and IList list) - { - for (int h = 0; h < list.Count; h++) - { - DataRow dr = ds.NewRow(); - dr.ItemArray = new object[] { p.PropertyName, p.VTType, list[h] }; - ds.Rows.Add(dr); - } - } - else - { - DataRow dr = ds.NewRow(); - dr.ItemArray = new object[] { p.PropertyName, p.VTType, p.Value }; - ds.Rows.Add(dr); - } - } - - ds.AcceptChanges(); - dgvOLEProps.DataSource = ds; - - if (c.HasUserDefinedProperties) - { - DataTable ds2 = new(); - ds2.Columns.Add("Name", typeof(string)); - ds2.Columns.Add("Type", typeof(string)); - ds2.Columns.Add("Value", typeof(string)); - - foreach (OLEProperty p in c.UserDefinedProperties.Properties) - { - if (p.Value is not byte[] and IList list) - { - for (int h = 0; h < list.Count; h++) - { - DataRow dr = ds2.NewRow(); - dr.ItemArray = new object[] { p.PropertyName, p.VTType, list[h] }; - ds2.Rows.Add(dr); - } - } - else - { - DataRow dr = ds2.NewRow(); - dr.ItemArray = new object[] { p.PropertyName, p.VTType, p.Value }; - ds2.Rows.Add(dr); - } - } - - ds2.AcceptChanges(); - dgvUserDefinedProperties.DataSource = ds2; - } - } - - hexEditor.ByteProvider = new StreamDataProvider(stream); -#endif - } - else - { - hexEditor.ByteProvider = null; - } - - propertyGrid1.SelectedObject = n.Tag; - } - catch (Exception ex) - { - cf?.Close(); - cf = null; - - fs?.Close(); - fs = null; - - treeView1.Nodes.Clear(); - fileNameLabel.Text = string.Empty; - - MessageBox.Show($"Internal error: {ex.Message}", "ERROR", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - void hexEditor_ByteProviderChanged(object sender, EventArgs e) - { - } - - private void closeStripMenuItem1_Click(object sender, EventArgs e) - { - if (hexEditor.ByteProvider != null - && hexEditor.ByteProvider.HasChanges() - && MessageBox.Show("Do you want to save pending changes?", "Save changes", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) - { - hexEditor.ByteProvider.ApplyChanges(); - } - - CloseCurrentFile(); - } - - private void preferencesToolStripMenuItem_Click(object sender, EventArgs e) - { - using PreferencesForm pref = new(); - pref.ShowDialog(); - } - } -} diff --git a/sources/Structured Storage Explorer/MainForm.resx b/sources/Structured Storage Explorer/MainForm.resx deleted file mode 100644 index f8835009..00000000 --- a/sources/Structured Storage Explorer/MainForm.resx +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - - 147, 17 - - - 292, 17 - - - 420, 17 - - - 529, 17 - - - 676, 17 - - - 100 - - \ No newline at end of file diff --git a/sources/Structured Storage Explorer/OpenMcdf.snk b/sources/Structured Storage Explorer/OpenMcdf.snk deleted file mode 100644 index b2990e73c74b1b002bd693d3d8008085b96f461f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097nwG6x-K;LO^j4j6cs`(%400Z5@yPQnZ7{$0%=kHi|-&%jj zIZo$C(^orbVlD(&H`PYI`!B&VSLuY?Q>Q)El2oao)5)5QriW~iFixpxnXCq=jeExP zwIDQlYjkbW{FAWhZ)4zsWDM5B0Ai$5KWtA3WU3fix^?x&19(Tr`ai`}{|vA3d;= zvaMECN!3mgvGE5t)=hH7N)+WzrP#-#HOb%u(+-Je%Npw`Uv^Y}ekuG=&$uUWso2C? z_`m=&4^8V#^8!SoI$f{Y;uM2Qdgc1+0wDpGERes_*oK=gF2S}Uz!p3<(}iN-sPo!x z - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.cbEnableValidation = new System.Windows.Forms.CheckBox(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.btnSavePreferences = new System.Windows.Forms.Button(); - this.btnCancelPreferences = new System.Windows.Forms.Button(); - this.groupBox1.SuspendLayout(); - this.SuspendLayout(); - // - // cbEnableValidation - // - this.cbEnableValidation.AutoSize = true; - this.cbEnableValidation.Location = new System.Drawing.Point(6, 25); - this.cbEnableValidation.Name = "cbEnableValidation"; - this.cbEnableValidation.Size = new System.Drawing.Size(277, 24); - this.cbEnableValidation.TabIndex = 0; - this.cbEnableValidation.Text = "File Validation Exceptions enabled"; - this.cbEnableValidation.UseVisualStyleBackColor = true; - // - // groupBox1 - // - this.groupBox1.Controls.Add(this.cbEnableValidation); - this.groupBox1.Location = new System.Drawing.Point(12, 12); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(555, 259); - this.groupBox1.TabIndex = 1; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Preferences"; - // - // btnSavePreferences - // - this.btnSavePreferences.Location = new System.Drawing.Point(473, 298); - this.btnSavePreferences.Name = "btnSavePreferences"; - this.btnSavePreferences.Size = new System.Drawing.Size(94, 30); - this.btnSavePreferences.TabIndex = 2; - this.btnSavePreferences.Text = "OK"; - this.btnSavePreferences.UseVisualStyleBackColor = true; - this.btnSavePreferences.Click += new System.EventHandler(this.btnSavePreferences_Click); - // - // btnCancelPreferences - // - this.btnCancelPreferences.Location = new System.Drawing.Point(373, 298); - this.btnCancelPreferences.Name = "btnCancelPreferences"; - this.btnCancelPreferences.Size = new System.Drawing.Size(94, 30); - this.btnCancelPreferences.TabIndex = 3; - this.btnCancelPreferences.Text = "Cancel"; - this.btnCancelPreferences.UseVisualStyleBackColor = true; - this.btnCancelPreferences.Click += new System.EventHandler(this.btnCancelPreferences_Click); - // - // PreferencesForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(579, 340); - this.Controls.Add(this.btnCancelPreferences); - this.Controls.Add(this.btnSavePreferences); - this.Controls.Add(this.groupBox1); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.Name = "PreferencesForm"; - this.ShowIcon = false; - this.ShowInTaskbar = false; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "Preferences"; - this.Load += new System.EventHandler(this.PreferencesForm_Load); - this.groupBox1.ResumeLayout(false); - this.groupBox1.PerformLayout(); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.CheckBox cbEnableValidation; - private System.Windows.Forms.GroupBox groupBox1; - private System.Windows.Forms.Button btnSavePreferences; - private System.Windows.Forms.Button btnCancelPreferences; - } -} \ No newline at end of file diff --git a/sources/Structured Storage Explorer/PreferencesForm.cs b/sources/Structured Storage Explorer/PreferencesForm.cs deleted file mode 100644 index bc8063af..00000000 --- a/sources/Structured Storage Explorer/PreferencesForm.cs +++ /dev/null @@ -1,34 +0,0 @@ -using StructuredStorageExplorer.Properties; -using System; -using System.Windows.Forms; - -namespace StructuredStorageExplorer -{ - public partial class PreferencesForm : Form - { - public PreferencesForm() - { - InitializeComponent(); - } - - private void btnSavePreferences_Click(object sender, EventArgs e) - { - Settings.Default.EnableValidation = cbEnableValidation.Checked; - Settings.Default.Save(); - DialogResult = DialogResult.OK; - Close(); - } - - private void btnCancelPreferences_Click(object sender, EventArgs e) - { - cbEnableValidation.Checked = Settings.Default.EnableValidation; - DialogResult = DialogResult.Cancel; - Close(); - } - - private void PreferencesForm_Load(object sender, EventArgs e) - { - cbEnableValidation.Checked = Settings.Default.EnableValidation; - } - } -} diff --git a/sources/Structured Storage Explorer/PreferencesForm.resx b/sources/Structured Storage Explorer/PreferencesForm.resx deleted file mode 100644 index 1af7de15..00000000 --- a/sources/Structured Storage Explorer/PreferencesForm.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/sources/Structured Storage Explorer/Program.cs b/sources/Structured Storage Explorer/Program.cs deleted file mode 100644 index f756a1d4..00000000 --- a/sources/Structured Storage Explorer/Program.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Windows.Forms; - -namespace StructuredStorageExplorer -{ - static class Program - { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new MainForm()); - } - } -} diff --git a/sources/Structured Storage Explorer/Properties/AssemblyInfo.cs b/sources/Structured Storage Explorer/Properties/AssemblyInfo.cs deleted file mode 100644 index 893e74d9..00000000 --- a/sources/Structured Storage Explorer/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("StucturedStorageExplorer")] -[assembly: AssemblyDescription("COM/OLE Structured Storage Viewer - Sample application for OpenMCDF component.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("StucturedStorageExplorer")] -[assembly: AssemblyCopyright("Copyright © 2010-2015, Federico Blaseotto")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("aaf2d359-361e-47a3-883e-194bb63c7930")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.0.0.*")] diff --git a/sources/Structured Storage Explorer/Properties/Resources.Designer.cs b/sources/Structured Storage Explorer/Properties/Resources.Designer.cs deleted file mode 100644 index fe0cdb90..00000000 --- a/sources/Structured Storage Explorer/Properties/Resources.Designer.cs +++ /dev/null @@ -1,123 +0,0 @@ -//------------------------------------------------------------------------------ -// -// Il codice è stato generato da uno strumento. -// Versione runtime:4.0.30319.42000 -// -// Le modifiche apportate a questo file possono provocare un comportamento non corretto e andranno perse se -// il codice viene rigenerato. -// -//------------------------------------------------------------------------------ - -namespace StructuredStorageExplorer.Properties { - using System; - - - /// - /// Classe di risorse fortemente tipizzata per la ricerca di stringhe localizzate e così via. - /// - // Questa classe è stata generata automaticamente dalla classe StronglyTypedResourceBuilder. - // tramite uno strumento quale ResGen o Visual Studio. - // Per aggiungere o rimuovere un membro, modificare il file con estensione ResX ed eseguire nuovamente ResGen - // con l'opzione /str oppure ricompilare il progetto VS. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Restituisce l'istanza di ResourceManager nella cache utilizzata da questa classe. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("StructuredStorageExplorer.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Esegue l'override della proprietà CurrentUICulture del thread corrente per tutte le - /// ricerche di risorse eseguite utilizzando questa classe di risorse fortemente tipizzata. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Cerca una risorsa localizzata di tipo System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap disk { - get { - object obj = ResourceManager.GetObject("disk", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Cerca una risorsa localizzata di tipo System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap door_out { - get { - object obj = ResourceManager.GetObject("door_out", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Cerca una risorsa localizzata di tipo System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap folder { - get { - object obj = ResourceManager.GetObject("folder", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Cerca una risorsa localizzata di tipo System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap page_white { - get { - object obj = ResourceManager.GetObject("page_white", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Cerca una risorsa localizzata di tipo System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap storage { - get { - object obj = ResourceManager.GetObject("storage", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Cerca una risorsa localizzata di tipo System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap stream { - get { - object obj = ResourceManager.GetObject("stream", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - } -} diff --git a/sources/Structured Storage Explorer/Properties/Resources.resx b/sources/Structured Storage Explorer/Properties/Resources.resx deleted file mode 100644 index 4dccc070..00000000 --- a/sources/Structured Storage Explorer/Properties/Resources.resx +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - ..\img\disk.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\img\door_out.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\img\folder.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\img\page_white.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\img\storage.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\img\stream.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - \ No newline at end of file diff --git a/sources/Structured Storage Explorer/Properties/Settings.Designer.cs b/sources/Structured Storage Explorer/Properties/Settings.Designer.cs deleted file mode 100644 index ebbdce8a..00000000 --- a/sources/Structured Storage Explorer/Properties/Settings.Designer.cs +++ /dev/null @@ -1,50 +0,0 @@ -//------------------------------------------------------------------------------ -// -// Il codice è stato generato da uno strumento. -// Versione runtime:4.0.30319.42000 -// -// Le modifiche apportate a questo file possono provocare un comportamento non corretto e andranno perse se -// il codice viene rigenerato. -// -//------------------------------------------------------------------------------ - -namespace StructuredStorageExplorer.Properties { - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default { - get { - return defaultInstance; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("True")] - public bool CommitEnabled { - get { - return ((bool)(this["CommitEnabled"])); - } - set { - this["CommitEnabled"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("False")] - public bool EnableValidation { - get { - return ((bool)(this["EnableValidation"])); - } - set { - this["EnableValidation"] = value; - } - } - } -} diff --git a/sources/Structured Storage Explorer/Properties/Settings.settings b/sources/Structured Storage Explorer/Properties/Settings.settings deleted file mode 100644 index 7c61266d..00000000 --- a/sources/Structured Storage Explorer/Properties/Settings.settings +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - True - - - False - - - \ No newline at end of file diff --git a/sources/Structured Storage Explorer/StreamDataProvider.cs b/sources/Structured Storage Explorer/StreamDataProvider.cs deleted file mode 100644 index 74ad1c88..00000000 --- a/sources/Structured Storage Explorer/StreamDataProvider.cs +++ /dev/null @@ -1,158 +0,0 @@ -using Be.Windows.Forms; -using OpenMcdf; -using System; - -namespace StructuredStorageExplorer -{ - public class StreamDataProvider : IByteProvider - { - /// - /// Modifying stream - /// - readonly CFStream _modifiedStream; - - /// - /// Contains information about changes. - /// - bool _hasChanges; - - /// - /// Initializes a new instance of the StreamDataProvider class. - /// - /// - public StreamDataProvider(CFStream modifiedStream) - { - Bytes = new ByteCollection(modifiedStream.GetData()); - _modifiedStream = modifiedStream; - } - - /// - /// Raises the Changed event. - /// - void OnChanged(EventArgs e) - { - _hasChanges = true; - - Changed?.Invoke(this, e); - } - - /// - /// Raises the LengthChanged event. - /// - void OnLengthChanged(EventArgs e) - { - LengthChanged?.Invoke(this, e); - } - - /// - /// Gets the byte collection. - /// - public ByteCollection Bytes { get; } - - #region IByteProvider Members - /// - /// True, when changes are done. - /// - public bool HasChanges() - { - return _hasChanges; - } - - /// - /// Applies changes. - /// - public void ApplyChanges() - { - _hasChanges = false; - - _modifiedStream.SetData(Bytes.ToArray()); - } - - /// - /// Occurs, when the write buffer contains new changes. - /// - public event EventHandler Changed; - - /// - /// Occurs, when InsertBytes or DeleteBytes method is called. - /// - public event EventHandler LengthChanged; - - /// - /// Reads a byte from the byte collection. - /// - /// the index of the byte to read - /// the byte - public byte ReadByte(long index) - { return Bytes[(int)index]; } - - /// - /// Write a byte into the byte collection. - /// - /// the index of the byte to write. - /// the byte - public void WriteByte(long index, byte value) - { - Bytes[(int)index] = value; - OnChanged(EventArgs.Empty); - } - - /// - /// Deletes bytes from the byte collection. - /// - /// the start index of the bytes to delete. - /// the length of bytes to delete. - public void DeleteBytes(long index, long length) - { - int internal_index = (int)Math.Max(0, index); - int internal_length = (int)Math.Min((int)Length, length); - Bytes.RemoveRange(internal_index, internal_length); - - OnLengthChanged(EventArgs.Empty); - OnChanged(EventArgs.Empty); - } - - /// - /// Inserts byte into the byte collection. - /// - /// the start index of the bytes in the byte collection - /// the byte array to insert - public void InsertBytes(long index, byte[] bs) - { - Bytes.InsertRange((int)index, bs); - - OnLengthChanged(EventArgs.Empty); - OnChanged(EventArgs.Empty); - } - - /// - /// Gets the length of the bytes in the byte collection. - /// - public long Length => Bytes.Count; - - /// - /// Returns true - /// - public bool SupportsWriteByte() - { - return true; - } - - /// - /// Returns true - /// - public bool SupportsInsertBytes() - { - return true; - } - - /// - /// Returns true - /// - public bool SupportsDeleteBytes() - { - return true; - } - #endregion - } -} diff --git a/sources/Structured Storage Explorer/StructuredStorageExplorer.csproj b/sources/Structured Storage Explorer/StructuredStorageExplorer.csproj deleted file mode 100644 index ad7cf6b6..00000000 --- a/sources/Structured Storage Explorer/StructuredStorageExplorer.csproj +++ /dev/null @@ -1,74 +0,0 @@ - - - net48 - WinExe - StucturedStorageExplorer - http://localhost/StucturedStorageExplorer/ - true - Web - true - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - true - false - true - false - true - true - False - - - ..\..\bin\Debug\StructuredStorageXplorer\ - AllRules.ruleset - false - ..\..\bin\Debug\StructuredStorageXplorer\StucturedStorageExplorer.XML - - - ..\..\bin\Release\StructuredStorageXplorer\ - AllRules.ruleset - ..\..\bin\Release\StructuredStorageXplorer\StucturedStorageExplorer.XML - - - - - - - - - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - - - - - - - - - \ No newline at end of file diff --git a/sources/Structured Storage Explorer/Utils.cs b/sources/Structured Storage Explorer/Utils.cs deleted file mode 100644 index 0a33f1fd..00000000 --- a/sources/Structured Storage Explorer/Utils.cs +++ /dev/null @@ -1,57 +0,0 @@ -using OpenMcdf; -using System; -using System.Drawing; -using System.Windows.Forms; - -namespace StructuredStorageExplorer -{ - static class Utils - { - public static DialogResult InputBox(string title, string promptText, ref string value) - { - Form form = new Form(); - Label label = new Label(); - TextBox textBox = new TextBox(); - Button buttonOk = new Button(); - Button buttonCancel = new Button(); - - form.Text = title; - label.Text = promptText; - textBox.Text = value; - - buttonOk.Text = "OK"; - buttonCancel.Text = "Cancel"; - buttonOk.DialogResult = DialogResult.OK; - buttonCancel.DialogResult = DialogResult.Cancel; - - label.SetBounds(9, 20, 372, 13); - textBox.SetBounds(12, 36, 372, 20); - buttonOk.SetBounds(228, 72, 75, 23); - buttonCancel.SetBounds(309, 72, 75, 23); - - label.AutoSize = true; - textBox.Anchor |= AnchorStyles.Right; - buttonOk.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; - buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; - - form.ClientSize = new Size(396, 107); - form.Controls.AddRange(new Control[] { label, textBox, buttonOk, buttonCancel }); - form.ClientSize = new Size(Math.Max(300, label.Right + 10), form.ClientSize.Height); - form.FormBorderStyle = FormBorderStyle.FixedDialog; - form.StartPosition = FormStartPosition.CenterScreen; - form.MinimizeBox = false; - form.MaximizeBox = false; - form.AcceptButton = buttonOk; - form.CancelButton = buttonCancel; - - DialogResult dialogResult = form.ShowDialog(); - value = textBox.Text; - return dialogResult; - } - - public static bool IsStreamTreeNode(TreeNode node) - { - return ((CFItem)node.Tag).IsStream; - } - } -} diff --git a/sources/Structured Storage Explorer/app.config b/sources/Structured Storage Explorer/app.config deleted file mode 100644 index 6cd5da2a..00000000 --- a/sources/Structured Storage Explorer/app.config +++ /dev/null @@ -1,18 +0,0 @@ - - - - -
- - - - - - True - - - False - - - - diff --git a/sources/Structured Storage Explorer/img/disk.png b/sources/Structured Storage Explorer/img/disk.png deleted file mode 100644 index 99d532e8b1750115952f97302a92d713c0486f97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 620 zcmV-y0+aoTP)~H+MJzd|s z^YP1Hc07G_>)Lgir!F1{Qn4GcTg%?koHo<=1qRN{}nPDolOeI^o4N5I>! zU$N=L=sg~ zDx#dOA*B0N~cqPsWI(^rbbkh)DS0_H_UN0C4l_kvWIm2#Kyy6%BCh z(yIUf003&1xdx>t$*eR2ZvXxT0001Z_R$y3Iju92q*wg58};}zm(OaAH=p|y0002M zh5O5#fxp|~jc?yi@+7$`d4Q6Hl%z;WiWG??NXR{Hx%)pMd~SE0000OQI diff --git a/sources/Structured Storage Explorer/img/door_out.png b/sources/Structured Storage Explorer/img/door_out.png deleted file mode 100644 index 2541d2bcbc218b194f79fd99f67d33de1873c6c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 688 zcmV;h0#E&kP)GS2eE@_I zS~TaE^z1tT1me$mOd>fuB1*9ukjYHe@2!~sjG5tP)N7xSr3G9P+3oKa++V)SLaGru zn`QvjgqvWRa7{oUyOUDA37GDE%9f3r>9Muk`Z$59p<W>iYj7vxikNWw_8sK+%_fnobvCa5%KNOO6e%CReDLPLmVwdHp%H5J z8cVW-n2=oPDz8D+5J{LSmLlCXMPg`l3MX6KVJNMw&n~!g&9zA<=CHFVyLz1l^k+DTiboyDIuKD~MJE&R)Oo;bO6gPHCylVLL*a<{&sTsdkI7k&ZU WO{4dBd(FK70000x(K@^6+>g^d@v4;gkbWsEoXE%32*i1tcpTNXd5CcIl)ECgqz|2rE6EW}s7R?kl za1q`0GCkMruC6-2LANtwVlsgzsp4?{@7$`KBv!G66>Vie3h?3OmEEkjwdLG0PgLVi z`!N((f$A@n17Ldj#`};0I3@iHJ5M{#IZz|UIYRm4(!uV7eYIYIwQf&}_2J~}>pQ^n z6o8--^T(=hkBNQ_k{-_GWE;FMW7!p}f{NG3nHZ{D5<3d8&tLh%a4AqqnjMkr3m&fkMdECD3N5}Unig5wy40;>lo4j~k+e}v)` zR6)J8Mk*u=SpB`p6o)7j?S0T@9?bz#m@l>gc*zk__|*!FMcHwP!gwLJvS~9c0px8E zWC#5QQ<|d}62BjvZR2H60wE-&H;pyTSqH(@-Vl>|&1p(LP>kg~E zYiz5X^`c$+%8#zC{u)yfe-5 zmgid={Z3k(ERKCKrE7DF;=x4^O+ pzO8rLO8p|Ip=x)jHOtWj`bJBmKdh_V<`47(gQu&X%Q~loCIFbEay|e6 diff --git a/sources/Structured Storage Explorer/img/storage.png b/sources/Structured Storage Explorer/img/storage.png deleted file mode 100644 index cbde61822a99090c03c45759518ad41a562e6bca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 396 zcmV;70dxL|P)!;NE`7)<>#@EXE+d`cov zvkwEWuoQ;m=eHn3w&2r$faUMszYJV_!VG_Z|HP05i5q#upTaRKY#u({Q32r zfgAwR%kum8Z-(DLup99C-7AJ0XZGSX1nh#}KfhsWe*c=`=D9-*c0uVlHD5Wl1Ly*v z0mpZ)#Hzp{D3#&E%ZE4(c=_Zm1B-x|41=zP3swd1p5DP920-t#ym)w!xji7{tEfEm|Kzbnt{P|730ssE}J8;R$-c>er3NHbV1vLP(!2H*mf zo{~UP63m`@^eY2~0e}AdVfg!xf#KTY_h7?*|NhPJ>(?(h2Ju1SKK907wb%^+xvVuwh7`}v3A^Y>p2AHV)$`26h`SS?H+K!5=N-dA4Q?faNt00000NkvXXu0mjf DU{|iw diff --git a/sources/Test/OpenMcdf.Benchmark/Extensions.cs b/sources/Test/OpenMcdf.Benchmark/Extensions.cs deleted file mode 100644 index c52e558c..00000000 --- a/sources/Test/OpenMcdf.Benchmark/Extensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using BenchmarkDotNet.Attributes; -using OpenMcdf.Extensions; -using System.IO; - -namespace OpenMcdf.Benchmark -{ - // Simple benchmarks for reading OLE Properties with OpenMcdf.Extensions. - [SimpleJob] - [MemoryDiagnoser] - public class ExtensionsRead - { - // Keep the Storage as a member, as we're only testing the OLEProperties bits, not the underlying CompoundFile - private readonly CompoundFile udTestFile; - - // Load the test file on creation - public ExtensionsRead() - { - string testFile = Path.Combine("TestFiles", "winUnicodeDictionary.doc"); - udTestFile = new CompoundFile(testFile); - } - - [GlobalCleanup] - public void GlobalCleanup() - { - udTestFile.Close(); - } - - [Benchmark] - public void TestReadSummaryInformation() - { - udTestFile.RootStorage.GetStream("\u0005SummaryInformation").AsOLEPropertiesContainer(); - } - - [Benchmark] - public void TestReadDocumentSummaryInformation() - { - udTestFile.RootStorage.GetStream("\u0005DocumentSummaryInformation").AsOLEPropertiesContainer(); - } - } -} \ No newline at end of file diff --git a/sources/Test/OpenMcdf.Benchmark/InMemory.cs b/sources/Test/OpenMcdf.Benchmark/InMemory.cs deleted file mode 100644 index c5de3a11..00000000 --- a/sources/Test/OpenMcdf.Benchmark/InMemory.cs +++ /dev/null @@ -1,99 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Order; -using System; -using System.IO; - -namespace OpenMcdf.Benchmark -{ - [SimpleJob] - [CsvExporter] - [HtmlExporter] - [MarkdownExporter] - //[DryCoreJob] // I always forget this attribute, so please leave it commented out - [MemoryDiagnoser] - [Orderer(SummaryOrderPolicy.FastestToSlowest)] - public class InMemory : IDisposable - { - private const int Kb = 1024; - private const int Mb = Kb * Kb; - private const string storageName = "MyStorage"; - private const string streamName = "MyStream"; - - private byte[] _readBuffer; - - private MemoryStream _stream; - - [Params(Kb / 2, Kb, 4 * Kb, 128 * Kb, 256 * Kb, 512 * Kb, Kb * Kb)] - public int BufferSize { get; set; } - - [Params(Mb /*, 8 * Mb, 64 * Mb, 128 * Mb*/)] - public int TotalStreamSize { get; set; } - - public void Dispose() - { - _stream?.Dispose(); - } - - [GlobalSetup] - public void GlobalSetup() - { - _stream = new MemoryStream(); - _readBuffer = new byte[BufferSize]; - CreateFile(1); - } - - [GlobalCleanup] - public void GlobalCleanup() - { - _stream.Dispose(); - _stream = null; - _readBuffer = null; - } - - [Benchmark] - public void Test() - { - // - _stream.Seek(0L, SeekOrigin.Begin); - // - var compoundFile = new CompoundFile(_stream); - var cfStream = compoundFile.RootStorage - .GetStorage(storageName) - .GetStream(streamName + 0); - var streamSize = cfStream.Size; - var position = 0L; - while (true) - { - if (position >= streamSize) break; - var read = cfStream - .Read(_readBuffer, position, _readBuffer.Length); - position += read; - if (read <= 0) break; - } - - //compoundFile.Close(); - } - - private void CreateFile(int streamCount) - { - var iterationCount = TotalStreamSize / BufferSize; - - var buffer = new byte[BufferSize]; - Array.Fill(buffer, byte.MaxValue); - const CFSConfiguration flags = CFSConfiguration.Default | CFSConfiguration.LeaveOpen; - using (var compoundFile = new CompoundFile(CFSVersion.Ver_4, flags)) - { - var st = compoundFile.RootStorage.AddStorage(storageName); - for (var streamId = 0; streamId < streamCount; ++streamId) - { - var sm = st.AddStream(streamName + streamId); - - for (var iteration = 0; iteration < iterationCount; ++iteration) sm.Append(buffer); - } - - compoundFile.Save(_stream); - compoundFile.Close(); - } - } - } -} \ No newline at end of file diff --git a/sources/Test/OpenMcdf.Benchmark/OpenMcdf.Benchmark.csproj b/sources/Test/OpenMcdf.Benchmark/OpenMcdf.Benchmark.csproj deleted file mode 100644 index d3a7fd4c..00000000 --- a/sources/Test/OpenMcdf.Benchmark/OpenMcdf.Benchmark.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - Exe - net6.0 - - - - - - - - - - - - - - PreserveNewest - - - - diff --git a/sources/Test/OpenMcdf.Benchmark/Program.cs b/sources/Test/OpenMcdf.Benchmark/Program.cs deleted file mode 100644 index 34aa8734..00000000 --- a/sources/Test/OpenMcdf.Benchmark/Program.cs +++ /dev/null @@ -1,12 +0,0 @@ -using BenchmarkDotNet.Running; - -namespace OpenMcdf.Benchmark -{ - internal static class Program - { - private static void Main(string[] args) - { - BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); - } - } -} diff --git a/sources/Test/OpenMcdf.Extensions.Test/CFSStreamExtensionsTest.cs b/sources/Test/OpenMcdf.Extensions.Test/CFSStreamExtensionsTest.cs deleted file mode 100644 index d09a5182..00000000 --- a/sources/Test/OpenMcdf.Extensions.Test/CFSStreamExtensionsTest.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.IO; - -namespace OpenMcdf.Extensions.Test -{ - /// - /// Summary description for UnitTest1 - /// - [TestClass] - public class CFSStreamExtensionsTest - { - public CFSStreamExtensionsTest() - { - } - - /// - /// Gets or sets the test context which provides - /// information about and functionality for the current test run. - /// - public TestContext TestContext { get; set; } - - #region Additional test attributes - // - // You can use the following additional attributes as you write your tests: - // - // Use ClassInitialize to run code before running the first test in the class - // [ClassInitialize()] - // public static void MyClassInitialize(TestContext testContext) { } - // - // Use ClassCleanup to run code after all tests in a class have run - // [ClassCleanup()] - // public static void MyClassCleanup() { } - // - // Use TestInitialize to run code before running each test - // [TestInitialize()] - // public void MyTestInitialize() { } - // - // Use TestCleanup to run code after each test has run - // [TestCleanup()] - // public void MyTestCleanup() { } - // - #endregion - - [TestMethod] - public void Test_AS_IOSTREAM_READ() - { - using CompoundFile cf = new("MultipleStorage.cfs"); - using Stream s = cf.RootStorage.GetStorage("MyStorage").GetStream("MyStream").AsIOStream(); - using BinaryReader br = new(s); - byte[] result = br.ReadBytes(32); - CollectionAssert.AreEqual(Helpers.GetBuffer(32, 1), result); - } - - [TestMethod] - public void Test_AS_IOSTREAM_WRITE() - { - const string cmp = "Hello World of BinaryWriter !"; - - using (CompoundFile cf = new()) - { - using Stream s = cf.RootStorage.AddStream("ANewStream").AsIOStream(); - using BinaryWriter bw = new(s); - bw.Write(cmp); - cf.SaveAs("$ACFFile.cfs"); - } - - using (CompoundFile cf = new("$ACFFile.cfs")) - { - using Stream s = cf.RootStorage.GetStream("ANewStream").AsIOStream(); - using BinaryReader br = new(s); - string st = br.ReadString(); - Assert.AreEqual(cmp, st); - } - } - - [TestMethod] - public void Test_AS_IOSTREAM_MULTISECTOR_WRITE() - { - byte[] data = new byte[670]; - for (int i = 0; i < data.Length; i++) - { - data[i] = (byte)(i % 255); - } - - using (CompoundFile cf = new()) - { - using Stream s = cf.RootStorage.AddStream("ANewStream").AsIOStream(); - using BinaryWriter bw = new(s); - bw.Write(data); - cf.SaveAs("$ACFFile2.cfs"); - } - - using (CompoundFile cf = new("$ACFFile2.cfs")) - { - using Stream s = cf.RootStorage.GetStream("ANewStream").AsIOStream(); - using BinaryReader br = new(s); - byte[] readData = new byte[data.Length]; - int readCount = br.Read(readData, 0, readData.Length); - Assert.AreEqual(readData.Length, readCount); - CollectionAssert.AreEqual(data, readData); - } - } - - [TestMethod] - [DataRow(CFSVersion.Ver_3, 0)] - [DataRow(CFSVersion.Ver_3, 63)] - [DataRow(CFSVersion.Ver_3, 64)] - [DataRow(CFSVersion.Ver_3, 65)] - [DataRow(CFSVersion.Ver_3, 511)] - [DataRow(CFSVersion.Ver_3, 512)] - [DataRow(CFSVersion.Ver_3, 513)] - [DataRow(CFSVersion.Ver_3, 4095)] - [DataRow(CFSVersion.Ver_3, 4096)] - [DataRow(CFSVersion.Ver_3, 409)] - [DataRow(CFSVersion.Ver_4, 0)] - [DataRow(CFSVersion.Ver_4, 63)] - [DataRow(CFSVersion.Ver_4, 64)] - [DataRow(CFSVersion.Ver_4, 65)] - [DataRow(CFSVersion.Ver_4, 511)] - [DataRow(CFSVersion.Ver_4, 512)] - [DataRow(CFSVersion.Ver_4, 513)] - [DataRow(CFSVersion.Ver_4, 4095)] - [DataRow(CFSVersion.Ver_4, 4096)] - [DataRow(CFSVersion.Ver_4, 4097)] - public void Test_STREAMDECORATOR_COPY(CFSVersion version, int length) - { - using CompoundFile cf = new(version, CFSConfiguration.Default); - CFStream cfStream = cf.RootStorage.AddStream("MyStream"); - using StreamDecorator stream = new(cfStream); - var buffer = Helpers.GetBuffer(length); - stream.Write(buffer, 0, buffer.Length); - stream.Position = 0; - Assert.AreEqual(length, cfStream.Size); - - using MemoryStream memoryStream = new(); - stream.CopyTo(memoryStream); - CollectionAssert.AreEqual(buffer, memoryStream.ToArray()); - } - } -} diff --git a/sources/Test/OpenMcdf.Extensions.Test/Helpers.cs b/sources/Test/OpenMcdf.Extensions.Test/Helpers.cs deleted file mode 100644 index c622777c..00000000 --- a/sources/Test/OpenMcdf.Extensions.Test/Helpers.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace OpenMcdf.Extensions.Test -{ - public static class Helpers - { - public static byte[] GetBuffer(int count) - { - Random r = new Random(); - byte[] b = new byte[count]; - r.NextBytes(b); - return b; - } - - public static byte[] GetBuffer(int count, byte c) - { - byte[] b = new byte[count]; - for (int i = 0; i < b.Length; i++) - { - b[i] = c; - } - - return b; - } - } -} diff --git a/sources/Test/OpenMcdf.Extensions.Test/OLEPropertiesExtensionsTest.cs b/sources/Test/OpenMcdf.Extensions.Test/OLEPropertiesExtensionsTest.cs deleted file mode 100644 index 5b72e304..00000000 --- a/sources/Test/OpenMcdf.Extensions.Test/OLEPropertiesExtensionsTest.cs +++ /dev/null @@ -1,551 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using OpenMcdf.Extensions.OLEProperties; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; - -namespace OpenMcdf.Extensions.Test -{ - /// - /// Summary description for UnitTest1 - /// - [TestClass] - public class OLEPropertiesExtensionsTest - { - public OLEPropertiesExtensionsTest() - { - } - - /// - /// Gets or sets the test context which provides - /// information about and functionality for the current test run. - /// - public TestContext TestContext { get; set; } - - #region Additional test attributes - // - // You can use the following additional attributes as you write your tests: - // - // Use ClassInitialize to run code before running the first test in the class - // [ClassInitialize()] - // public static void MyClassInitialize(TestContext testContext) { } - // - // Use ClassCleanup to run code after all tests in a class have run - // [ClassCleanup()] - // public static void MyClassCleanup() { } - // - // Use TestInitialize to run code before running each test - // [TestInitialize()] - // public void MyTestInitialize() { } - // - // Use TestCleanup to run code after each test has run - // [TestCleanup()] - // public void MyTestCleanup() { } - // - #endregion - - [TestMethod] - public void Test_SUMMARY_INFO_READ() - { - using CompoundFile cf = new CompoundFile("_Test.ppt"); - var co = cf.RootStorage.GetStream("\u0005SummaryInformation").AsOLEPropertiesContainer(); - - Assert.IsNotNull(co.Properties); - - foreach (OLEProperty p in co.Properties) - { - Debug.WriteLine(p); - } - } - - [TestMethod] - public void Test_DOCUMENT_SUMMARY_INFO_READ() - { - using CompoundFile cf = new CompoundFile("_Test.ppt"); - var co = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation").AsOLEPropertiesContainer(); - - Assert.IsNotNull(co.Properties); - - foreach (OLEProperty p in co.Properties) - { - Debug.WriteLine(p); - } - } - - [TestMethod] - public void Test_DOCUMENT_SUMMARY_INFO_ROUND_TRIP() - { - File.Delete("test1.cfs"); - - using CompoundFile cf = new CompoundFile("_Test.ppt"); - var co = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation").AsOLEPropertiesContainer(); - - using CompoundFile cf2 = new CompoundFile(); - cf2.RootStorage.AddStream("\u0005DocumentSummaryInformation"); - - co.Save(cf2.RootStorage.GetStream("\u0005DocumentSummaryInformation")); - - cf2.SaveAs("test1.cfs"); - } - - // Modify some document summary information properties, save to a file, and then validate the expected results - [TestMethod] - public void Test_DOCUMENT_SUMMARY_INFO_MODIFY() - { - File.Delete("test_modify_summary.ppt"); - - // Verify initial properties, and then create a modified document - using (CompoundFile cf = new CompoundFile("_Test.ppt")) - { - var dsiStream = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation"); - var co = dsiStream.AsOLEPropertiesContainer(); - - // The company property should exist but be empty - var companyProperty = co.Properties.First(prop => prop.PropertyName == "PIDDSI_COMPANY"); - Assert.AreEqual("", companyProperty.Value); - - // As a sanity check, check that the value of a property that we don't change remains the same - var formatProperty = co.Properties.First(prop => prop.PropertyName == "PIDDSI_PRESFORMAT"); - Assert.AreEqual("A4 Paper (210x297 mm)", formatProperty.Value); - - // The manager property shouldn't exist, and we'll add it - Assert.IsFalse(co.Properties.Any(prop => prop.PropertyName == "PIDDSI_MANAGER")); - - var managerProp = co.NewProperty(VTPropertyType.VT_LPSTR, 0x0000000E, "PIDDSI_MANAGER"); - co.AddProperty(managerProp); - - companyProperty.Value = "My Company"; - managerProp.Value = "The Boss"; - - co.Save(dsiStream); - cf.SaveAs(@"test_modify_summary.ppt"); - } - - using (CompoundFile cf = new CompoundFile("test_modify_summary.ppt")) - { - var co = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation").AsOLEPropertiesContainer(); - - var companyProperty = co.Properties.First(prop => prop.PropertyName == "PIDDSI_COMPANY"); - Assert.AreEqual("My Company", companyProperty.Value); - - var formatProperty = co.Properties.First(prop => prop.PropertyName == "PIDDSI_PRESFORMAT"); - Assert.AreEqual("A4 Paper (210x297 mm)", formatProperty.Value); - - var managerProperty = co.Properties.First(prop => prop.PropertyName == "PIDDSI_MANAGER"); - Assert.AreEqual("The Boss", managerProperty.Value); - } - } - - [TestMethod] - public void Test_SUMMARY_INFO_READ_UTF8_ISSUE_33() - { - using CompoundFile cf = new CompoundFile("wstr_presets.doc"); - var co = cf.RootStorage.GetStream("\u0005SummaryInformation").AsOLEPropertiesContainer(); - - Assert.IsNotNull(co.Properties); - - foreach (OLEProperty p in co.Properties) - { - Debug.WriteLine(p); - } - - var co2 = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation").AsOLEPropertiesContainer(); - - Assert.IsNotNull(co2.Properties); - - foreach (OLEProperty p in co2.Properties) - { - Debug.WriteLine(p); - } - } - - [TestMethod] - public void Test_SUMMARY_INFO_READ_UTF8_ISSUE_34() - { - using CompoundFile cf = new CompoundFile("2custom.doc"); - var co = cf.RootStorage.GetStream("\u0005SummaryInformation").AsOLEPropertiesContainer(); - - Assert.IsNotNull(co.Properties); - foreach (OLEProperty p in co.Properties) - { - Debug.WriteLine(p); - } - - var co2 = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation").AsOLEPropertiesContainer(); - - Assert.IsNotNull(co2.Properties); - foreach (OLEProperty p in co2.Properties) - { - Debug.WriteLine(p); - } - - Assert.IsNotNull(co2.UserDefinedProperties.Properties); - foreach (OLEProperty p in co2.UserDefinedProperties.Properties) - { - Debug.WriteLine(p); - } - } - - [TestMethod] - public void Test_SUMMARY_INFO_READ_LPWSTRING() - { - using CompoundFile cf = new CompoundFile("english.presets.doc"); - var co = cf.RootStorage.GetStream("\u0005SummaryInformation").AsOLEPropertiesContainer(); - - Assert.IsNotNull(co.Properties); - - foreach (OLEProperty p in co.Properties) - { - Debug.WriteLine(p); - } - - cf.Close(); - } - - // Test that we can modify an LPWSTR property, and the value is null terminated as required - [TestMethod] - public void Test_SUMMARY_INFO_MODIFY_LPWSTRING() - { - File.Delete("test_write_lpwstr.doc"); - - // Modify some LPWSTR properties, and save to a new file - using (CompoundFile cf = new CompoundFile("wstr_presets.doc")) - { - var dsiStream = cf.RootStorage.GetStream("\u0005SummaryInformation"); - var co = dsiStream.AsOLEPropertiesContainer(); - - var authorProperty = co.Properties.First(prop => prop.PropertyName == "PIDSI_AUTHOR"); - Assert.AreEqual(VTPropertyType.VT_LPWSTR, authorProperty.VTType); - Assert.AreEqual("zkyiqpqoroxnbdwhnjfqroxlgylpbgcwuhjfifpkvycugvuecoputqgknnbs", authorProperty.Value); - - var keyWordsProperty = co.Properties.First(prop => prop.PropertyName == "PIDSI_KEYWORDS"); - Assert.AreEqual(VTPropertyType.VT_LPWSTR, keyWordsProperty.VTType); - Assert.AreEqual("abcdefghijk", keyWordsProperty.Value); - - authorProperty.Value = "ABC"; - keyWordsProperty.Value = ""; - co.Save(dsiStream); - cf.SaveAs("test_write_lpwstr.doc"); - } - - // Open the new file and check for the expected values - using (CompoundFile cf = new CompoundFile("test_write_lpwstr.doc")) - { - var co = cf.RootStorage.GetStream("\u0005SummaryInformation").AsOLEPropertiesContainer(); - - var authorProperty = co.Properties.First(prop => prop.PropertyName == "PIDSI_AUTHOR"); - Assert.AreEqual(VTPropertyType.VT_LPWSTR, authorProperty.VTType); - Assert.AreEqual("ABC", authorProperty.Value); - - var keyWordsProperty = co.Properties.First(prop => prop.PropertyName == "PIDSI_KEYWORDS"); - Assert.AreEqual(VTPropertyType.VT_LPWSTR, keyWordsProperty.VTType); - Assert.AreEqual("", keyWordsProperty.Value); - } - } - - // winUnicodeDictionary.doc contains a UserProperties section with the CP_WINUNICODE codepage, and LPWSTR string properties - [TestMethod] - public void Test_Read_Unicode_User_Properties_Dictionary() - { - using CompoundFile cf = new CompoundFile("winUnicodeDictionary.doc"); - var dsiStream = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation"); - var co = dsiStream.AsOLEPropertiesContainer(); - var userProps = co.UserDefinedProperties; - - // CodePage should be CP_WINUNICODE (1200) - Assert.AreEqual(1200, userProps.Context.CodePage); - - // There should be 5 property names present, and 6 properties (the properties include the code page) - Assert.AreEqual(5, userProps.PropertyNames.Count); - Assert.AreEqual(6, userProps.Properties.Count()); - - // Check for expected names and values - var propArray = userProps.Properties.ToArray(); - - // CodePage prop - Assert.AreEqual(1u, propArray[0].PropertyIdentifier); - Assert.AreEqual("0x00000001", propArray[0].PropertyName); - Assert.AreEqual((short)1200, propArray[0].Value); - - // String properties - Assert.AreEqual("A", propArray[1].PropertyName); - Assert.AreEqual("", propArray[1].Value); - Assert.AreEqual("AB", propArray[2].PropertyName); - Assert.AreEqual("X", propArray[2].Value); - Assert.AreEqual("ABC", propArray[3].PropertyName); - Assert.AreEqual("XY", propArray[3].Value); - Assert.AreEqual("ABCD", propArray[4].PropertyName); - Assert.AreEqual("XYZ", propArray[4].Value); - Assert.AreEqual("ABCDE", propArray[5].PropertyName); - Assert.AreEqual("XYZ!", propArray[5].Value); - } - - // Test that we can add user properties of various types and then read them back - [TestMethod] - public void Test_DOCUMENT_SUMMARY_INFO_ADD_CUSTOM() - { - const string tempFileName = nameof(Test_Add_User_Defined_Property); - File.Delete(tempFileName); - - // Test value for a VT_FILETIME property - DateTime testNow = DateTime.UtcNow; - - // english.presets.doc has a user defined property section, but no properties other than the codepage - using (CompoundFile cf = new CompoundFile("english.presets.doc")) - { - var dsiStream = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation"); - var co = dsiStream.AsOLEPropertiesContainer(); - var userProperties = co.UserDefinedProperties; - - userProperties.PropertyNames[2] = "StringProperty"; - userProperties.PropertyNames[3] = "BooleanProperty"; - userProperties.PropertyNames[4] = "IntegerProperty"; - userProperties.PropertyNames[5] = "DateProperty"; - userProperties.PropertyNames[6] = "DoubleProperty"; - - var stringProperty = co.NewProperty(VTPropertyType.VT_LPSTR, 2); - stringProperty.Value = "Hello"; - userProperties.AddProperty(stringProperty); - - var booleanProperty = co.NewProperty(VTPropertyType.VT_BOOL, 3); - booleanProperty.Value = true; - userProperties.AddProperty(booleanProperty); - - var integerProperty = co.NewProperty(VTPropertyType.VT_I4, 4); - integerProperty.Value = 3456; - userProperties.AddProperty(integerProperty); - - var timeProperty = co.NewProperty(VTPropertyType.VT_FILETIME, 5); - timeProperty.Value = testNow; - userProperties.AddProperty(timeProperty); - - var doubleProperty = co.NewProperty(VTPropertyType.VT_R8, 6); - doubleProperty.Value = 1.234567d; - userProperties.AddProperty(doubleProperty); - - co.Save(dsiStream); - cf.SaveAs(tempFileName); - } - - ValidateAddedUserDefinedProperties(tempFileName, testNow); - } - - /// As Test_DOCUMENT_SUMMARY_INFO_ADD_CUSTOM, but adding user defined properties with the AddUserDefinedProperty function - [TestMethod] - public void Test_Add_User_Defined_Property() - { - const string tempFileName = nameof(Test_Add_User_Defined_Property); - File.Delete(tempFileName); - - // Test value for a VT_FILETIME property - DateTime testNow = DateTime.UtcNow; - - // english.presets.doc has a user defined property section, but no properties other than the codepage - using (CompoundFile cf = new CompoundFile("english.presets.doc")) - { - var dsiStream = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation"); - var co = dsiStream.AsOLEPropertiesContainer(); - var userProperties = co.UserDefinedProperties; - - userProperties.AddUserDefinedProperty(VTPropertyType.VT_LPSTR, "StringProperty").Value = "Hello"; - userProperties.AddUserDefinedProperty(VTPropertyType.VT_BOOL, "BooleanProperty").Value = true; - userProperties.AddUserDefinedProperty(VTPropertyType.VT_I4, "IntegerProperty").Value = 3456; - userProperties.AddUserDefinedProperty(VTPropertyType.VT_FILETIME, "DateProperty").Value = testNow; - userProperties.AddUserDefinedProperty(VTPropertyType.VT_R8, "DoubleProperty").Value = 1.234567d; - - co.Save(dsiStream); - cf.SaveAs(tempFileName); - } - - ValidateAddedUserDefinedProperties(tempFileName, testNow); - } - - // Validate that the user defined properties added by Test_DOCUMENT_SUMMARY_INFO_ADD_CUSTOM / Test_Add_User_Defined_Property are as expected - private static void ValidateAddedUserDefinedProperties(string filePath, DateTime testFileTimeValue) - { - using CompoundFile cf = new(filePath); - OLEPropertiesContainer co = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation").AsOLEPropertiesContainer(); - OLEProperty[] propArray = co.UserDefinedProperties.Properties.ToArray(); - Assert.AreEqual(6, propArray.Length); - - // CodePage prop - Assert.AreEqual(1u, propArray[0].PropertyIdentifier); - Assert.AreEqual("0x00000001", propArray[0].PropertyName); - Assert.AreEqual((short)-535, propArray[0].Value); - - // User properties - Assert.AreEqual("StringProperty", propArray[1].PropertyName); - Assert.AreEqual("Hello", propArray[1].Value); - Assert.AreEqual(VTPropertyType.VT_LPSTR, propArray[1].VTType); - Assert.AreEqual("BooleanProperty", propArray[2].PropertyName); - Assert.AreEqual(true, propArray[2].Value); - Assert.AreEqual(VTPropertyType.VT_BOOL, propArray[2].VTType); - Assert.AreEqual("IntegerProperty", propArray[3].PropertyName); - Assert.AreEqual(3456, propArray[3].Value); - Assert.AreEqual(VTPropertyType.VT_I4, propArray[3].VTType); - Assert.AreEqual("DateProperty", propArray[4].PropertyName); - Assert.AreEqual(testFileTimeValue, propArray[4].Value); - Assert.AreEqual(VTPropertyType.VT_FILETIME, propArray[4].VTType); - Assert.AreEqual("DoubleProperty", propArray[5].PropertyName); - Assert.AreEqual(1.234567d, propArray[5].Value); - Assert.AreEqual(VTPropertyType.VT_R8, propArray[5].VTType); - } - - /// The names of user defined properties must be unique - adding a duplicate should throw. - [TestMethod] - public void Test_Add_User_Defined_Property_Should_Prevent_Duplicates() - { - using var cf = new CompoundFile("english.presets.doc"); - - CFStream dsiStream = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation"); - OLEPropertiesContainer co = dsiStream.AsOLEPropertiesContainer(); - OLEPropertiesContainer userProperties = co.UserDefinedProperties; - - userProperties.AddUserDefinedProperty(VTPropertyType.VT_LPSTR, "StringProperty"); - - ArgumentException exception = - Assert.ThrowsException(() => userProperties.AddUserDefinedProperty(VTPropertyType.VT_LPSTR, "stringproperty")); - - Assert.AreEqual("name", exception.ParamName); - } - - // Try to read a document which contains Vector/String properties - // refs https://github.com/ironfede/openmcdf/issues/98 - [TestMethod] - public void Test_SUMMARY_INFO_READ_LPWSTRING_VECTOR() - { - using CompoundFile cf = new CompoundFile("SampleWorkBook_bug98.xls"); - var co = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation").AsOLEPropertiesContainer(); - - var docPartsProperty = co.Properties.FirstOrDefault(property => property.PropertyIdentifier == 13); //13 == PIDDSI_DOCPARTS - - Assert.IsNotNull(docPartsProperty); - - var docPartsValues = docPartsProperty.Value as IList; - Assert.AreEqual(3, docPartsValues.Count); - Assert.AreEqual("Sheet1", docPartsValues[0]); - Assert.AreEqual("Sheet2", docPartsValues[1]); - Assert.AreEqual("Sheet3", docPartsValues[2]); - } - - [TestMethod] - public void Test_CLSID_PROPERTY() - { - var guid = new Guid("15891a95-bf6e-4409-b7d0-3a31c391fa31"); - using CompoundFile cf = new CompoundFile("CLSIDPropertyTest.file"); - var co = cf.RootStorage.GetStream("\u0005C3teagxwOttdbfkuIaamtae3Ie").AsOLEPropertiesContainer(); - var clsidProp = co.Properties.First(x => x.PropertyName == "DocumentID"); - Assert.AreEqual(guid, clsidProp.Value); - } - - // The test file 'report.xls' contains a DocumentSummaryInfo section, but no user defined properties. - // This tests adding a new user defined properties section to the existing DocumentSummaryInfo. - [TestMethod] - public void Test_ADD_USER_DEFINED_PROPERTIES_SECTION() - { - File.Delete("test_add_user_defined_properties.xls"); - - using (CompoundFile cf = new CompoundFile("report.xls")) - { - var dsiStream = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation"); - var co = dsiStream.AsOLEPropertiesContainer(); - - Assert.IsFalse(co.HasUserDefinedProperties); - Assert.IsNull(co.UserDefinedProperties); - - var newUserDefinedProperties = co.CreateUserDefinedProperties(65001); // 65001 - UTF-8 - - newUserDefinedProperties.PropertyNames[2] = "MyCustomProperty"; - - var newProperty = co.NewProperty(VTPropertyType.VT_LPSTR, 2); - newProperty.Value = "Testing"; - newUserDefinedProperties.AddProperty(newProperty); - - co.Save(dsiStream); - cf.SaveAs("test_add_user_defined_properties.xls"); - } - - using (CompoundFile cf = new CompoundFile("test_add_user_defined_properties.xls")) - { - var co = cf.RootStorage.GetStream("\u0005DocumentSummaryInformation").AsOLEPropertiesContainer(); - - // User defined properties should be present now - Assert.IsTrue(co.HasUserDefinedProperties); - Assert.IsNotNull(co.UserDefinedProperties); - Assert.AreEqual(65001, co.UserDefinedProperties.Context.CodePage); - - // And the expected properties should the there - var propArray = co.UserDefinedProperties.Properties.ToArray(); - Assert.AreEqual(propArray.Length, 2); - - // CodePage prop - Assert.AreEqual(1u, propArray[0].PropertyIdentifier); - Assert.AreEqual("0x00000001", propArray[0].PropertyName); - Assert.AreEqual((short)-535, propArray[0].Value); - - // User properties - Assert.AreEqual("MyCustomProperty", propArray[1].PropertyName); - Assert.AreEqual("Testing", propArray[1].Value); - Assert.AreEqual(VTPropertyType.VT_LPSTR, propArray[1].VTType); - } - } - - // A test for the issue described in https://github.com/ironfede/openmcdf/issues/134 where modifying an AppSpecific stream - // removes any already-existing Dictionary property - [TestMethod] - public void Test_Retain_Dictionary_Property_In_AppSpecific_Streams() - { - File.Delete("Issue134RoundTrip.cfs"); - - var expectedPropertyNames = new Dictionary() - { - [2] = "Document Number", - [3] = "Revision", - [4] = "Project Name" - }; - - using (CompoundFile cf = new CompoundFile("Issue134.cfs")) - { - var testStream = cf.RootStorage.GetStream("Issue134"); - var co = testStream.AsOLEPropertiesContainer(); - - CollectionAssert.AreEqual(expectedPropertyNames, co.PropertyNames); - - // Write test file - co.Save(testStream); - cf.SaveAs("Issue134RoundTrip.cfs"); - } - - // Open test file, and check that the property names are still as expected. - using (CompoundFile cf = new CompoundFile("Issue134RoundTrip.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.Default)) - { - var co = cf.RootStorage.GetStream("Issue134").AsOLEPropertiesContainer(); - CollectionAssert.AreEqual(expectedPropertyNames, co.PropertyNames); - } - } - - [TestMethod] - public void Test_FIX_CRASH() - { - using CompoundFile cf = new(CFSVersion.Ver_4, CFSConfiguration.Default); - - CFStream cfStream = cf.RootStorage.AddStream("MyStream"); - using Stream stream = cfStream.AsIOStream(); - const int BufferLength = 4096 * 21; - var buffer = Helpers.GetBuffer(BufferLength); - stream.Write(buffer, 0, buffer.Length); - stream.Position = 0; - - Assert.AreEqual(BufferLength, cfStream.Size); - - using MemoryStream memoryStream = new(); - using BufferedStream bufferedStream = new(stream); - bufferedStream.CopyTo(memoryStream); - - Assert.AreEqual(memoryStream.Length, cfStream.Size); - - } - } -} diff --git a/sources/Test/OpenMcdf.Extensions.Test/OpenMcdf.Extensions.Test.csproj b/sources/Test/OpenMcdf.Extensions.Test/OpenMcdf.Extensions.Test.csproj deleted file mode 100644 index 72d0b863..00000000 --- a/sources/Test/OpenMcdf.Extensions.Test/OpenMcdf.Extensions.Test.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - net45;net6.0 - false - false - false - false - false - false - false - false - - - - - - - - - - - - - - - - False - PreserveNewest - - - \ No newline at end of file diff --git a/sources/Test/OpenMcdf.Extensions.Test/Properties/AssemblyInfo.cs b/sources/Test/OpenMcdf.Extensions.Test/Properties/AssemblyInfo.cs deleted file mode 100644 index d0e94e39..00000000 --- a/sources/Test/OpenMcdf.Extensions.Test/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("OpenMcdfExtensionsTest")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("-")] -[assembly: AssemblyProduct("OpenMcdfExtensionsTest")] -[assembly: AssemblyCopyright("Copyright © Federico Blaseotto - 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("80a1f6c3-2533-4e2b-9c49-46dd8e3a1e33")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/sources/Test/OpenMcdf.MemTest/OpenMcdf.MemTest.csproj b/sources/Test/OpenMcdf.MemTest/OpenMcdf.MemTest.csproj deleted file mode 100644 index c6e34c64..00000000 --- a/sources/Test/OpenMcdf.MemTest/OpenMcdf.MemTest.csproj +++ /dev/null @@ -1,46 +0,0 @@ - - - net48 - Exe - OpenMcdfMemTest - OpenMcdfMemTest - http://localhost/OpenMcdfMemTest/ - true - Web - true - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - true - false - true - false - AllRules.ruleset - False - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - - \ No newline at end of file diff --git a/sources/Test/OpenMcdf.MemTest/Program.cs b/sources/Test/OpenMcdf.MemTest/Program.cs deleted file mode 100644 index 0d8ff744..00000000 --- a/sources/Test/OpenMcdf.MemTest/Program.cs +++ /dev/null @@ -1,290 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -//This project is used for profiling memory and performances of OpenMCDF . - -namespace OpenMcdf.MemTest -{ - static class Program - { - static void Main(string[] args) - { - //TestMultipleStreamCommit(); - TestCode(); - //StressMemory(); - //DummyFile(); - //Console.WriteLine("CLOSED"); - //Console.ReadKey(); - } - - private static void TestCode() - { - const int N_FACTOR = 1000; - - byte[] bA = GetBuffer(20 * 1024 * N_FACTOR, 0x0A); - byte[] bB = GetBuffer(5 * 1024, 0x0B); - byte[] bC = GetBuffer(5 * 1024, 0x0C); - byte[] bD = GetBuffer(5 * 1024, 0x0D); - byte[] bE = GetBuffer(8 * 1024 * N_FACTOR + 1, 0x1A); - byte[] bF = GetBuffer(16 * 1024 * N_FACTOR, 0x1B); - byte[] bG = GetBuffer(14 * 1024 * N_FACTOR, 0x1C); - byte[] bH = GetBuffer(12 * 1024 * N_FACTOR, 0x1D); - byte[] bE2 = GetBuffer(8 * 1024 * N_FACTOR, 0x2A); - byte[] bMini = GetBuffer(1027, 0xEE); - - Stopwatch sw = new Stopwatch(); - sw.Start(); - - var cf = new CompoundFile(CFSVersion.Ver_3, CFSConfiguration.SectorRecycle); - cf.RootStorage.AddStream("A").SetData(bA); - cf.SaveAs("OneStream.cfs"); - - cf.Close(); - - cf = new CompoundFile("OneStream.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle); - - cf.RootStorage.AddStream("B").SetData(bB); - cf.RootStorage.AddStream("C").SetData(bC); - cf.RootStorage.AddStream("D").SetData(bD); - cf.RootStorage.AddStream("E").SetData(bE); - cf.RootStorage.AddStream("F").SetData(bF); - cf.RootStorage.AddStream("G").SetData(bG); - cf.RootStorage.AddStream("H").SetData(bH); - - cf.SaveAs("8_Streams.cfs"); - - cf.Close(); - - File.Copy("8_Streams.cfs", "6_Streams.cfs", true); - - cf = new CompoundFile("6_Streams.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors); - cf.RootStorage.Delete("D"); - cf.RootStorage.Delete("G"); - cf.Commit(); - - cf.Close(); - - File.Copy("6_Streams.cfs", "6_Streams_Shrinked.cfs", true); - - cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); - cf.RootStorage.AddStream("ZZZ").SetData(bF); - cf.RootStorage.GetStream("E").Append(bE2); - cf.Commit(); - cf.Close(); - - cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); - cf.RootStorage.CLSID = new Guid("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"); - cf.Commit(); - cf.Close(); - - cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); - cf.RootStorage.AddStorage("MyStorage").AddStream("ANS").Append(bE); - cf.Commit(); - cf.Close(); - - cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); - cf.RootStorage.AddStorage("AnotherStorage").AddStream("ANS").Append(bE); - cf.RootStorage.Delete("MyStorage"); - cf.Commit(); - cf.Close(); - - CompoundFile.ShrinkCompoundFile("6_Streams_Shrinked.cfs"); - - cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); - cf.RootStorage.AddStorage("MiniStorage").AddStream("miniSt").Append(bMini); - cf.RootStorage.GetStorage("MiniStorage").AddStream("miniSt2").Append(bMini); - cf.Commit(); - cf.Close(); - - cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); - cf.RootStorage.GetStorage("MiniStorage").Delete("miniSt"); - - cf.RootStorage.GetStorage("MiniStorage").GetStream("miniSt2").Append(bE); - cf.Commit(); - cf.Close(); - - cf = new CompoundFile("6_Streams_Shrinked.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle); - - var myStream = cf.RootStorage.GetStream("C"); - var data = myStream.GetData(); - Console.WriteLine(data[0] + " : " + data[data.Length - 1]); - - myStream = cf.RootStorage.GetStream("B"); - data = myStream.GetData(); - Console.WriteLine(data[0] + " : " + data[data.Length - 1]); - - cf.Close(); - - sw.Stop(); - Console.WriteLine(sw.ElapsedMilliseconds); - - Console.ReadKey(); - } - - private static void StressMemory() - { - const int N_LOOP = 20; - const int MB_SIZE = 10; - - byte[] b = GetBuffer(1024 * 1024 * MB_SIZE); //2GB buffer - byte[] cmp = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 }; - - CompoundFile cf = new CompoundFile(CFSVersion.Ver_4, CFSConfiguration.Default); - _ = cf.RootStorage.AddStream("MySuperLargeStream"); - cf.SaveAs("LARGE.cfs"); - cf.Close(); - - //Console.WriteLine("Closed save"); - //Console.ReadKey(); - - cf = new CompoundFile("LARGE.cfs", CFSUpdateMode.Update, CFSConfiguration.Default); - CFStream cfst = cf.RootStorage.GetStream("MySuperLargeStream"); - - Stopwatch sw = new Stopwatch(); - sw.Start(); - for (int i = 0; i < N_LOOP; i++) - { - cfst.Append(b); - cf.Commit(true); - - Console.WriteLine(" Updated " + i.ToString()); - //Console.ReadKey(); - } - - cfst.Append(cmp); - cf.Commit(true); - sw.Stop(); - - cf.Close(); - - Console.WriteLine(sw.Elapsed.TotalMilliseconds); - sw.Reset(); - - //Console.WriteLine(sw.Elapsed.TotalMilliseconds); - - //Console.WriteLine("Closed Transacted"); - //Console.ReadKey(); - - cf = new CompoundFile("LARGE.cfs"); - int count = 8; - sw.Reset(); - sw.Start(); - byte[] data = new byte[count]; - count = cf.RootStorage.GetStream("MySuperLargeStream").Read(data, b.Length * (long)N_LOOP, count); - sw.Stop(); - Console.Write(count); - cf.Close(); - - Console.WriteLine("Closed Final " + sw.ElapsedMilliseconds); - Console.ReadKey(); - } - - private static void DummyFile() - { - Console.WriteLine("Start"); - FileStream fs = new FileStream("myDummyFile", FileMode.Create); - fs.Close(); - - Stopwatch sw = new Stopwatch(); - - byte[] b = GetBuffer(1024 * 1024 * 50); //2GB buffer - - fs = new FileStream("myDummyFile", FileMode.Open); - sw.Start(); - for (int i = 0; i < 42; i++) - { - fs.Seek(b.Length * i, SeekOrigin.Begin); - fs.Write(b, 0, b.Length); - } - - fs.Close(); - sw.Stop(); - Console.WriteLine("Stop - " + sw.ElapsedMilliseconds); - sw.Reset(); - - Console.ReadKey(); - } - - private static void AddNodes(string depth, CFStorage cfs) - { - void va(CFItem target) - { - string temp = target.Name + (target is CFStorage ? "" : " (" + target.Size + " bytes )"); - - //Stream - - Console.WriteLine(depth + temp); - - if (target is CFStorage) - { //Storage - string newDepth = depth + " "; - - //Recursion into the storage - AddNodes(newDepth, (CFStorage)target); - } - } - - //Visit NON-recursively (first level only) - cfs.VisitEntries(va, false); - } - - public static void TestMultipleStreamCommit() - { - string srcFilename = Directory.GetCurrentDirectory() + @"\testfile\report.xls"; - string dstFilename = Directory.GetCurrentDirectory() + @"\testfile\reportOverwriteMultiple.xls"; - //Console.WriteLine(Directory.GetCurrentDirectory()); - //Console.ReadKey(); - File.Copy(srcFilename, dstFilename, true); - - CompoundFile cf = new CompoundFile(dstFilename, CFSUpdateMode.Update, CFSConfiguration.SectorRecycle); - - Random r = new Random(); - - var stopwatch = Stopwatch.StartNew(); - - for (int i = 0; i < 1000; i++) - { - byte[] buffer = GetBuffer(r.Next(100, 3500), 0x0A); - - if (i > 0) - { - if (r.Next(0, 100) > 50) - { - cf.RootStorage.Delete("MyNewStream" + (i - 1).ToString()); - } - } - - CFStream addedStream = cf.RootStorage.AddStream("MyNewStream" + i.ToString()); - - addedStream.SetData(buffer); - - // Random commit, not on single addition - if (r.Next(0, 100) > 50) - cf.Commit(); - } - - cf.Close(); - - Console.WriteLine($"Elapsed: {stopwatch.Elapsed}"); - } - - private static byte[] GetBuffer(int count) - { - Random r = new Random(); - byte[] b = new byte[count]; - r.NextBytes(b); - return b; - } - - private static byte[] GetBuffer(int count, byte c) - { - byte[] b = new byte[count]; - for (int i = 0; i < b.Length; i++) - { - b[i] = c; - } - - return b; - } - } -} diff --git a/sources/Test/OpenMcdf.MemTest/Properties/AssemblyInfo.cs b/sources/Test/OpenMcdf.MemTest/Properties/AssemblyInfo.cs deleted file mode 100644 index 502534f6..00000000 --- a/sources/Test/OpenMcdf.MemTest/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("OpenMcdfMemTest")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Federico Blaseotto")] -[assembly: AssemblyProduct("OpenMcdfMemTest")] -[assembly: AssemblyCopyright("Copyright © 2010-2011, Federico Blaseotto")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("6be95c7d-5e29-4e84-b1d0-ac5a0de6bfe2")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.5.0.*")] diff --git a/sources/Test/OpenMcdf.MemTest/app.config b/sources/Test/OpenMcdf.MemTest/app.config deleted file mode 100644 index 786a845b..00000000 --- a/sources/Test/OpenMcdf.MemTest/app.config +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/sources/Test/OpenMcdf.MemTest/testfile/report.xls b/sources/Test/OpenMcdf.MemTest/testfile/report.xls deleted file mode 100644 index 7b667a5077161aeba1d12f02944707b21581b2f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16896 zcmeHOdvH|M8UOBPlPnJ*f$)$=vIfF4i6ILJA}kL<8N*`)2RrIu2)mFH6AZz^jFiyY z{!yu;P@x4{>uaV}>jU3YTcOj@nRacRR%?BA&{1n^A0xHX>Zse_@0`86_ny7??rzmi zaX6WC_ulWG^ZUN@ec$=+dF9z(SD(1`fibU%krqgqe4HL26%M+EduElYM8@HU&mX7L zX-yQt?dfz1Y2YKsS_k8mA%~I6k!kZvvX(T+xR!j)h zq7hz2j;X6G(?9z9{h!bKa^2EjTI2C}Oc#@N;NB}+WP_4pi?eu9_roM)(Qj=Tim61_ ze~jsp<+4V$%T@SmS8ZPoEhLppr1VKA?z;m(r+`0WAo~wx2q@}$^>*f|T+RoF4kbfE zS)ep|=77K5_G0-{Uy)289}qz=t|*tg>Yk}Qj%Z$im#;}k?hhZwRpfQ#%VjJ3_~N!j zYnHBByLMG{+0yfuUc7wCs_18z7AqOrEy{2Vvd*_EAYBN0K7wl%;tJou`nV2P*ZWl2 zC)9O5-kh+8%HCyPubYDs{7zjOTByo>Q(f!TbqKDOJgVxvq$o3_R$f8*h}=jGh2-^0 zYY9A|3!bn69?~iQPbD_P_vXkGR6&U zV2SezmRL}rge7r9AEu<1%q&nsP$Jm+7QTyat(#o<)eOc6;TsuT!h#nT;xfP&qc$8v zY#t?6z1$P6M<rZzku1AuXU)h?5ZB_SK-iU5E(Mi1k?0h+-( zh`WAY8(xm4*9Susmc$0TTBgZtvDUjrKhJavR?Mc5)-%WLN)HcZ-h^*a`p>8T$cKKH5B**r`e7gXeLnPieCXfxp5`I+C%Mn2o{og2{lI@>Uedvuo^l6@S z&NCIh5l=WrRrIX?Yk#h^>Cee(++wP$<>A~`mDhAMU(-3?&CsK`Me_~1!I$%4RX(d9 z?f;w`XK=2>EvCR4hjVB}&&qjsMsFGVk?QJ^gGNHnhZLQ2Z5vaA4$^h9C1i;lVQ$T0I5{w zq}1{FH9nH0zLN#1%rtTL?%nB@%shl+4cCwI(hPBo>1|4JWnXnOrc0Kg(58@6SQOsHPt252fKXK?IqNwrzx{H&W2FB4^aDg zLfK|X_gh&_<+p5Fwq^8xCQp~X^Ugc7Ahr*Ddg?Yi=6r~uJ0@EOySRNIzZG06)K0@z z+59=+Qi05I$E_epsx26VTQfNjJb3FQ7-V8G zAbOk8zd4+kh@Z2ChwzO|{9UHJ?p=02@6AQEbW+$&;_X;>~85$tIT1W>NqfJxft+DiX;*zx%2; zo8cy#nfYv{1+d{frc4x@%0%+b6Myk$Q)9B3mCvR*fDNY$88#8v=5PP-W;4QMGdrJ6 zYXBQPtW~WUkVt;?rx(51j3k?x!JbwJHZzOxv{c(;sW-gYj567Hc-n*jHcn4VwY~ND zW8Q2=n`}HhZE^q`r>CXbUU=gPZ#HL|Y&<+|dH@@zr={9b$6oSgGsa}&;b~_FuyJ}? zs_l_i-}YuR)@0-1X|Vt{PESj}S6_WJ4^NvJz{crmt+5Y|-RRYt!Pw!&xGVeJ zTx5JV$EPcOzm+1Z_WZ;fbV3dk9%X`_Wq>-p2wNYkS9pU?%z?sxOwa}c)af%KhkyJ9 zZ_tJuD7?i4ooawOJw&8`<89uclX9T&4HLA<0CoC<$VK-&%cNE(1=XI*Hsp`Z`>D|>$bc#HeDu*vD~;Z`vorNq6k(9IXdD!mk~3OqzdO3u!g^+ zy{lt;S9@P)Pxrpa9yefUWO|V3!%;lI)&=O?fw@XGk{~FTvup-jUQ!GyaEg>Kxt?J` z4bG>C>uG@NX@6)amPD}|v1D_iV@q^NqN{6PWVa#LsPsU{wHbrK4je6v;ywZCy3p*L zR9uQugFkR7F-Q{bWm}pKPi0(|#=4PVX;E?~6p;qLi0`$r7CK(AtRWch zV+L_yqJ3j$_ogW33WlI%RnY8(5Qx6t2|+hOKs`rLE`SbGw|tj|qbpWIG93u_hlXI1 z;Q&YMGr_Apz;*od0Z+$&aO@9g2*G1caKu0pe3%Egj)OkndT0<*;4_`zh=oQ#C>!np z&UmOTR_RpB{?I^~1w+d6?ycM53ftW#3)P@KJUhD;zDT3zI$9d*{p1#Ds$Obn?XdwCvG5R1B*BUwNQKDd>+74-4WX=Uz{bYwU{bYwU z{j@(cNahL_wN`EKQx^Y{!E^+mS1BFj+uRxQ86jOZCm|<^ezGOePqrlbiJ|P={&MJU zqFaRWau{MY+T5F+_)}$c)p>N)wVi!k(AErht+Jso&sum#9}Gu*<$JqDi1QRgPai$N zSbHctQ$bMS+WKMTRIc>}>fMeby0!KbU94K>!$z{My|48-K{{|84P*&eJ-@vDc2|u%9UUb z+Jvttms4iTl@>QqzJ^vca7O~3v1W53(KmyXm${>9Q(8%BX8^P*u|tv3a>yZarc6@*Fhry4?gHa@>Q-w@`FM(e614e&VvrNBcFj* zvHyjj-J$BfTD7wZl79HZcYm{D!|M2YMW3eV)7AAWiqelVc4~mvi!@q$@aA}!yhb2@ zRh7O@T_>S*Ql+=zlO6X{vM6uIr#kL$#l(P!7UZ*_VQL|!(r@GTZ=W2&5aUQr4AgL^ z^Z4~q=@LkRg7}CrQ3v`O2~}n88#BbA@{!V`{bj6&8lUZHqSh0D$lvYM97uJl)o0!|%^v4OUw>|YGp|4WP6 zI>=Z-Jub`nUi zpFa(JjKM>Bh#ufh^otW;yXEBjt2S5NeN#lHPk!)4eBIvF0gvAP+~rADNuqKqYRf@}rjjPi2MQF@&$^?C9;;)|2duF5S_Q=u)ys z^0q6VP5r`Ji1+=y?_ACM%qIgB?-bPj(DTncf24VA)&7HMf8+c2vi-kD=57_+zZ#iG z1TRJA?mOGhV~-rfQ%25%*zRkQIT*PPnflm^O#M8J?9>O;f}Jeg7g>T>On$$$V8Zs% z9(pjAyKT+g#$+%Gk`a|ouysAXTeflZwoTP9q$AYuO%@_U83F`*hWWpr+~3XFX;m`W z*^$uZing}2#G+X-RK!`_nM}^X(2)xduxPVtQS%&mZ|>2nec7LW@d-6xWmcTFVlKBq izF0CE{{zz*6^j92|LHBJV3B1?{{I1vYc3lA diff --git a/sources/Test/OpenMcdf.MemTest/testfile/reportOverwriteMultiple.xls b/sources/Test/OpenMcdf.MemTest/testfile/reportOverwriteMultiple.xls deleted file mode 100644 index 3e4848645186c1f602e161c4c4177034e7e84109..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1832960 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 rKm}C5YX#hT9QWGVX - - net48 - Exe - http://localhost/OpenMcdfPerfTest/ - true - Web - true - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - true - false - true - false - AllRules.ruleset - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - - - - - - \ No newline at end of file diff --git a/sources/Test/OpenMcdf.PerfTest/Program.cs b/sources/Test/OpenMcdf.PerfTest/Program.cs deleted file mode 100644 index f002f3a7..00000000 --- a/sources/Test/OpenMcdf.PerfTest/Program.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; - -namespace OpenMcdf.PerfTest -{ - static class Program - { - const int MAX_STREAM_COUNT = 5000; - const string fileName = "PerfLoad.cfs"; - - static void Main(string[] args) - { - File.Delete(fileName); - if (!File.Exists(fileName)) - { - CreateFile(fileName); - } - - CompoundFile cf = new CompoundFile(fileName); - var stopwatch = Stopwatch.StartNew(); - _ = cf.RootStorage.GetStream("Test1"); - Console.WriteLine($"Elapsed: {stopwatch.Elapsed}"); - Console.Read(); - } - - private static void CreateFile(string fn) - { - CompoundFile cf = new CompoundFile(); - for (int i = 0; i < MAX_STREAM_COUNT; i++) - { - cf.RootStorage.AddStream("Test" + i.ToString()).SetData(Helpers.GetBuffer(300)); - } - - cf.SaveAs(fileName); - cf.Close(); - } - } -} diff --git a/sources/Test/OpenMcdf.PerfTest/Properties/AssemblyInfo.cs b/sources/Test/OpenMcdf.PerfTest/Properties/AssemblyInfo.cs deleted file mode 100644 index 6f9a4b01..00000000 --- a/sources/Test/OpenMcdf.PerfTest/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("OpenMcdfPerfTest")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("-")] -[assembly: AssemblyProduct("OpenMcdfPerfTest")] -[assembly: AssemblyCopyright("Copyright © - 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c3a84d86-d1be-4fdc-8294-169e1f6747d8")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/sources/Test/OpenMcdf.PerfTest/app.config b/sources/Test/OpenMcdf.PerfTest/app.config deleted file mode 100644 index 786a845b..00000000 --- a/sources/Test/OpenMcdf.PerfTest/app.config +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/sources/Test/OpenMcdf.Test/AuthoringTests.txt b/sources/Test/OpenMcdf.Test/AuthoringTests.txt deleted file mode 100644 index c94b3cfd..00000000 --- a/sources/Test/OpenMcdf.Test/AuthoringTests.txt +++ /dev/null @@ -1,136 +0,0 @@ -========================================================================== - Visual Studio Team System: Overview of Authoring and Running Tests -========================================================================== - -This overview describes the features for authoring and running tests in -Visual Studio Team System and Visual Studio Team Edition for Software Testers. - -Opening Tests -------------- -To open a test, open a test project or a test metadata file (a file with -extension .vsmdi) that contains the definition of the test. You can find -test projects and metadata files in Solution Explorer. - -Viewing Tests -------------- -To see which tests are available to you, open the Test View window. Or, -if you have installed Team Edition for Software Testers, you can also open -the Test List Editor window to view tests. - -To open the Test View window, click the Test menu, point to Windows, and -then click Test View. To open the Test List Editor window (if you have -installed Team Edition for Software Testers), click Test, point to Windows, -and then click Test List Editor. - -Running Tests -------------- -You can run tests from the Test View window and the Test List Editor window. -See Viewing Tests to learn how to open these windows. To run one or more -tests displayed in the Test View window, first select the tests in that -window; to select multiple tests, hold either the Shift or CTRL key while -clicking tests. Then click the Run Tests button in the Test View window -toolbar. - -If you have installed Visual Studio Team Edition for Software Testers, you can -also use the Test List Editor window to run tests. To run tests in Test List Editor, -select the check box next to each test that you want to run. Then click the -Run Tests button in the Test List Editor window toolbar. - -Viewing Test Results --------------------- -When you run a test or a series of tests, the results of the test run will be -shown in the Test Results window. Each individual test in the run is shown on -a separate line so that you can see its status. The window contains an -embedded status bar in the top half of the window that provides you with -summary details of the complete test run. - -To see more detailed results for a particular test result, double-click it in -the Test Results window. This opens a window that provides more information -about the particular test result, such as any specific error messages returned -by the test. - -Changing the way that tests are run ------------------------------------ -Each time you run one or more tests, a collection of settings is used to -determine how those tests are run. These settings are contained in a “test -run configuration” file. - -Here is a partial list of the changes you can make with a test run -configuration file: - - - Change the naming scheme for each test run. - - Change the test controller that the tests are run on so that you can run - tests remotely. - - Gather code coverage data for the code being tested so that you can see - which lines of code are covered by your tests. - - Enable and disable test deployment. - - Specify additional files to deploy before tests are run. - - Select a different host, ASP.NET, for running ASP.NET unit tests. - - Select a different host, the smart device test host, for running smart device unit tests. - - Set various properties for the test agents that run your tests. - - Run custom scripts at the start and end of each test run so that you can - set up the test environment exactly as required each time tests are run. - - Set time limits for tests and test runs. - - Set the browser mix and the number of times to repeat Web tests in the - test run. - -By default, a test run configuration file is created whenever you create a -new test project. You make changes to this file by double-clicking it in -Solution Explorer and then changing its settings. (Test run configuration -files have the extension .testrunconfig.) - -A solution can contain multiple test run configuration files. Only one of -those files, known as the “Active” test run configuration file, is used to -determine the settings that are currently used for test runs. You select -the active test run configuration by clicking Select Active Test Run -Configuration on the Test menu. - -------------------------------------------------------------------------------- - -Test Types ----------- -Using Visual Studio Team Edition for Software Testers, you can create a number -of different test types: - -Unit test: Use a unit test to create a programmatic test in C++, Visual C# or -Visual Basic that exercises source code. A unit test calls the methods of a -class, passing suitable parameters, and verifies that the returned value is -what you expect. -There are three specialized variants of unit tests: - - Data-driven unit tests are created when you configure a unit test to be - called repeatedly for each row of a data source. The data from each row - is used by the unit test as input data. - - ASP.NET unit tests are unit tests that exercise code in an ASP.NET Web - application. - - Smart device unit tests are unit tests that are deployed to a smart device - or emulator and then executed by the smart device test host. - -Web Test: Web tests consist of an ordered series of HTTP requests that you -record in a browser session using Microsoft Internet Explorer. You can have -the test report specific details about the pages or sites it requests, such -as whether a particular page contains a specified string. - -Load Test: You use a load test to encapsulate non-manual tests, such as -unit, Web, and generic tests, and then run them simultaneously by using -virtual users. Running these tests under load generates test results, -including performance and other counters, in tables and in graphs. - -Generic test: A generic test is an existing program wrapped to function as a -test in Visual Studio. The following are examples of tests or programs that -you can turn into generic tests: - - An existing test that uses process exit codes to communicate whether the - test passed or failed. 0 indicates passing and any other value indicates - a failure. - - A general program to obtain specific functionality during a test scenario. - - A test or program that uses a special XML file (called a “summary results - file”), to communicate detailed results. - -Manual test: The manual test type is used when the test tasks are to be -completed by a test engineer as opposed to an automated script. - -Ordered test: Use an ordered test to execute a set of tests in an order you -specify. - -------------------------------------------------------------------------------- - - diff --git a/sources/Test/OpenMcdf.Test/CFStorageTest.cs b/sources/Test/OpenMcdf.Test/CFStorageTest.cs deleted file mode 100644 index 861208e0..00000000 --- a/sources/Test/OpenMcdf.Test/CFStorageTest.cs +++ /dev/null @@ -1,335 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.IO; - -namespace OpenMcdf.Test -{ - /// - /// Summary description for CFStorageTest - /// - [TestClass] - public class CFStorageTest - { - //const String OUTPUT_DIR = "C:\\TestOutputFiles\\"; - - public CFStorageTest() - { - } - - /// - /// Gets or sets the test context which provides - /// information about and functionality for the current test run. - /// - public TestContext TestContext { get; set; } - - #region Additional test attributes - // - // You can use the following additional attributes as you write your tests: - // - // Use ClassInitialize to run code before running the first test in the class - // [ClassInitialize()] - //public static void MyClassInitialize(TestContext testContext) - - // - // Use ClassCleanup to run code after all tests in a class have run - // [ClassCleanup()] - // public static void MyClassCleanup() { } - // - // Use TestInitialize to run code before running each test - // [TestInitialize()] - // public void MyTestInitialize() { } - // - // Use TestCleanup to run code after each test has run - // [TestCleanup()] - // public void MyTestCleanup() { } - // - #endregion - - [TestMethod] - public void Test_CREATE_STORAGE() - { - const string STORAGE_NAME = "NewStorage"; - using CompoundFile cf = new(); - - CFStorage st = cf.RootStorage.AddStorage(STORAGE_NAME); - - Assert.IsNotNull(st); - Assert.AreEqual(STORAGE_NAME, st.Name, false); - } - - [TestMethod] - public void Test_CREATE_STORAGE_WITH_CREATION_DATE() - { - const string STORAGE_NAME = "NewStorage1"; - using CompoundFile cf = new(); - - CFStorage st = cf.RootStorage.AddStorage(STORAGE_NAME); - st.CreationDate = DateTime.Now; - - Assert.IsNotNull(st); - Assert.AreEqual(STORAGE_NAME, st.Name, false); - - cf.SaveAs("ProvaData.cfs"); - } - - [TestMethod] - public void Test_VISIT_ENTRIES() - { - const string STORAGE_NAME = "report.xls"; - using CompoundFile cf = new(STORAGE_NAME); - - using FileStream output = new("LogEntries.txt", FileMode.Create); - using StreamWriter tw = new(output); - - void va(CFItem item) - { - tw.WriteLine(item.Name); - } - - cf.RootStorage.VisitEntries(va, true); - } - - [TestMethod] - public void Test_TRY_GET_STREAM_STORAGE() - { - string FILENAME = "MultipleStorage.cfs"; - using CompoundFile cf = new(FILENAME); - - cf.RootStorage.TryGetStorage("MyStorage", out CFStorage st); - Assert.IsNotNull(st); - - cf.RootStorage.TryGetStorage("IDONTEXIST", out CFStorage nf); - Assert.IsNull(nf); - - st.TryGetStream("MyStream", out CFStream s); - Assert.IsNotNull(s); - st.TryGetStream("IDONTEXIST2", out CFStream ns); - Assert.IsNull(ns); - - } - - [TestMethod] - public void Test_TRY_GET_STREAM_STORAGE_NEW() - { - string FILENAME = "MultipleStorage.cfs"; - using CompoundFile cf = new(FILENAME); - bool bs = cf.RootStorage.TryGetStorage("MyStorage", out CFStorage st); - - Assert.IsTrue(bs); - Assert.IsNotNull(st); - - bool nb = cf.RootStorage.TryGetStorage("IDONTEXIST", out CFStorage nf); - Assert.IsFalse(nb); - Assert.IsNull(nf); - - var b = st.TryGetStream("MyStream", out CFStream s); - Assert.IsNotNull(s); - b = st.TryGetStream("IDONTEXIST2", out CFStream ns); - Assert.IsFalse(b); - } - - [TestMethod] - public void Test_VISIT_ENTRIES_CORRUPTED_FILE_VALIDATION_ON() - { - using CompoundFile f = new("CorruptedDoc_bug3547815.doc", CFSUpdateMode.ReadOnly, CFSConfiguration.Default); - - Assert.ThrowsException(() => - { - using StreamWriter tw = new StreamWriter("LogEntriesCorrupted_1.txt"); - f.RootStorage.VisitEntries(item => tw.WriteLine(item.Name), true); - }); - } - - [TestMethod] - public void Test_VISIT_ENTRIES_CORRUPTED_FILE_VALIDATION_OFF_BUT_CAN_LOAD() - { - using CompoundFile f = new("CorruptedDoc_bug3547815_B.doc", CFSUpdateMode.ReadOnly, CFSConfiguration.NoValidationException); - - using StreamWriter tw = new("LogEntriesCorrupted_2.txt"); - f.RootStorage.VisitEntries(item => tw.WriteLine(item.Name), true); - } - - [TestMethod] - public void Test_VISIT_STORAGE() - { - string FILENAME = "testVisiting.xls"; - - // Remove... - File.Delete(FILENAME); - - //Create... - - using (CompoundFile ncf = new()) - { - CFStorage l1 = ncf.RootStorage.AddStorage("Storage Level 1"); - l1.AddStream("l1ns1"); - l1.AddStream("l1ns2"); - l1.AddStream("l1ns3"); - - CFStorage l2 = l1.AddStorage("Storage Level 2"); - l2.AddStream("l2ns1"); - l2.AddStream("l2ns2"); - - ncf.SaveAs(FILENAME); - } - - // Read... - - using CompoundFile cf = new(FILENAME); - using FileStream output = new("reportVisit.txt", FileMode.Create); - using StreamWriter sw = new(output); - - Console.SetOut(sw); - - void va(CFItem target) - { - sw.WriteLine(target.Name); - } - - cf.RootStorage.VisitEntries(va, true); - } - - [TestMethod] - public void Test_DELETE_DIRECTORY() - { - string FILENAME = "MultipleStorage2.cfs"; - using CompoundFile cf = new(FILENAME, CFSUpdateMode.ReadOnly, CFSConfiguration.Default); - - CFStorage st = cf.RootStorage.GetStorage("MyStorage"); - - Assert.IsNotNull(st); - - st.Delete("AnotherStorage"); - - cf.SaveAs("MultipleStorage_Delete.cfs"); - } - - [TestMethod] - public void Test_DELETE_MINISTREAM_STREAM() - { - string FILENAME = "MultipleStorage2.cfs"; - using CompoundFile cf = new(FILENAME); - - CFStorage found = null; - void action(CFItem item) { if (item.Name == "AnotherStorage") found = item as CFStorage; } - cf.RootStorage.VisitEntries(action, true); - - Assert.IsNotNull(found); - - found.Delete("AnotherStream"); - - cf.SaveAs("MultipleDeleteMiniStream"); - } - - [TestMethod] - public void Test_DELETE_STREAM() - { - string FILENAME = "MultipleStorage3.cfs"; - using CompoundFile cf = new(FILENAME); - - CFStorage found = null; - void action(CFItem item) - { - if (item.Name == "AnotherStorage") - found = item as CFStorage; - } - - cf.RootStorage.VisitEntries(action, true); - - Assert.IsNotNull(found); - - found.Delete("Another2Stream"); - - cf.SaveAs("MultipleDeleteStream"); - } - - [TestMethod] - public void Test_CHECK_DISPOSED_() - { - const string FILENAME = "MultipleStorage.cfs"; - using CompoundFile cf = new CompoundFile(FILENAME); - - CFStorage st = cf.RootStorage.GetStorage("MyStorage"); - cf.Close(); - - Assert.ThrowsException(() => st.GetStream("MyStream").GetData()); - } - - [TestMethod] - public void Test_LAZY_LOAD_CHILDREN_() - { - using (CompoundFile cf = new()) - { - cf.RootStorage.AddStorage("Level_1") - .AddStorage("Level_2") - .AddStream("Level2Stream") - .SetData(Helpers.GetBuffer(100)); - cf.SaveAs("$Hel1"); - } - - using (CompoundFile cf = new("$Hel1")) - { - IList i = cf.GetAllNamedEntries("Level2Stream"); - Assert.IsNotNull(i[0]); - Assert.IsTrue(i[0] is CFStream); - Assert.AreEqual(100, (i[0] as CFStream).GetData().Length); - cf.SaveAs("$Hel2"); - } - - File.Delete("$Hel1"); - File.Delete("$Hel2"); - } - - [TestMethod] - public void Test_FIX_BUG_31() - { - using (CompoundFile cf = new()) - { - cf.RootStorage.AddStorage("Level_1") - .AddStream("Level2Stream") - .SetData(Helpers.GetBuffer(100)); - - cf.SaveAs("$Hel3"); - } - - using CompoundFile cf1 = new("$Hel3"); - - Assert.ThrowsException(() => - { - CFStream cs = cf1.RootStorage.GetStorage("Level_1").AddStream("Level2Stream"); - }); - } - - [TestMethod] - public void Test_FIX_BUG_116() - { - using (CompoundFile cf = new()) - { - cf.RootStorage.AddStorage("AStorage") - .AddStream("AStream") - .SetData(Helpers.GetBuffer(100)); - - cf.SaveAs("Hello$File"); - } - - using (CompoundFile cf1 = new("Hello$File", CFSUpdateMode.Update, CFSConfiguration.Default)) - { - cf1.RootStorage.RenameItem("AStorage", "NewStorage"); - cf1.Commit(); - } - - using CompoundFile cf2 = new CompoundFile("Hello$File"); - var st2 = cf2.RootStorage.GetStorage("NewStorage"); - Assert.IsNotNull(st2); - } - - [TestMethod] - [ExpectedException(typeof(CFCorruptedFileException))] - public void Test_CORRUPTEDDOC_BUG36_SHOULD_THROW_CORRUPTED_FILE_EXCEPTION() - { - using CompoundFile file = new CompoundFile("CorruptedDoc_bug36.doc", CFSUpdateMode.ReadOnly, CFSConfiguration.NoValidationException); - //Many thanks to theseus for bug reporting - } - } -} diff --git a/sources/Test/OpenMcdf.Test/CFStreamTest.cs b/sources/Test/OpenMcdf.Test/CFStreamTest.cs deleted file mode 100644 index 4f76a617..00000000 --- a/sources/Test/OpenMcdf.Test/CFStreamTest.cs +++ /dev/null @@ -1,1051 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace OpenMcdf.Test -{ - /// - /// Summary description for UnitTest1 - /// - [TestClass] - public class CFStreamTest - - { - //const String TestContext.TestDir = "C:\\TestOutputFiles\\"; - - public CFStreamTest() - { - } - - /// - /// Gets or sets the test context which provides - /// information about and functionality for the current test run. - /// - public TestContext TestContext { get; set; } - - #region Additional test attributes - // - // You can use the following additional attributes as you write your tests: - // - // Use ClassInitialize to run code before running the first test in the class - // [ClassInitialize()] - // public static void MyClassInitialize(TestContext testContext) { } - // - // Use ClassCleanup to run code after all tests in a class have run - // [ClassCleanup()] - // public static void MyClassCleanup() { } - // - // Use TestInitialize to run code before running each test - // [TestInitialize()] - // public void MyTestInitialize() { } - // - // Use TestCleanup to run code after each test has run - // [TestCleanup()] - // public void MyTestCleanup() { } - // - #endregion - - [TestMethod] - public void Test_READ_STREAM() - { - string filename = "report.xls"; - - using CompoundFile cf = new(filename); - CFStream foundStream = cf.RootStorage.GetStream("Workbook"); - - byte[] temp = foundStream.GetData(); - - Assert.IsNotNull(temp); - Assert.IsTrue(temp.Length > 0); - } - - [TestMethod] - public void Test_WRITE_STREAM() - { - const int BUFFER_LENGTH = 10000; - - byte[] b = Helpers.GetBuffer(BUFFER_LENGTH); - - using CompoundFile cf = new(); - CFStream myStream = cf.RootStorage.AddStream("MyStream"); - - Assert.IsNotNull(myStream); - Assert.AreEqual(0, myStream.Size); - - myStream.SetData(b); - - Assert.AreEqual(BUFFER_LENGTH, myStream.Size, "Stream size differs from buffer size"); - } - - [TestMethod] - public void Test_WRITE_MINI_STREAM() - { - const int BUFFER_LENGTH = 1023; // < 4096 - - byte[] b = Helpers.GetBuffer(BUFFER_LENGTH); - - using CompoundFile cf = new(); - CFStream myStream = cf.RootStorage.AddStream("MyMiniStream"); - - Assert.IsNotNull(myStream); - Assert.AreEqual(0, myStream.Size); - - myStream.SetData(b); - - Assert.AreEqual(BUFFER_LENGTH, myStream.Size, "Mini Stream size differs from buffer size"); - } - - [TestMethod] - public void Test_ZERO_LENGTH_WRITE_STREAM() - { - byte[] b = new byte[0]; - - using CompoundFile cf = new(); - CFStream myStream = cf.RootStorage.AddStream("MyStream"); - - Assert.IsNotNull(myStream); - myStream.SetData(b); - cf.SaveAs("ZERO_LENGTH_STREAM.cfs"); - - File.Delete("ZERO_LENGTH_STREAM.cfs"); - } - - [TestMethod] - public void Test_ZERO_LENGTH_RE_WRITE_STREAM() - { - byte[] b = new byte[0]; - - using (CompoundFile cf = new()) - { - CFStream myStream = cf.RootStorage.AddStream("MyStream"); - - Assert.IsNotNull(myStream); - - myStream.SetData(b); - - cf.SaveAs("ZERO_LENGTH_STREAM_RE.cfs"); - } - - using (CompoundFile cfo = new("ZERO_LENGTH_STREAM_RE.cfs")) - { - CFStream oStream = cfo.RootStorage.GetStream("MyStream"); - - Assert.IsNotNull(oStream); - Assert.AreEqual(0, oStream.Size); - - oStream.SetData(Helpers.GetBuffer(30)); - cfo.SaveAs("ZERO_LENGTH_STREAM_RE2.cfs"); - } - - File.Delete("ZERO_LENGTH_STREAM_RE.cfs"); - - File.Delete("ZERO_LENGTH_STREAM_RE2.cfs"); - } - - [TestMethod] - public void Test_WRITE_STREAM_WITH_DIFAT() - { - //const int SIZE = 15388609; //Incredible condition of 'resonance' between FAT and DIFAT sec number - const int SIZE = 15345665; // 64 -> 65 NOT working (in the past ;-) ) - byte[] b = Helpers.GetBuffer(SIZE, 0); - - using (CompoundFile cf = new()) - { - CFStream myStream = cf.RootStorage.AddStream("MyStream"); - Assert.IsNotNull(myStream); - myStream.SetData(b); - - cf.SaveAs("WRITE_STREAM_WITH_DIFAT.cfs"); - } - - using (CompoundFile cf2 = new("WRITE_STREAM_WITH_DIFAT.cfs")) - { - CFStream st = cf2.RootStorage.GetStream("MyStream"); - - Assert.IsNotNull(cf2); - Assert.AreEqual(SIZE, st.Size); - - CollectionAssert.AreEqual(b, st.GetData()); - } - - File.Delete("WRITE_STREAM_WITH_DIFAT.cfs"); - } - - [TestMethod] - public void Test_WRITE_MINISTREAM_READ_REWRITE_STREAM() - { - const int BIGGER_SIZE = 350; - //const int SMALLER_SIZE = 290; - const int MEGA_SIZE = 18000000; - - byte[] ba1 = Helpers.GetBuffer(BIGGER_SIZE, 1); - byte[] ba2 = Helpers.GetBuffer(BIGGER_SIZE, 2); - byte[] ba3 = Helpers.GetBuffer(BIGGER_SIZE, 3); - byte[] ba4 = Helpers.GetBuffer(BIGGER_SIZE, 4); - byte[] ba5 = Helpers.GetBuffer(BIGGER_SIZE, 5); - - //WRITE 5 (mini)streams in a compound file -- - - using (CompoundFile cfa = new()) - { - CFStream myStream = cfa.RootStorage.AddStream("MyFirstStream"); - Assert.IsNotNull(myStream); - - myStream.SetData(ba1); - Assert.AreEqual(BIGGER_SIZE, myStream.Size); - - CFStream myStream2 = cfa.RootStorage.AddStream("MySecondStream"); - Assert.IsNotNull(myStream2); - - myStream2.SetData(ba2); - Assert.AreEqual(BIGGER_SIZE, myStream2.Size); - - CFStream myStream3 = cfa.RootStorage.AddStream("MyThirdStream"); - Assert.IsNotNull(myStream3); - - myStream3.SetData(ba3); - Assert.AreEqual(BIGGER_SIZE, myStream3.Size); - - CFStream myStream4 = cfa.RootStorage.AddStream("MyFourthStream"); - Assert.IsNotNull(myStream4); - - myStream4.SetData(ba4); - Assert.AreEqual(BIGGER_SIZE, myStream4.Size); - - CFStream myStream5 = cfa.RootStorage.AddStream("MyFifthStream"); - Assert.IsNotNull(myStream5); - - myStream5.SetData(ba5); - Assert.AreEqual(BIGGER_SIZE, myStream5.Size); - - cfa.SaveAs("WRITE_MINISTREAM_READ_REWRITE_STREAM.cfs"); - } - - // Now get the second stream and rewrite it smaller - byte[] bb = Helpers.GetBuffer(MEGA_SIZE); - using (CompoundFile cfb = new("WRITE_MINISTREAM_READ_REWRITE_STREAM.cfs")) - { - CFStream myStreamB = cfb.RootStorage.GetStream("MySecondStream"); - Assert.IsNotNull(myStreamB); - myStreamB.SetData(bb); - Assert.AreEqual(MEGA_SIZE, myStreamB.Size); - - byte[] bufferB = myStreamB.GetData(); - CollectionAssert.AreEqual(bb, bufferB); - cfb.SaveAs("WRITE_MINISTREAM_READ_REWRITE_STREAM_2ND.cfs"); - } - - using (CompoundFile cfc = new("WRITE_MINISTREAM_READ_REWRITE_STREAM_2ND.cfs")) - { - CFStream myStreamC = cfc.RootStorage.GetStream("MySecondStream"); - Assert.AreEqual(MEGA_SIZE, myStreamC.Size, "DATA SIZE FAILED"); - - byte[] bufferC = myStreamC.GetData(); - CollectionAssert.AreEqual(bb, bufferC); - } - - File.Delete("WRITE_MINISTREAM_READ_REWRITE_STREAM.cfs"); - - File.Delete("WRITE_MINISTREAM_READ_REWRITE_STREAM_2ND.cfs"); - } - - [TestMethod] - public void Test_RE_WRITE_SMALLER_STREAM() - { - const int BUFFER_LENGTH = 8000; - - string filename = "report.xls"; - - byte[] b = Helpers.GetBuffer(BUFFER_LENGTH); - - using (CompoundFile cf = new(filename)) - { - CFStream foundStream = cf.RootStorage.GetStream("Workbook"); - foundStream.SetData(b); - cf.SaveAs("reportRW_SMALL.xls"); - } - - using (CompoundFile cf = new("reportRW_SMALL.xls")) - { - byte[] c = cf.RootStorage.GetStream("Workbook").GetData(); - Assert.AreEqual(BUFFER_LENGTH, c.Length); - } - - File.Delete("reportRW_SMALL.xls"); - } - - [TestMethod] - public void Test_RE_WRITE_SMALLER_MINI_STREAM() - { - string filename = "report.xls"; - - byte[] b; - using (CompoundFile cf = new(filename)) - { - CFStream foundStream = cf.RootStorage.GetStream("\x05SummaryInformation"); - int TEST_LENGTH = (int)foundStream.Size - 20; - b = Helpers.GetBuffer(TEST_LENGTH); - foundStream.SetData(b); - - cf.SaveAs("RE_WRITE_SMALLER_MINI_STREAM.xls"); - } - - using (CompoundFile cf = new("RE_WRITE_SMALLER_MINI_STREAM.xls")) - { - byte[] c = cf.RootStorage.GetStream("\x05SummaryInformation").GetData(); - CollectionAssert.AreEqual(b, c); - } - - File.Delete("RE_WRITE_SMALLER_MINI_STREAM.xls"); - } - - [TestMethod] - public void Test_TRANSACTED_ADD_STREAM_TO_EXISTING_FILE() - { - string srcFilename = "report.xls"; - string dstFilename = "reportOverwrite.xls"; - - File.Copy(srcFilename, dstFilename, true); - - using CompoundFile cf = new(dstFilename, CFSUpdateMode.Update, CFSConfiguration.Default); - - byte[] buffer = Helpers.GetBuffer(5000); - - CFStream addedStream = cf.RootStorage.AddStream("MyNewStream"); - addedStream.SetData(buffer); - - cf.Commit(); - cf.Close(); - - File.Delete("reportOverwrite.xls"); - } - - [TestMethod] - public void Test_TRANSACTED_ADD_REMOVE_MULTIPLE_STREAM_TO_EXISTING_FILE() - { - string srcFilename = "report.xls"; - string dstFilename = "reportOverwriteMultiple.xls"; - - File.Copy(srcFilename, dstFilename, true); - - using CompoundFile cf = new(dstFilename, CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle); - - //CompoundFile cf = new CompoundFile(); - - Random r = new Random(); - - for (int i = 0; i < 254; i++) - { - //byte[] buffer = Helpers.GetBuffer(r.Next(100, 3500), (byte)i); - byte[] buffer = Helpers.GetBuffer(1995, 1); - - //if (i > 0) - //{ - // if (r.Next(0, 100) > 50) - // { - // cf.RootStorage.Delete("MyNewStream" + (i - 1).ToString()); - // } - //} - - CFStream addedStream = cf.RootStorage.AddStream("MyNewStream" + i.ToString()); - Assert.IsNotNull(addedStream, "Stream not found"); - addedStream.SetData(buffer); - - CollectionAssert.AreEqual(addedStream.GetData(), buffer); - - // Random commit, not on single addition - //if (r.Next(0, 100) > 50) - // cf.UpdateFile(); - } - - cf.SaveAs(dstFilename + "PP"); - cf.Close(); - - File.Delete("reportOverwriteMultiple.xls"); - - File.Delete("reportOverwriteMultiple.xlsPP"); - } - - [TestMethod] - public void Test_TRANSACTED_ADD_MINISTREAM_TO_EXISTING_FILE() - { - string srcFilename = "report.xls"; - string dstFilename = "reportOverwriteMultiple.xls"; - - File.Copy(srcFilename, dstFilename, true); - - using CompoundFile cf = new(dstFilename, CFSUpdateMode.Update, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors); - - Random r = new Random(); - - byte[] buffer = Helpers.GetBuffer(31, 0x0A); - - cf.RootStorage.AddStream("MyStream").SetData(buffer); - cf.Commit(); - cf.Close(); - - using FileStream larger = new(dstFilename, FileMode.Open); - using FileStream smaller = new(srcFilename, FileMode.Open); - - // Equal condition if minisector can be "allocated" - // within the existing standard sector border - Assert.IsTrue(larger.Length >= smaller.Length); - - larger.Close(); - smaller.Close(); - - File.Delete("reportOverwriteMultiple.xlsPP"); - } - - [TestMethod] - public void Test_TRANSACTED_REMOVE_MINI_STREAM_ADD_MINISTREAM_TO_EXISTING_FILE() - { - string srcFilename = "report.xls"; - string dstFilename = "reportOverwrite2.xls"; - - File.Copy(srcFilename, dstFilename, true); - - using CompoundFile cf = new(dstFilename, CFSUpdateMode.Update, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors); - - cf.RootStorage.Delete("\x05SummaryInformation"); - - byte[] buffer = Helpers.GetBuffer(2000); - - CFStream addedStream = cf.RootStorage.AddStream("MyNewStream"); - addedStream.SetData(buffer); - - cf.Commit(); - cf.Close(); - - File.Delete("reportOverwrite2.xlsPP"); - } - - [TestMethod] - public void Test_DELETE_STREAM_1() - { - string filename = "MultipleStorage.cfs"; - - using CompoundFile cf = new(filename); - CFStorage cfs = cf.RootStorage.GetStorage("MyStorage"); - cfs.Delete("MySecondStream"); - - cf.SaveAs(TestContext + "MultipleStorage_REMOVED_STREAM_1.cfs"); - } - - [TestMethod] - public void Test_DELETE_STREAM_2() - { - string filename = "MultipleStorage.cfs"; - - using CompoundFile cf = new(filename); - CFStorage cfs = cf.RootStorage.GetStorage("MyStorage").GetStorage("AnotherStorage"); - - cfs.Delete("AnotherStream"); - - cf.SaveAs(TestContext + "MultipleStorage_REMOVED_STREAM_2.cfs"); - } - - [TestMethod] - [DataRow(CFSVersion.Ver_3, 0)] - [DataRow(CFSVersion.Ver_3, 63)] - [DataRow(CFSVersion.Ver_3, 64)] - [DataRow(CFSVersion.Ver_3, 65)] - [DataRow(CFSVersion.Ver_3, 511)] - [DataRow(CFSVersion.Ver_3, 512)] - [DataRow(CFSVersion.Ver_3, 513)] - [DataRow(CFSVersion.Ver_3, 4095)] - [DataRow(CFSVersion.Ver_3, 4096)] - [DataRow(CFSVersion.Ver_3, 409)] - [DataRow(CFSVersion.Ver_4, 0)] - [DataRow(CFSVersion.Ver_4, 63)] - [DataRow(CFSVersion.Ver_4, 64)] - [DataRow(CFSVersion.Ver_4, 65)] - [DataRow(CFSVersion.Ver_4, 511)] - [DataRow(CFSVersion.Ver_4, 512)] - [DataRow(CFSVersion.Ver_4, 513)] - [DataRow(CFSVersion.Ver_4, 4095)] - [DataRow(CFSVersion.Ver_4, 4096)] - [DataRow(CFSVersion.Ver_4, 4097)] - public void Test_INCREMENTAL_SIZE_MULTIPLE_WRITE_AND_READ_CFS(CFSVersion version, int bufferSize) - { - SingleWriteReadMatching(version, bufferSize); - } - - [TestMethod] - [DataRow(CFSVersion.Ver_3, 0)] - [DataRow(CFSVersion.Ver_3, 63)] - [DataRow(CFSVersion.Ver_3, 64)] - [DataRow(CFSVersion.Ver_3, 65)] - [DataRow(CFSVersion.Ver_3, 511)] - [DataRow(CFSVersion.Ver_3, 512)] - [DataRow(CFSVersion.Ver_3, 513)] - [DataRow(CFSVersion.Ver_3, 4095)] - [DataRow(CFSVersion.Ver_3, 4096)] - [DataRow(CFSVersion.Ver_3, 409)] - [DataRow(CFSVersion.Ver_4, 0)] - [DataRow(CFSVersion.Ver_4, 63)] - [DataRow(CFSVersion.Ver_4, 64)] - [DataRow(CFSVersion.Ver_4, 65)] - [DataRow(CFSVersion.Ver_4, 511)] - [DataRow(CFSVersion.Ver_4, 512)] - [DataRow(CFSVersion.Ver_4, 513)] - [DataRow(CFSVersion.Ver_4, 4095)] - [DataRow(CFSVersion.Ver_4, 4096)] - [DataRow(CFSVersion.Ver_4, 4097)] - public void Test_INCREMENTAL_SIZE_MULTIPLE_WRITE_AND_READ_CFS_STREAM(CFSVersion version, int bufferLength) - { - SingleWriteReadMatchingSTREAMED(version, bufferLength); - } - - static IEnumerable FuzzyBufferLengths - { - get - { - Random r = new Random(); - - foreach (CFSVersion version in Enum.GetValues(typeof(CFSVersion))) - { - for (int i = r.Next(1, 100); i < 1024 * 1024 * 70; i <<= 1) - { - yield return new object[] { version, i + r.Next(0, 3) }; - } - } - } - } - - [TestMethod] - [Ignore("Run fuzzing tests manually")] - [DynamicData(nameof(FuzzyBufferLengths), DynamicDataSourceType.Property)] - public void Test_INCREMENTAL_SIZE_MULTIPLE_WRITE_AND_READ_CFS_STREAM_FUZZING(CFSVersion version, int bufferLength) - { - SingleWriteReadMatchingSTREAMED(version, bufferLength); - } - - [TestMethod] - public void Test_DELETE_ZERO_LENGTH_STREAM() - { - string zeroLengthName = "MyZeroStream"; - string filename = "DeleteZeroLengthStream.cfs"; - byte[] b = new byte[0]; - - using (CompoundFile cf = new()) - { - CFStream myStream = cf.RootStorage.AddStream(zeroLengthName); - Assert.IsNotNull(myStream); - myStream.SetData(b); - cf.SaveAs(filename); - } - - using CompoundFile cf2 = new(filename); - cf2.RootStorage.Delete(zeroLengthName); - Assert.ThrowsException(() => cf2.RootStorage.GetStream(zeroLengthName)); - cf2.SaveAs("MultipleDeleteMiniStream.cfs"); - } - - //[TestMethod] - //public void Test_INCREMENTAL_TRANSACTED_CHANGE_CFS() - //{ - - // Random r = new Random(); - - // for (int i = r.Next(1, 100); i < 1024 * 1024 * 70; i = i << 1) - // { - // SingleTransactedChange(i + r.Next(0, 3)); - // } - - //} - - public static void SingleTransactedChange(int size) - { - string filename = "INCREMENTAL_SIZE_MULTIPLE_WRITE_AND_READ_CFS.cfs"; - - File.Delete(filename); - - byte[] b = Helpers.GetBuffer(size); - - using (CompoundFile cf = new()) - { - CFStorage st = cf.RootStorage.AddStorage("MyStorage"); - CFStream sm = st.AddStream("MyStream"); - sm.SetData(b); - cf.SaveAs(filename); - } - - using CompoundFile cf2 = new(filename); - CFStorage st2 = cf2.RootStorage.GetStorage("MyStorage"); - CFStream sm2 = st2.GetStream("MyStream"); - - Assert.IsNotNull(sm2); - Assert.AreEqual(size, sm2.Size); - CollectionAssert.AreEqual(b, sm2.GetData()); - } - - private static void SingleWriteReadMatching(CFSVersion version, int bufferLength) - { - string filename = $"{nameof(SingleWriteReadMatching)}_{version}_{bufferLength}.cfs"; - - File.Delete(filename); - - byte[] b = Helpers.GetBuffer(bufferLength); - - using (CompoundFile cf = new()) - { - CFStorage st = cf.RootStorage.AddStorage("MyStorage"); - CFStream sm = st.AddStream("MyStream"); - - sm.SetData(b); - cf.SaveAs(filename); - } - - using CompoundFile cf2 = new(filename); - CFStorage st2 = cf2.RootStorage.GetStorage("MyStorage"); - CFStream sm2 = st2.GetStream("MyStream"); - - Assert.IsNotNull(sm2); - Assert.AreEqual(bufferLength, sm2.Size); - CollectionAssert.AreEqual(b, sm2.GetData()); - } - - private static void SingleWriteReadMatchingSTREAMED(CFSVersion version, int bufferLength) - { - byte[] b = Helpers.GetBuffer(bufferLength); - - using MemoryStream ms = new(bufferLength); - - using (CompoundFile cf = new(version, CFSConfiguration.Default)) - { - CFStorage st = cf.RootStorage.AddStorage("MyStorage"); - CFStream sm = st.AddStream("MyStream"); - sm.SetData(b); - cf.Save(ms); - } - - using CompoundFile cf2 = new(ms); - CFStorage st2 = cf2.RootStorage.GetStorage("MyStorage"); - CFStream sm2 = st2.GetStream("MyStream"); - - Assert.IsNotNull(sm2); - CollectionAssert.AreEqual(b, sm2.GetData()); - } - - [TestMethod] - public void Test_APPEND_DATA_TO_STREAM() - { - using MemoryStream ms = new(); - - byte[] b = new byte[] { 0x0, 0x1, 0x2, 0x3 }; - byte[] b2 = new byte[] { 0x4, 0x5, 0x6, 0x7 }; - - using (CompoundFile cf = new()) - { - CFStream st = cf.RootStorage.AddStream("MyMiniStream"); - st.SetData(b); - st.Append(b2); - cf.Save(ms); - } - - byte[] cmp = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 }; - using (CompoundFile cf = new(ms)) - { - byte[] data = cf.RootStorage.GetStream("MyMiniStream").GetData(); - CollectionAssert.AreEqual(cmp, data); - } - } - - [TestMethod] - public void Test_COPY_FROM_STREAM() - { - byte[] b = Helpers.GetBuffer(100); - - using (CompoundFile cf = new()) - { - CFStream st = cf.RootStorage.AddStream("MyImportedStream"); - using MemoryStream ms = new(b); - st.CopyFrom(ms); - ms.Close(); - cf.SaveAs("COPY_FROM_STREAM.cfs"); - } - - using (CompoundFile cf = new("COPY_FROM_STREAM.cfs")) - { - byte[] data = cf.RootStorage.GetStream("MyImportedStream").GetData(); - CollectionAssert.AreEqual(b, data); - } - } - -#if LARGETEST - - [TestMethod] - public void Test_APPEND_DATA_TO_CREATE_LARGE_STREAM() - { - byte[] b = Helpers.GetBuffer(1024 * 1024 * 50); //2GB buffer - byte[] cmp = new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 }; - - CompoundFile cf = new CompoundFile(CFSVersion.Ver_4, false, false); - CFStream st = cf.RootStorage.AddStream("MySuperLargeStream"); - cf.Save("MEGALARGESSIMUSFILE.cfs"); - cf.Close(); - - - cf = new CompoundFile("MEGALARGESSIMUSFILE.cfs", UpdateMode.Update, false, false); - CFStream cfst = cf.RootStorage.GetStream("MySuperLargeStream"); - for (int i = 0; i < 42; i++) - { - cfst.AppendData(b); - cf.Commit(true); - } - - cfst.AppendData(cmp); - cf.Commit(true); - - cf.Close(); - - - cf = new CompoundFile("MEGALARGESSIMUSFILE.cfs"); - int count = 8; - byte[] data = cf.RootStorage.GetStream("MySuperLargeStream").GetData((long)b.Length * 42L, ref count); - CollectionAssert.AreEqual(cmp, data); - cf.Close(); - - } -#endif - - [TestMethod] - public void Test_RESIZE_STREAM_NO_TRANSITION() - { - int INITIAL_SIZE = 1024 * 1024 * 2; - int DELTA_SIZE = 300; - //CFStream st = null; - byte[] b = Helpers.GetBuffer(INITIAL_SIZE); //2MB buffer - - using (CompoundFile cf = new(CFSVersion.Ver_3, CFSConfiguration.Default)) - { - cf.RootStorage.AddStream("AStream").SetData(b); - cf.SaveAs("$Test_RESIZE_STREAM.cfs"); - } - - using (CompoundFile cf = new("$Test_RESIZE_STREAM.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle)) - { - CFStream item = cf.RootStorage.GetStream("AStream"); - item.Resize(INITIAL_SIZE - DELTA_SIZE); - //cf.RootStorage.AddStream("BStream").SetData(b); - cf.Commit(true); - } - - using (CompoundFile cf = new("$Test_RESIZE_STREAM.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.Default)) - { - CFStream item = cf.RootStorage.GetStream("AStream"); - Assert.IsNotNull(item); - Assert.AreEqual(INITIAL_SIZE - DELTA_SIZE, item.Size); - - byte[] buffer = new byte[INITIAL_SIZE - DELTA_SIZE]; - item.Read(buffer, 0, buffer.Length); - CollectionAssert.AreEqual(b.Take(buffer.Length).ToList(), buffer); - } - } - - [TestMethod] - public void Test_RESIZE_STREAM_TRANSITION_TO_MINI() - { - string FILE_NAME = "$Test_RESIZE_STREAM_TRANSITION_TO_MINI.cfs"; - byte[] b = Helpers.GetBuffer(1024 * 1024 * 2); //2MB buffer - byte[] b100 = new byte[100]; - - for (int i = 0; i < 100; i++) - { - b100[i] = b[i]; - } - - using (CompoundFile cf = new(CFSVersion.Ver_3, CFSConfiguration.Default)) - { - cf.RootStorage.AddStream("AStream").SetData(b); - cf.SaveAs(FILE_NAME); - } - - using (CompoundFile cf = new(FILE_NAME, CFSUpdateMode.Update, CFSConfiguration.SectorRecycle)) - { - CFStream item = cf.RootStorage.GetStream("AStream"); - item.Resize(100); - cf.Commit(); - } - - using (CompoundFile cf = new(FILE_NAME, CFSUpdateMode.ReadOnly, CFSConfiguration.Default)) - { - CollectionAssert.AreEqual(b100, cf.RootStorage.GetStream("AStream").GetData()); - } - - File.Delete(FILE_NAME); - } - - [TestMethod] - public void Test_RESIZE_STREAM_TRANSITION_TO_NORMAL() - { - byte[] b = Helpers.GetBuffer(1024 * 2, 0xAA); //2MB buffer - - using (CompoundFile cf = new(CFSVersion.Ver_3, CFSConfiguration.Default)) - { - cf.RootStorage.AddStream("AStream").SetData(b); - cf.SaveAs("$Test_RESIZE_STREAM_TRANSITION_TO_NORMAL.cfs"); - cf.SaveAs("$Test_RESIZE_STREAM_TRANSITION_TO_NORMAL2.cfs"); - } - - using (CompoundFile cf = new("$Test_RESIZE_STREAM_TRANSITION_TO_NORMAL.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors)) - { - CFStream item = cf.RootStorage.GetStream("AStream"); - item.Resize(5000); - cf.Commit(); - } - - using (CompoundFile cf = new("$Test_RESIZE_STREAM_TRANSITION_TO_NORMAL.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.Default)) - { - CFStream item = cf.RootStorage.GetStream("AStream"); - Assert.IsNotNull(item); - Assert.AreEqual(5000, item.Size); - - byte[] buffer = new byte[2048]; - item.Read(buffer, 0, 2048); - CollectionAssert.AreEqual(b, buffer); - } - } - - [TestMethod] - public void Test_RESIZE_MINISTREAM_NO_TRANSITION_MOD() - { - int INITIAL_SIZE = 1024 * 2; - int SIZE_DELTA = 148; - - byte[] b = Helpers.GetBuffer(INITIAL_SIZE); - - using (CompoundFile cf = new(CFSVersion.Ver_3, CFSConfiguration.Default)) - { - cf.RootStorage.AddStream("MiniStream").SetData(b); - cf.SaveAs("$Test_RESIZE_MINISTREAM.cfs"); - } - - using (CompoundFile cf = new("$Test_RESIZE_MINISTREAM.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors)) - { - CFStream item = cf.RootStorage.GetStream("MiniStream"); - item.Resize(item.Size - SIZE_DELTA); - cf.Commit(); - } - - using (CompoundFile cf = new("$Test_RESIZE_MINISTREAM.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.Default)) - { - CFStream st = cf.RootStorage.GetStream("MiniStream"); - Assert.IsNotNull(st); - Assert.AreEqual(INITIAL_SIZE - SIZE_DELTA, st.Size); - - byte[] buffer = new byte[INITIAL_SIZE - SIZE_DELTA]; - st.Read(buffer, 0, buffer.Length); - CollectionAssert.AreEqual(b.Take(buffer.Length).ToList(), buffer); - } - } - - [TestMethod] - public void Test_RESIZE_MINISTREAM_SECTOR_RECYCLE() - { - byte[] b = Helpers.GetBuffer(1024 * 2); - - using (CompoundFile cf = new(CFSVersion.Ver_3, CFSConfiguration.Default)) - { - cf.RootStorage.AddStream("MiniStream").SetData(b); - cf.SaveAs("$Test_RESIZE_MINISTREAM_RECYCLE.cfs"); - } - - using (CompoundFile cf = new("$Test_RESIZE_MINISTREAM_RECYCLE.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors)) - { - CFStream item = cf.RootStorage.GetStream("MiniStream"); - item.Resize(item.Size / 2); - cf.Commit(); - } - - using (CompoundFile cf = new("$Test_RESIZE_MINISTREAM_RECYCLE.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors)) - { - CFStream st = cf.RootStorage.AddStream("ANewStream"); - st.SetData(Helpers.GetBuffer(400)); - cf.SaveAs("$Test_RESIZE_MINISTREAM_RECYCLE2.cfs"); - } - - Assert.AreEqual( - new FileInfo("$Test_RESIZE_MINISTREAM_RECYCLE.cfs").Length, - new FileInfo("$Test_RESIZE_MINISTREAM_RECYCLE2.cfs").Length); - } - - [TestMethod] - public void Test_DELETE_STREAM_SECTOR_REUSE() - { - byte[] b = Helpers.GetBuffer(1024 * 1024 * 2); // 2MB buffer - - using (CompoundFile cf = new(CFSVersion.Ver_4, CFSConfiguration.Default)) - { - CFStream st = cf.RootStorage.AddStream("AStream"); - st.Append(b); - cf.SaveAs("SectorRecycle.cfs"); - } - - using (CompoundFile cf = new("SectorRecycle.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle)) - { - cf.RootStorage.Delete("AStream"); - cf.Commit(true); - } - - using (CompoundFile cf = new("SectorRecycle.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.Default)) // No sector recycle - { - CFStream st = cf.RootStorage.AddStream("BStream"); - st.Append(Helpers.GetBuffer(1024 * 1024 * 1)); - cf.SaveAs("SectorRecycleLarger.cfs"); - } - - Assert.IsFalse(new FileInfo("SectorRecycle.cfs").Length >= new FileInfo("SectorRecycleLarger.cfs").Length); - - using (CompoundFile cf = new("SectorRecycle.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle)) - { - CFStream st = cf.RootStorage.AddStream("BStream"); - st.Append(Helpers.GetBuffer(1024 * 1024 * 1)); - cf.SaveAs("SectorRecycleSmaller.cfs"); - } - - long larger = new FileInfo("SectorRecycle.cfs").Length; - long smaller = new FileInfo("SectorRecycleSmaller.cfs").Length; - - Assert.IsTrue(larger >= smaller, $"Larger size: {larger} - Smaller size: {smaller}"); - } - - [TestMethod] - public void TEST_STREAM_VIEW() - { - List temp = new List(); - Sector s = new Sector(512); - Buffer.BlockCopy(BitConverter.GetBytes(1), 0, s.GetData(), 0, 4); - temp.Add(s); - - using StreamView sv = new(temp, 512, 4, null, null); - using BinaryReader br = new(sv); - int t = br.ReadInt32(); - - Assert.AreEqual(1, t); - } - - [TestMethod] - public void Test_STREAM_VIEW_2() - { - List temp = new List(); - - using StreamView sv = new(temp, 512, null); - sv.Write(BitConverter.GetBytes(1), 0, 4); - sv.Seek(0, SeekOrigin.Begin); - using BinaryReader br = new(sv); - int t = br.ReadInt32(); - - Assert.AreEqual(1, t); - } - - /// - /// Write a sequence of Int32 greater than sector size, - /// read and compare. - /// - [TestMethod] - public void Test_STREAM_VIEW_3() - { - List temp = new List(); - - using StreamView sv = new(temp, 512, null); - for (int i = 0; i < 200; i++) - { - sv.Write(BitConverter.GetBytes(i), 0, 4); - } - - sv.Seek(0, SeekOrigin.Begin); - using BinaryReader br = new(sv); - for (int i = 0; i < 200; i++) - { - Assert.AreEqual(i, br.ReadInt32(), "Failed with " + i.ToString()); - } - } - - /// - /// Write a sequence of Int32 greater than sector size, - /// read and compare. - /// - [TestMethod] - public void Test_STREAM_VIEW_LARGE_DATA() - { - List temp = new List(); - - using StreamView sv = new(temp, 512, null); - for (int i = 0; i < 200; i++) - { - sv.Write(BitConverter.GetBytes(i), 0, 4); - } - - sv.Seek(0, SeekOrigin.Begin); - using BinaryReader br = new(sv); - for (int i = 0; i < 200; i++) - { - Assert.AreEqual(i, br.ReadInt32(), "Failed with " + i.ToString()); - } - } - - /// - /// Write a sequence of Int32 greater than sector size, - /// read and compare. - /// - [TestMethod] - public void Test_CHANGE_STREAM_NAME_FIX_54() - { - using (CompoundFile cf = new("report.xls", CFSUpdateMode.ReadOnly, CFSConfiguration.Default)) - { - cf.RootStorage.RenameItem("Workbook", "Workbuk"); - cf.SaveAs("report_n.xls"); - } - - using (CompoundFile cf2 = new("report_n.xls", CFSUpdateMode.Update, CFSConfiguration.Default)) - { - cf2.RootStorage.RenameItem("Workbuk", "Workbook"); - cf2.Commit(); - } - - using (CompoundFile cf3 = new("MultipleStorage.cfs", CFSUpdateMode.Update, CFSConfiguration.Default)) - { - cf3.RootStorage.RenameItem("MyStorage", "MyNewStorage"); - cf3.Commit(); - } - - using CompoundFile cf4 = new("MultipleStorage.cfs", CFSUpdateMode.Update, CFSConfiguration.Default); - cf4.RootStorage.RenameItem("MyNewStorage", "MyStorage"); - cf4.Commit(); - } - - /// - /// Resize without transition to smaller chain has a wrong behavior - /// - [TestMethod] - public void TEST_RESIZE_STREAM_BUG_119() - { - const string DATA = "data"; - const int size = 10; - using MemoryStream ms = new(); - - using (CompoundFile cf = new()) - { - CFStream st = cf.RootStorage.AddStream(DATA); - var data = Enumerable.Range(0, size).Select(v => (byte)v).ToArray(); - st.SetData(data); - cf.Save(ms); - } - - ms.Position = 0; - using (CompoundFile cf = new(ms, CFSUpdateMode.Update, CFSConfiguration.Default)) - { - CFStream st = cf.RootStorage.GetStream(DATA); - byte[] buffer = new byte[size]; - st.Read(buffer, 0, size); - st.Resize(5); // <- can be any number smaller than the current size - st.Write(new byte[] { 0 }, 0); - cf.Commit(); - } - } - } -} diff --git a/sources/Test/OpenMcdf.Test/CompoundFileTest.cs b/sources/Test/OpenMcdf.Test/CompoundFileTest.cs deleted file mode 100644 index dd8963eb..00000000 --- a/sources/Test/OpenMcdf.Test/CompoundFileTest.cs +++ /dev/null @@ -1,1094 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; - -namespace OpenMcdf.Test -{ - /// - /// Summary description for CompoundFileTest - /// - [TestClass] - public class CompoundFileTest - { - public CompoundFileTest() - { - } - - /// - /// Gets or sets the test context which provides - /// information about and functionality for the current test run. - /// - public TestContext TestContext { get; set; } - - #region Additional test attributes - // - // You can use the following additional attributes as you write your tests: - // - // Use ClassInitialize to run code before running the first test in the class - // [ClassInitialize()] - // public static void MyClassInitialize(TestContext testContext) { } - // - // Use ClassCleanup to run code after all tests in a class have run - // [ClassCleanup()] - // public static void MyClassCleanup() { } - // - // Use TestInitialize to run code before running each test - // [TestInitialize()] - // public void MyTestInitialize() { } - // - // Use TestCleanup to run code after each test has run - // [TestCleanup()] - // public void MyTestCleanup() { } - // - #endregion - - [TestMethod] - public void Test_COMPRESS_SPACE() - { - string FILENAME = "MultipleStorage3.cfs"; // 22Kb - - FileInfo srcFile = new FileInfo(FILENAME); - - File.Copy(FILENAME, "MultipleStorage_Deleted_Compress.cfs", true); - - using (CompoundFile cf = new("MultipleStorage_Deleted_Compress.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors)) - { - CFStorage st = cf.RootStorage.GetStorage("MyStorage"); - st = st.GetStorage("AnotherStorage"); - - Assert.IsNotNull(st); - st.Delete("Another2Stream"); - cf.Commit(); - } - - CompoundFile.ShrinkCompoundFile("MultipleStorage_Deleted_Compress.cfs"); // -> 7Kb - - FileInfo dstFile = new FileInfo("MultipleStorage_Deleted_Compress.cfs"); - - Assert.IsTrue(srcFile.Length > dstFile.Length); - } - - [TestMethod] - public void Test_ENTRY_NAME_LENGTH() - { - // Thanks to Mark Bosold for bug fix and unit - - using CompoundFile cf = new(); - - // Cannot be equal. - string maxCharactersStreamName = "1234567890123456789A12345678901"; // 31 chars - string maxCharactersStorageName = "1234567890123456789012345678901"; // 31 chars - - // Try Storage entry name with max characters. - Assert.IsNotNull(cf.RootStorage.AddStorage(maxCharactersStorageName)); - CFStorage strg = cf.RootStorage.GetStorage(maxCharactersStorageName); - Assert.IsNotNull(strg); - Assert.AreEqual(maxCharactersStorageName, strg.Name); - - // Try Stream entry name with max characters. - Assert.IsNotNull(cf.RootStorage.AddStream(maxCharactersStreamName)); - CFStream strm = cf.RootStorage.GetStream(maxCharactersStreamName); - Assert.IsNotNull(strm); - Assert.AreEqual(maxCharactersStreamName, strm.Name); - - string tooManyCharactersEntryName = "12345678901234567890123456789012"; // 32 chars - - Assert.ThrowsException(() => cf.RootStorage.AddStorage(tooManyCharactersEntryName)); - - Assert.ThrowsException(() => cf.RootStorage.AddStream(tooManyCharactersEntryName)); - - cf.SaveAs("EntryNameLength"); - } - - [TestMethod] - public void Test_DELETE_WITHOUT_COMPRESSION() - { - string FILENAME = "MultipleStorage3.cfs"; - - FileInfo srcFile = new FileInfo(FILENAME); - - using (CompoundFile cf = new(FILENAME)) - { - - CFStorage st = cf.RootStorage.GetStorage("MyStorage"); - st = st.GetStorage("AnotherStorage"); - - Assert.IsNotNull(st); - - st.Delete("Another2Stream"); //17Kb - - //cf.CompressFreeSpace(); - cf.SaveAs("MultipleStorage_Deleted_Compress.cfs"); - } - - FileInfo dstFile = new FileInfo("MultipleStorage_Deleted_Compress.cfs"); - - Assert.IsFalse(srcFile.Length > dstFile.Length); - } - - [TestMethod] - public void Test_WRITE_AND_READ_CFS_VERSION_4() - { - string filename = "WRITE_AND_READ_CFS_V4.cfs"; - - using (CompoundFile cf = new(CFSVersion.Ver_4, CFSConfiguration.EraseFreeSectors | CFSConfiguration.SectorRecycle)) - { - CFStorage st = cf.RootStorage.AddStorage("MyStorage"); - CFStream sm = st.AddStream("MyStream"); - byte[] b = new byte[220]; - sm.SetData(b); - - cf.SaveAs(filename); - } - - using CompoundFile cf2 = new CompoundFile(filename); - CFStorage st2 = cf2.RootStorage.GetStorage("MyStorage"); - CFStream sm2 = st2.GetStream("MyStream"); - - Assert.IsNotNull(sm2); - Assert.AreEqual(220, sm2.Size); - } - - [TestMethod] - public void Test_WRITE_READ_CFS_VERSION_4_STREAM() - { - string filename = "WRITE_COMMIT_READ_CFS_V4.cfs"; - byte[] b = Helpers.GetBuffer(227); - - using (CompoundFile cf = new(CFSVersion.Ver_4, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors)) - { - CFStorage st = cf.RootStorage.AddStorage("MyStorage"); - CFStream sm = st.AddStream("MyStream"); - sm.SetData(b); - - cf.SaveAs(filename); - } - - using CompoundFile cf2 = new CompoundFile(filename); - CFStorage st2 = cf2.RootStorage.GetStorage("MyStorage"); - CFStream sm2 = st2.GetStream("MyStream"); - - Assert.IsNotNull(sm2); - Assert.AreEqual(b.Length, sm2.Size); - } - - [TestMethod] - public void Test_OPEN_FROM_STREAM() - { - const string filename = "reportREAD.xls"; - - using var fs = new FileStream(filename, FileMode.Open); - using CompoundFile cf = new(fs); - var foundStream = cf.RootStorage.GetStream("Workbook"); - var temp = foundStream.GetData(); - Assert.IsNotNull(temp); - } - - [TestMethod] - public void Test_MULTIPLE_SAVE() - { - using CompoundFile file = new(); - - file.SaveAs("test.mdf"); - - var meta = file. - RootStorage. - AddStream("meta"); - - meta.Append(BitConverter.GetBytes(DateTime.Now.ToBinary())); - meta.Append(BitConverter.GetBytes(DateTime.Now.ToBinary())); - - file.SaveAs("test.mdf"); - } - - [TestMethod] - public void Test_OPEN_COMPOUND_BUG_FIX_133() - { - using CompoundFile f = new("testbad.ole"); - CFStream cfs = f.RootStorage.GetStream("\x01Ole10Native"); - byte[] data = cfs.GetData(); - Assert.AreEqual(18140, data.Length); - } - - [TestMethod] - public void Test_COMPARE_DIR_ENTRY_NAME_BUG_FIX_ID_3487353() - { - using (CompoundFile f = new("report_name_fix.xls", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors)) - { - CFStream cfs = f.RootStorage.AddStream("Poorbook"); - cfs.Append(Helpers.GetBuffer(20)); - f.Commit(); - } - - using CompoundFile f2 = new("report_name_fix.xls", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors); - var cfs2 = f2.RootStorage.GetStream("Workbook"); - Assert.AreEqual("Workbook", cfs2.Name); - f2.RootStorage.Delete("PoorBook"); - f2.Commit(); - } - - [TestMethod] - public void Test_GET_COMPOUND_VERSION() - { - using CompoundFile f = new("report_name_fix.xls"); - Assert.AreEqual(CFSVersion.Ver_3, f.Version); - } - - [TestMethod] - public void Test_FUNCTIONAL_BEHAVIOUR() - { - //System.Diagnostics.Trace.Listeners.Add(new ConsoleTraceListener()); - - const int N_FACTOR = 1; - - byte[] bA = Helpers.GetBuffer(20 * 1024 * N_FACTOR, 0x0A); - byte[] bB = Helpers.GetBuffer(5 * 1024, 0x0B); - byte[] bC = Helpers.GetBuffer(5 * 1024, 0x0C); - byte[] bD = Helpers.GetBuffer(5 * 1024, 0x0D); - byte[] bE = Helpers.GetBuffer(8 * 1024 * N_FACTOR + 1, 0x1A); - byte[] bF = Helpers.GetBuffer(16 * 1024 * N_FACTOR, 0x1B); - byte[] bG = Helpers.GetBuffer(14 * 1024 * N_FACTOR, 0x1C); - byte[] bH = Helpers.GetBuffer(12 * 1024 * N_FACTOR, 0x1D); - byte[] bE2 = Helpers.GetBuffer(8 * 1024 * N_FACTOR, 0x2A); - byte[] bMini = Helpers.GetBuffer(1027, 0xEE); - - Stopwatch sw = new Stopwatch(); - sw.Start(); - - //############ - - // Phase 1 - using (CompoundFile cf = new(CFSVersion.Ver_3, CFSConfiguration.SectorRecycle)) - { - cf.RootStorage.AddStream("A").SetData(bA); - cf.SaveAs("OneStream.cfs"); - } - - // Test Phase 1 - using (CompoundFile cfTest = new("OneStream.cfs")) - { - CFStream testSt = cfTest.RootStorage.GetStream("A"); - - Assert.IsNotNull(testSt); - CollectionAssert.AreEqual(bA, testSt.GetData()); - } - - //########### - - //Phase 2 - using (CompoundFile cf = new("OneStream.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle)) - { - cf.RootStorage.AddStream("B").SetData(bB); - cf.RootStorage.AddStream("C").SetData(bC); - cf.RootStorage.AddStream("D").SetData(bD); - cf.RootStorage.AddStream("E").SetData(bE); - cf.RootStorage.AddStream("F").SetData(bF); - cf.RootStorage.AddStream("G").SetData(bG); - cf.RootStorage.AddStream("H").SetData(bH); - - cf.SaveAs("8_Streams.cfs"); - } - - // Test Phase 2 - - using (CompoundFile cfTest = new("8_Streams.cfs")) - { - CFStream testSt = cfTest.RootStorage.GetStream("B"); - Assert.IsNotNull(testSt); - CollectionAssert.AreEqual(bB, testSt.GetData()); - - testSt = cfTest.RootStorage.GetStream("C"); - Assert.IsNotNull(testSt); - CollectionAssert.AreEqual(bC, testSt.GetData()); - - testSt = cfTest.RootStorage.GetStream("D"); - Assert.IsNotNull(testSt); - CollectionAssert.AreEqual(bD, testSt.GetData()); - - testSt = cfTest.RootStorage.GetStream("E"); - Assert.IsNotNull(testSt); - CollectionAssert.AreEqual(bE, testSt.GetData()); - - testSt = cfTest.RootStorage.GetStream("F"); - Assert.IsNotNull(testSt); - CollectionAssert.AreEqual(bF, testSt.GetData()); - - testSt = cfTest.RootStorage.GetStream("G"); - Assert.IsNotNull(testSt); - CollectionAssert.AreEqual(bG, testSt.GetData()); - - testSt = cfTest.RootStorage.GetStream("H"); - Assert.IsNotNull(testSt); - CollectionAssert.AreEqual(bH, testSt.GetData()); - - } - - File.Copy("8_Streams.cfs", "6_Streams.cfs", true); - File.Delete("8_Streams.cfs"); - - //########### - // -#if !NETCOREAPP2_0 - Trace.Listeners.Add(new ConsoleTraceListener()); -#endif - // Phase 3 - using (CompoundFile cf = new("6_Streams.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors)) - { - cf.RootStorage.Delete("D"); - cf.RootStorage.Delete("G"); - cf.Commit(); - } - - // Test Phase 3 - - using (CompoundFile cfTest = new("6_Streams.cfs")) - { - Assert.ThrowsException(() => cfTest.RootStorage.GetStream("D")); - Assert.ThrowsException(() => cfTest.RootStorage.GetStream("G")); - } - - //########## - - // Phase 4 - - File.Copy("6_Streams.cfs", "6_Streams_Shrinked.cfs", true); - CompoundFile.ShrinkCompoundFile("6_Streams_Shrinked.cfs"); - - // Test Phase 4 - - Assert.IsTrue(new FileInfo("6_Streams_Shrinked.cfs").Length < new FileInfo("6_Streams.cfs").Length); - - using (CompoundFile cfTest = new("6_Streams_Shrinked.cfs")) - { - static void va(CFItem item) - { - if (item.IsStream) - { - CFStream ia = item as CFStream; - Assert.IsNotNull(ia); - Assert.IsTrue(ia.Size > 0); - byte[] d = ia.GetData(); - Assert.IsNotNull(d); - Assert.IsTrue(d.Length > 0); - Assert.AreEqual(ia.Size, d.Length); - } - } - - cfTest.RootStorage.VisitEntries(va, true); - } - - //########## - - //Phase 5 - - using (CompoundFile cf = new("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle)) - { - cf.RootStorage.AddStream("ZZZ").SetData(bF); - cf.RootStorage.GetStream("E").Append(bE2); - cf.Commit(); - } - - using (CompoundFile cf = new("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle)) - { - cf.RootStorage.CLSID = new Guid("EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"); - cf.Commit(); - } - - using (CompoundFile cf = new("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle)) - { - cf.RootStorage.AddStorage("MyStorage").AddStream("ZIP").Append(bE); - cf.Commit(); - } - - using (CompoundFile cf = new("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle)) - { - cf.RootStorage.AddStorage("AnotherStorage").AddStream("ANS").Append(bE); - cf.RootStorage.Delete("MyStorage"); - cf.Commit(); - } - - //Test Phase 5 - - //##### - - // Phase 6 - - using (CompoundFile cf = new("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle)) - { - CFStorage root = cf.RootStorage; - root.AddStorage("MiniStorage").AddStream("miniSt").Append(bMini); - root.GetStorage("MiniStorage").AddStream("miniSt2").Append(bMini); - cf.Commit(); - } - - using (CompoundFile cf = new("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle)) - { - cf.RootStorage.GetStorage("MiniStorage").Delete("miniSt"); - cf.RootStorage.GetStorage("MiniStorage").GetStream("miniSt2").Append(bE); - cf.Commit(); - } - - //Test Phase 6 - - using (CompoundFile cfTest = new("6_Streams_Shrinked.cfs")) - { - byte[] d2 = cfTest.RootStorage.GetStorage("MiniStorage").GetStream("miniSt2").GetData(); - Assert.AreEqual(bE.Length + bMini.Length, d2.Length); - - int cnt = 1; - byte[] buf = new byte[cnt]; - cnt = cfTest.RootStorage.GetStorage("MiniStorage").GetStream("miniSt2").Read(buf, bMini.Length, cnt); - - Assert.AreEqual(1, cnt); - Assert.AreEqual(0x1A, buf[0]); - - cnt = 1; - cnt = cfTest.RootStorage.GetStorage("MiniStorage").GetStream("miniSt2").Read(buf, bMini.Length - 1, cnt); - Assert.AreEqual(1, cnt); - Assert.AreEqual(0xEE, buf[0]); - - Assert.ThrowsException(() => cfTest.RootStorage.GetStorage("MiniStorage").GetStream("miniSt")); - } - - //############## - - //Phase 7 - - using (CompoundFile cf = new("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle)) - { - cf.RootStorage.GetStorage("MiniStorage").GetStream("miniSt2").SetData(bA); - cf.Commit(); - } - - //Test Phase 7 - - using (CompoundFile cf = new("6_Streams_Shrinked.cfs", CFSUpdateMode.Update, CFSConfiguration.SectorRecycle)) - { - var d2 = cf.RootStorage.GetStorage("MiniStorage").GetStream("miniSt2").GetData(); - Assert.IsNotNull(d2); - Assert.AreEqual(bA.Length, d2.Length); - } - - //############## - - using (CompoundFile cf = new("6_Streams_Shrinked.cfs", CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle)) - { - - var myStream = cf.RootStorage.GetStream("C"); - var data = myStream.GetData(); - Console.WriteLine(data[0] + " : " + data[data.Length - 1]); - - myStream = cf.RootStorage.GetStream("B"); - data = myStream.GetData(); - Console.WriteLine(data[0] + " : " + data[data.Length - 1]); - } - - sw.Stop(); - Console.WriteLine(sw.ElapsedMilliseconds); - } - - [TestMethod] - public void Test_RETRIVE_ALL_NAMED_ENTRIES() - { - using CompoundFile f = new("MultipleStorage4.cfs"); - IList result = f.GetAllNamedEntries("MyStream"); - - Assert.AreEqual(3, result.Count); - } - - [TestMethod] - public void Test_CORRUPTED_CYCLIC_FAT_CHECK() - { - Assert.ThrowsException(() => - { - using CompoundFile cf = new("CyclicFAT.cfs"); - }); - } - - [TestMethod] - public void Test_DIFAT_CHECK() - { - try - { - byte[] b1 = Helpers.GetBuffer(3, 0x0B); - - using (CompoundFile f = new()) - { - CFStream st = f.RootStorage.AddStream("LargeStream"); - st.Append(Helpers.GetBuffer(20000000, 0x0A)); // Forcing creation of two DIFAT sectors - st.Append(b1); // Forcing creation of two DIFAT sectors - - f.SaveAs("$OpenMcdf$LargeFile.cfs"); - } - - int cnt = 3; - using (CompoundFile f = new("$OpenMcdf$LargeFile.cfs")) - { - byte[] b2 = new byte[cnt]; - cnt = f.RootStorage.GetStream("LargeStream").Read(b2, 20000000, cnt); - CollectionAssert.AreEqual(b1, b2); - } - } - finally - { - File.Delete("$OpenMcdf$LargeFile.cfs"); - } - } - - [TestMethod] - public void Test_ADD_LARGE_NUMBER_OF_ITEMS() - { - int ITEM_NUMBER = 10000; - byte[] buffer = Helpers.GetBuffer(10, 0x0A); - try - { - using (CompoundFile f = new()) - { - for (int i = 0; i < ITEM_NUMBER; i++) - { - CFStream st = f.RootStorage.AddStream("Stream" + i.ToString()); - st.Append(buffer); - } - - File.Delete("$ItemsLargeNumber.cfs"); - f.SaveAs("$ItemsLargeNumber.cfs"); - } - - using (CompoundFile f = new("$ItemsLargeNumber.cfs")) - { - CFStream cfs = f.RootStorage.GetStream("Stream" + (ITEM_NUMBER / 2).ToString()); - Assert.IsNotNull(cfs, "Item is null"); - CollectionAssert.AreEqual(buffer, cfs.GetData()); - } - } - finally - { - File.Delete("$ItemsLargeNumber.cfs"); - } - } - - [TestMethod] - public void Test_FIX_BUG_16_CORRUPTED_AFTER_RESIZE() - { - const string FILE_PATH = @"BUG_16_.xls"; - - using CompoundFile cf = new(FILE_PATH); - - CFStream dirStream = cf.RootStorage.GetStorage("_VBA_PROJECT_CUR").GetStorage("VBA").GetStream("dir"); - - byte[] currentData = dirStream.GetData(); - - Array.Resize(ref currentData, currentData.Length - 50); - - dirStream.SetData(currentData); - - cf.SaveAs(FILE_PATH + ".edited"); - } - - [TestMethod] - public void Test_FIX_BUG_17_CORRUPTED_PPT_FILE() - { - const string FILE_PATH = @"2_MB-W.ppt"; - - using CompoundFile file = new CompoundFile(FILE_PATH); - //CFStorage dataSpaceInfo = file.RootStorage.GetStorage("\u0006DataSpaces").GetStorage("DataSpaceInfo"); - CFItem dsiItem = file.GetAllNamedEntries("DataSpaceInfo").FirstOrDefault(); - } - - [TestMethod] - public void Test_FIX_BUG_24_CORRUPTED_THUMBS_DB_FILE() - { - Assert.ThrowsException(() => - { - using CompoundFile cf = new("_thumbs_bug_24.db"); - cf.RootStorage.VisitEntries(item => Console.WriteLine(item.Name), recursive: false); - }); - } - - [TestMethod] - public void Test_FIX_BUG_28_CompoundFile_Delete_ChildElementMaintainsFiles() - { - using CompoundFile compoundFile = new(); - var storage1 = compoundFile.RootStorage.AddStorage("A"); - var storage2 = compoundFile.RootStorage.AddStorage("B"); - var storage3 = compoundFile.RootStorage.AddStorage("C"); - storage1.AddStream("A.1"); - compoundFile.RootStorage.Delete("B"); - storage1 = compoundFile.RootStorage.GetStorage("A"); - storage1.GetStream("A.1"); - } - - [TestMethod] - public void Test_CORRUPTEDDOC_BUG36_SHOULD_THROW_CORRUPTED_FILE_EXCEPTION() - { - using FileStream fs = new("CorruptedDoc_bug36.doc", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); - - Assert.ThrowsException(() => - { - using CompoundFile file = new(fs, CFSUpdateMode.ReadOnly, CFSConfiguration.LeaveOpen); - }); - - Assert.IsTrue(fs.CanRead && fs.CanSeek && fs.CanWrite); - } - - [TestMethod] - public void Test_ISSUE_2_WRONG_CUTOFF_SIZE() - { - File.Delete("TEST_ISSUE_2"); - - using (CompoundFile cf = new(CFSVersion.Ver_3, CFSConfiguration.Default)) - { - var s = cf.RootStorage.AddStream("miniToNormal"); - s.Append(Helpers.GetBuffer(4090, 0xAA)); - cf.SaveAs("TEST_ISSUE_2"); - } - - using CompoundFile cf2 = new("TEST_ISSUE_2", CFSUpdateMode.Update, CFSConfiguration.Default); - cf2.RootStorage.GetStream("miniToNormal").Append(Helpers.GetBuffer(6, 0xBB)); - cf2.Commit(); - } - - [TestMethod] - public void Test_PR_13() - { - using CompoundFile cf = new("report.xls"); - Guid g = cf.getGuidBySID(0); - Assert.AreNotEqual(Guid.Empty, g); - g = cf.getGuidForStream(3); - Assert.AreEqual(Guid.Empty, g); - Assert.AreNotEqual(0, cf.GetNumDirectories()); - Assert.IsTrue(!string.IsNullOrEmpty(cf.GetNameDirEntry(2))); - } - - //[TestMethod] - //public void Test_CORRUPTED_CYCLIC_DIFAT_VALIDATION_CHECK() - //{ - - // CompoundFile cf = null; - // try - // { - // using (CompoundFile cf = new("CiclycDFAT.cfs"); - // CFStorage s = cf.RootStorage.GetStorage("MyStorage"); - // CFStream st = s.GetStream("MyStream"); - // Assert.IsTrue(st.Size > 0); - // } - // catch (Exception ex) - // { - // Assert.IsTrue(ex is CFCorruptedFileException); - // } - // finally - // { - // if (cf != null) - // { - // cf.Close(); - // } - // } - //} - //[TestMethod] - //public void Test_REM() - //{ - // var f = new CompoundFile(); - - // byte[] bB = Helpers.GetBuffer(5 * 1024, 0x0B); - // f.RootStorage.AddStream("Test").AppendData(bB); - // f.Save("Astorage.cfs"); - //} - - //} - - [TestMethod] - public void Test_COPY_ENTRIES_FROM_TO_STORAGE() - { - using CompoundFile cfDst = new(); - using CompoundFile cfSrc = new("MultipleStorage4.cfs"); - - Copy(cfSrc.RootStorage, cfDst.RootStorage); - - cfDst.SaveAs("MultipleStorage4Copy.cfs"); - } - - #region Copy helper method - /// - /// Copies the given to the given - /// - /// - /// - public static void Copy(CFStorage source, CFStorage destination) - { - source.VisitEntries(action => - { - if (action.IsStorage) - { - var destionationStorage = destination.AddStorage(action.Name); - destionationStorage.CLSID = action.CLSID; - destionationStorage.CreationDate = action.CreationDate; - destionationStorage.ModifyDate = action.ModifyDate; - Copy(action as CFStorage, destionationStorage); - } - else - { - var sourceStream = action as CFStream; - var destinationStream = destination.AddStream(action.Name); - if (sourceStream != null) destinationStream.SetData(sourceStream.GetData()); - } - }, false); - } - #endregion - private const int Mb = 1024 * 1024; - [TestMethod] - public void Test_FIX_BUG_GH_14() - { - string filename = "MyFile.dat"; - string storageName = "MyStorage"; - string streamName = "MyStream"; - int BUFFER_SIZE = 800 * Mb; - int iterationCount = 1; - int streamCount = 3; - - using (CompoundFile compoundFileInit = new(CFSVersion.Ver_4, CFSConfiguration.Default)) - { - compoundFileInit.SaveAs(filename); - } - - using (CompoundFile compoundFile = new(filename, CFSUpdateMode.Update, CFSConfiguration.Default)) - { - CFStorage st = compoundFile.RootStorage.AddStorage(storageName); - byte b = 0X0A; - - for (int streamId = 0; streamId < streamCount; ++streamId) - { - CFStream sm = st.AddStream(streamName + streamId); - for (int iteration = 0; iteration < iterationCount; ++iteration) - { - sm.Append(Helpers.GetBuffer(BUFFER_SIZE, b)); - compoundFile.Commit(); - } - - b++; - } - } - - using (CompoundFile compoundFile = new(filename, CFSUpdateMode.ReadOnly, CFSConfiguration.Default)) - { - byte[] testBuffer = new byte[100]; - byte t = 0x0A; - - for (int streamId = 0; streamId < streamCount; ++streamId) - { - compoundFile.RootStorage.GetStorage(storageName).GetStream(streamName + streamId).Read(testBuffer, BUFFER_SIZE / 2, 100); - Assert.IsTrue(testBuffer.All(g => g == t)); - compoundFile.RootStorage.GetStorage(storageName).GetStream(streamName + streamId).Read(testBuffer, BUFFER_SIZE - 101, 100); - Assert.IsTrue(testBuffer.All(g => g == t)); - compoundFile.RootStorage.GetStorage(storageName).GetStream(streamName + streamId).Read(testBuffer, 0, 100); - Assert.IsTrue(testBuffer.All(g => g == t)); - t++; - } - } - } - - [TestMethod] - public void Test_FIX_BUG_GH_15() - { - string filename = "MyFile.dat"; - string storageName = "MyStorage"; - string streamName = "MyStream"; - int BUFFER_SIZE = 800 * Mb; - int iterationCount = 1; - int streamCount = 1; - - using (CompoundFile compoundFile = new(CFSVersion.Ver_4, CFSConfiguration.Default)) - { - CFStorage st = compoundFile.RootStorage.AddStorage(storageName); - - for (int streamId = 0; streamId < streamCount; ++streamId) - { - CFStream sm = st.AddStream(streamName + streamId); - for (int iteration = 0; iteration < iterationCount; ++iteration) - { - byte b = (byte)(0x0A + iteration); - sm.Append(Helpers.GetBuffer(BUFFER_SIZE, b)); - } - } - - compoundFile.SaveAs(filename); - } - - using (CompoundFile compoundFile = new(filename)) - { - byte c = 0x0A; - byte[] readBuffer = new byte[15]; - CFStorage storage = compoundFile.RootStorage.GetStorage(storageName); - for (int i = 0; i < iterationCount; i++) - { - Array.Clear(readBuffer, 0, readBuffer.Length); - CFStream stream = storage.GetStream($"{streamName}{0}"); - stream.Read(readBuffer, BUFFER_SIZE + ((long)BUFFER_SIZE * i) - 15, 15); - Assert.IsTrue(readBuffer.All(by => by == c)); - c++; - } - } - } - - [TestMethod] - public void Test_PR_GH_18() - { - using CompoundFile f = new("MultipleStorage4.cfs", CFSUpdateMode.Update, CFSConfiguration.Default); - var st = f.RootStorage.GetStorage("MyStorage").GetStorage("AnotherStorage").GetStream("MyStream"); - st.Write(Helpers.GetBuffer(100, 0x02), 100); - f.Commit(true); - Assert.AreEqual(31220, st.GetData().Length); - } - - [TestMethod] - public void Test_FIX_GH_38() - { - Assert.ThrowsException(() => - { - using CompoundFile f = new("empty_directory_chain.doc", CFSUpdateMode.Update, CFSConfiguration.Default); - }); - } - - [TestMethod] - public void Test_FIX_GH_38_B() - { - Assert.ThrowsException(() => new CompoundFile("no_sectors.doc", CFSUpdateMode.Update, CFSConfiguration.Default)); - } - - [TestMethod] - public void Test_FIX_GH_50() - { - Assert.ThrowsException(() => - { - using CompoundFile f = new("64-67.numberOfMiniFATSectors.docx", CFSUpdateMode.Update, CFSConfiguration.Default); - }); - } - - [TestMethod] - public void Test_FIX_GH_83() - { - try - { - byte[] bigDataBuffer = Helpers.GetBuffer(1024 * 1024 * 260); - - using (FileStream fTest = new("BigFile.data", FileMode.Create)) - { - fTest.Write(bigDataBuffer, 0, 1024 * 1024 * 260); - } - - using CompoundFile f = new(); - var cfStream = f.RootStorage.AddStream("NewStream"); - using (FileStream fs = new("BigFile.data", FileMode.Open)) - { - cfStream.CopyFrom(fs); - } - - f.SaveAs("BigFile.cfs"); - } - finally - { - File.Delete("BigFile.data"); - File.Delete("BigFile.cfs"); - } - } - - [TestMethod] - [ExpectedException(typeof(CFCorruptedFileException))] - public void Test_CorruptedSectorChain_Doc() - { - using CompoundFile f = new("corrupted-sector-chain.doc"); - } - - [TestMethod] - [ExpectedException(typeof(CFCorruptedFileException))] - public void Test_CorruptedSectorChain_Cfs() - { - using CompoundFile f = new("corrupted-sector-chain.cfs"); - } - - [TestMethod] - public void Test_WRONG_CORRUPTED_EXCEPTION() - { - using CompoundFile cf = new(); - - for (int i = 0; i < 100; i++) - { - cf.RootStorage.AddStream("Stream" + i).SetData(Helpers.GetBuffer(100000, 0xAA)); - } - - cf.RootStorage.AddStream("BigStream").SetData(Helpers.GetBuffer(5250000, 0xAA)); - - using var stream = new MemoryStream(); - cf.Save(stream); - } - - [TestMethod] - [ExpectedException(typeof(CFCorruptedFileException))] - public void Test_CorruptedSectorChain_Doc2() - { - using CompoundFile f = new("corrupted-sector-chain-2.doc"); - } - - //[TestMethod] - //public void Test_CORRUPTED_CYCLIC_DIFAT_VALIDATION_CHECK() - //{ - - // CompoundFile cf = null; - // try - // { - // CompoundFile cf = new("CiclycDFAT.cfs"); - // CFStorage s = cf.RootStorage.GetStorage("MyStorage"); - // CFStream st = s.GetStream("MyStream"); - // Assert.IsTrue(st.Size > 0); - // } - // catch (Exception ex) - // { - // Assert.IsTrue(ex is CFCorruptedFileException); - // } - // finally - // { - // if (cf != null) - // { - // cf.Close(); - // } - // } - //} - //[TestMethod] - //public void Test_REM() - //{ - // var f = new CompoundFile(); - - // byte[] bB = Helpers.GetBuffer(5 * 1024, 0x0B); - // f.RootStorage.AddStream("Test").AppendData(bB); - // f.Save("Astorage.cfs"); - //} - - [TestMethod] - public void Test_FIX_BUG_90_CompoundFile_Delete_Storages() - { - using var memStream = new MemoryStream(); - var storageNames = new HashSet(); - - using (CompoundFile compoundFile = new()) - { - var root = compoundFile.RootStorage; - - // add 99 storages to root - for (int i = 1; i <= 99; i++) - { - var name = "Storage " + i; - root.AddStorage(name); - storageNames.Add(name); - } - - // remove storages until tree becomes unbalanced and its Root changes - var rootChild = root.DirEntry.Child; - var newChild = rootChild; - var j = 1; - while (newChild == rootChild && j <= 99) - { - var name = "Storage " + j; - root.Delete(name); - storageNames.Remove(name); - newChild = ((DirectoryEntry)root.Children.Root).SID; // stop as soon as root.Children has a new Root - j++; - } - - // check if all remaining storages are still there - foreach (var storageName in storageNames) - { - Assert.IsTrue(root.TryGetStorage(storageName, out var storage)); // <- no problem here - } - - // write CompundFile into MemoryStream... - compoundFile.Save(memStream); - } - - // ....and read new CompundFile from that stream - using CompoundFile cf = new(memStream); - // check if all storages can be found in to copied CompundFile - foreach (var storageName in storageNames) - { - Assert.IsTrue(cf.RootStorage.TryGetStorage(storageName, out _)); //<- we will see some missing storages here - } - } - - [TestMethod] - public void Test_FIX_BUG_75_ForeverLoop() - { - Assert.ThrowsException(() => - { - using CompoundFile cf = new("mediationform.doc", CFSUpdateMode.ReadOnly, CFSConfiguration.Default & ~CFSConfiguration.NoValidationException); - var s = cf.RootStorage.GetStream("\u0001CompObj"); - byte[] data = s.GetData(); - }); - } - - [TestMethod] - public void Test_FIX_BUG_94_GrowingSizeSave() - { - string filename = "_Test.ppt"; - string filename2 = "MyFile4.dat"; - - File.Copy(filename, filename2, true); - - using (CompoundFile cf = new(filename2, CFSUpdateMode.Update, CFSConfiguration.EraseFreeSectors)) - { - cf.RootStorage.Delete("PowerPoint Document"); - cf.Commit(); - } - - CompoundFile.ShrinkCompoundFile(filename2); - - long length = new FileInfo(filename).Length; - long length2 = new FileInfo(filename2).Length; - - Assert.IsTrue(length > length2); - - File.Delete(filename2); - } - - [TestMethod] - public void Test_FIX_BUG_96_CompoundFile_SaveOverwrite() - { - string filename = "MultipleStorage.cfs"; - string filename2 = "MyFile2.dat"; - string storageName = "MyStorage"; - string streamName = "MyStream"; - - File.Copy(filename, filename2, true); - - Assert.ThrowsException(() => - { - using CompoundFile compoundFile = new(filename2); - var s = compoundFile.RootStorage.GetStorage(storageName).GetStream(streamName); - s.Write(new byte[] { 0x0A, 0x0A }, 0); - compoundFile.SaveAs(filename2); - }); - - Assert.ThrowsException(() => - { - string rootedPath = Path.GetFullPath(filename2); - using CompoundFile compoundFile = new(rootedPath); - var s = compoundFile.RootStorage.GetStorage(storageName).GetStream(streamName); - s.Write(new byte[] { 0x0A, 0x0A }, 0); - compoundFile.SaveAs(rootedPath); - }); - - Assert.ThrowsException(() => - { - using CompoundFile compoundFile = new(filename2); - using FileStream fs = new(filename2, FileMode.Open); - var s = compoundFile.RootStorage.GetStorage(storageName).GetStream(streamName); - s.Write(new byte[] { 0x0A, 0x0A }, 0); - compoundFile.Save(fs); - }); - - File.Delete(filename2); - } - - - } -} diff --git a/sources/Test/OpenMcdf.Test/Helpers.cs b/sources/Test/OpenMcdf.Test/Helpers.cs deleted file mode 100644 index bd1f2757..00000000 --- a/sources/Test/OpenMcdf.Test/Helpers.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -namespace OpenMcdf.Test -{ - public static class Helpers - { - public static byte[] GetBuffer(int count) - { - Random r = new Random(); - byte[] b = new byte[count]; - r.NextBytes(b); - return b; - } - - public static void FillBufferWithRandomData(byte[] buffer) - { - Random r = new Random(); - r.NextBytes(buffer); - } - - public static void FillBuffer(byte[] buffer, byte c) - { - for (int i = 0; i < buffer.Length; i++) - { - buffer[i] = c; - } - } - - public static byte[] GetBuffer(int count, byte c) - { - byte[] b = new byte[count]; - FillBuffer(b, c); - return b; - } - } -} diff --git a/sources/Test/OpenMcdf.Test/OpenMcdf.Test.csproj b/sources/Test/OpenMcdf.Test/OpenMcdf.Test.csproj deleted file mode 100644 index 6d086f0e..00000000 --- a/sources/Test/OpenMcdf.Test/OpenMcdf.Test.csproj +++ /dev/null @@ -1,93 +0,0 @@ - - - net45;net6.0 - false - Debug;Release - true - OpenMcdf.snk - false - false - false - false - false - false - false - - - - - - true - OpenMcdf.snk - - - x64 - - - - - - - - - - - - - - - - - False - Always - - - - - - \ No newline at end of file diff --git a/sources/Test/OpenMcdf.Test/OpenMcdf.snk b/sources/Test/OpenMcdf.Test/OpenMcdf.snk deleted file mode 100644 index b2990e73c74b1b002bd693d3d8008085b96f461f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50097nwG6x-K;LO^j4j6cs`(%400Z5@yPQnZ7{$0%=kHi|-&%jj zIZo$C(^orbVlD(&H`PYI`!B&VSLuY?Q>Q)El2oao)5)5QriW~iFixpxnXCq=jeExP zwIDQlYjkbW{FAWhZ)4zsWDM5B0Ai$5KWtA3WU3fix^?x&19(Tr`ai`}{|vA3d;= zvaMECN!3mgvGE5t)=hH7N)+WzrP#-#HOb%u(+-Je%Npw`Uv^Y}ekuG=&$uUWso2C? z_`m=&4^8V#^8!SoI$f{Y;uM2Qdgc1+0wDpGERes_*oK=gF2S}Uz!p3<(}iN-sPo!x z - /// Summary description for RBTreeTest - /// - [TestClass] - public class RBTreeTest - { - public RBTreeTest() - { - } - - /// - /// Gets or sets the test context which provides - /// information about and functionality for the current test run. - /// - public TestContext TestContext { get; set; } - - #region Additional test attributes - // - // You can use the following additional attributes as you write your tests: - // - // Use ClassInitialize to run code before running the first test in the class - // [ClassInitialize()] - // public static void MyClassInitialize(TestContext testContext) { } - // - // Use ClassCleanup to run code after all tests in a class have run - // [ClassCleanup()] - // public static void MyClassCleanup() { } - // - // Use TestInitialize to run code before running each test - // [TestInitialize()] - // public void MyTestInitialize() { } - // - // Use TestCleanup to run code after each test has run - // [TestCleanup()] - // public void MyTestCleanup() { } - // - #endregion - - internal static IList GetDirectoryRepository(int count) - { - List repo = new List(); - for (int i = 0; i < count; i++) - { - _ = DirectoryEntry.New(i.ToString(), StgType.StgInvalid, repo); - } - - return repo; - } - - [TestMethod] - public void Test_RBTREE_INSERT() - { - RBTree rbTree = new RBTree(); - IList repo = GetDirectoryRepository(1000000); - - foreach (var item in repo) - { - rbTree.Insert(item); - } - - for (int i = 0; i < repo.Count; i++) - { - rbTree.TryLookup(DirectoryEntry.Mock(i.ToString(), StgType.StgInvalid), out IRBNode c); - Assert.IsTrue(c is IDirectoryEntry); - Assert.AreEqual(i.ToString(), ((IDirectoryEntry)c).Name); - //Assert.IsTrue(c.IsStream); - } - } - - [TestMethod] - public void Test_RBTREE_DELETE() - { - RBTree rbTree = new RBTree(); - IList repo = GetDirectoryRepository(25); - - foreach (var item in repo) - { - rbTree.Insert(item); - } - - rbTree.Delete(DirectoryEntry.Mock("5", StgType.StgInvalid), out _); - rbTree.Delete(DirectoryEntry.Mock("24", StgType.StgInvalid), out _); - rbTree.Delete(DirectoryEntry.Mock("7", StgType.StgInvalid), out _); - - // CFItem c; - // bool s = rbTree.TryLookup(new CFMock("7", StgType.StgStream), out c); - - // Assert.IsFalse(s); - - // c = null; - - // Assert.IsTrue(rbTree.TryLookup(new CFMock("6", StgType.StgStream), out c)); - // Assert.IsTrue(c.IsStream); - // Assert.IsTrue(rbTree.TryLookup(new CFMock("12", StgType.StgStream), out c)); - // Assert.IsTrue(c.Name == "12"); - - //} - } - - private static void VerifyProperties(RBTree t) - { - VerifyProperty1(t.Root); - VerifyProperty2(t.Root); - // Property 3 is implicit - VerifyProperty4(t.Root); - VerifyProperty5(t.Root); - } - - private static Color NodeColor(IRBNode n) - { - return n == null ? Color.BLACK : n.Color; - } - - private static void VerifyProperty1(IRBNode n) - { - Assert.IsTrue(NodeColor(n) is Color.RED or Color.BLACK); - - if (n == null) return; - VerifyProperty1(n.Left); - VerifyProperty1(n.Right); - } - - private static void VerifyProperty2(IRBNode root) - { - Assert.AreEqual(Color.BLACK, NodeColor(root)); - } - - private static void VerifyProperty4(IRBNode n) - { - if (NodeColor(n) == Color.RED) - { - Assert.AreEqual(Color.BLACK, NodeColor(n.Left)); - Assert.AreEqual(Color.BLACK, NodeColor(n.Right)); - Assert.AreEqual(Color.BLACK, NodeColor(n.Parent)); - } - - if (n == null) return; - VerifyProperty4(n.Left); - VerifyProperty4(n.Right); - } - - private static void VerifyProperty5(IRBNode root) - { - VerifyProperty5Helper(root, 0, -1); - } - - private static int VerifyProperty5Helper(IRBNode n, int blackCount, int pathBlackCount) - { - if (NodeColor(n) == Color.BLACK) - { - blackCount++; - } - - if (n == null) - { - if (pathBlackCount == -1) - { - pathBlackCount = blackCount; - } - else - { - Assert.AreEqual(blackCount, pathBlackCount); - } - - return pathBlackCount; - } - - pathBlackCount = VerifyProperty5Helper(n.Left, blackCount, pathBlackCount); - pathBlackCount = VerifyProperty5Helper(n.Right, blackCount, pathBlackCount); - - return pathBlackCount; - } - - [TestMethod] - public void Test_RBTREE_ENUMERATE() - { - RBTree rbTree = new RBTree(); - IList repo = GetDirectoryRepository(10000); - - foreach (var item in repo) - { - rbTree.Insert(item); - } - - VerifyProperties(rbTree); - //rbTree.Print(); - } - } -} diff --git a/sources/Test/OpenMcdf.Test/SectorCollectionTest.cs b/sources/Test/OpenMcdf.Test/SectorCollectionTest.cs deleted file mode 100644 index ab5f32a2..00000000 --- a/sources/Test/OpenMcdf.Test/SectorCollectionTest.cs +++ /dev/null @@ -1,151 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Collections.Generic; - -namespace OpenMcdf.Test -{ - /// - /// This is a test class for SectorCollectionTest and is intended - /// to contain all SectorCollectionTest Unit Tests - /// - [TestClass()] - public class SectorCollectionTest - { - /// - /// Gets or sets the test context which provides - /// information about and functionality for the current test run. - /// - public TestContext TestContext { get; set; } - - #region Additional test attributes - // - //You can use the following additional attributes as you write your tests: - // - //Use ClassInitialize to run code before running the first test in the class - //[ClassInitialize()] - //public static void MyClassInitialize(TestContext testContext) - //{ - //} - // - //Use ClassCleanup to run code after all tests in a class have run - //[ClassCleanup()] - //public static void MyClassCleanup() - //{ - //} - // - //Use TestInitialize to run code before running each test - //[TestInitialize()] - //public void MyTestInitialize() - //{ - //} - // - //Use TestCleanup to run code after each test has run - //[TestCleanup()] - //public void MyTestCleanup() - //{ - //} - // - #endregion - - /// - /// A test for Count - /// - [TestMethod()] - public void CountTest() - { - SectorCollection target = new SectorCollection(); // TODO: Initialize to an appropriate value - - Assert.AreEqual(0, target.Count); - Sector s = new Sector(4096); - - target.Add(s); - Assert.AreEqual(1, target.Count); - - for (int i = 0; i < 5000; i++) - target.Add(s); - - Assert.AreEqual(5001, target.Count); - } - - /// - /// A test for Item - /// - [TestMethod()] - public void ItemTest() - { - int count = 37; - - SectorCollection target = new SectorCollection(); - int index = 0; - - Sector expected = new Sector(4096); - target.Add(null); - - Sector actual; - target[index] = expected; - actual = target[index]; - - Assert.AreEqual(expected, actual); - Assert.AreEqual(expected.Id, actual.Id); - Assert.ThrowsException(() => target[count + 100]); - Assert.ThrowsException(() => target[-1]); - } - - /// - /// A test for SectorCollection Constructor - /// - [TestMethod()] - public void SectorCollectionConstructorTest() - { - SectorCollection target = new SectorCollection(); - - Assert.IsNotNull(target); - Assert.AreEqual(0, target.Count); - - Sector s = new Sector(4096); - target.Add(s); - Assert.AreEqual(1, target.Count); - } - - /// - /// A test for Add - /// - [TestMethod()] - public void AddTest() - { - SectorCollection target = new SectorCollection(); - for (int i = 0; i < 579; i++) - { - target.Add(null); - } - - Sector item = new Sector(4096); - target.Add(item); - Assert.AreEqual(580, target.Count); - } - - /// - /// A test for GetEnumerator - /// - [TestMethod()] - public void GetEnumeratorTest() - { - SectorCollection target = new SectorCollection(); - for (int i = 0; i < 579; i++) - { - target.Add(null); - } - - Sector item = new Sector(4096); - target.Add(item); - Assert.AreEqual(580, target.Count); - - int count = 0; - using IEnumerator enumerator = target.GetEnumerator(); - while (enumerator.MoveNext()) - { - count++; - } - Assert.AreEqual(580, count); - } - } -} diff --git a/sources/Test/OpenMcdf.Test/StreamRWTest.cs b/sources/Test/OpenMcdf.Test/StreamRWTest.cs deleted file mode 100644 index 09f5567a..00000000 --- a/sources/Test/OpenMcdf.Test/StreamRWTest.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.IO; - -namespace OpenMcdf.Test -{ - [TestClass] - public class StreamRWTest - { - [TestMethod] - public void ReadInt64_MaxSizeRead() - { - long input = long.MaxValue; - byte[] bytes = BitConverter.GetBytes(input); - long actual = 0; - using (MemoryStream memStream = new MemoryStream(bytes)) - { - StreamRW reader = new StreamRW(memStream); - actual = reader.ReadInt64(); - } - - Assert.AreEqual(input, actual); - } - - [TestMethod] - public void ReadInt64_SmallNumber() - { - long input = 1234; - byte[] bytes = BitConverter.GetBytes(input); - long actual = 0; - using (MemoryStream memStream = new MemoryStream(bytes)) - { - StreamRW reader = new StreamRW(memStream); - actual = reader.ReadInt64(); - } - - Assert.AreEqual(input, actual); - } - - [TestMethod] - public void ReadInt64_Int32MaxPlusTen() - { - long input = (long)int.MaxValue + 10; - byte[] bytes = BitConverter.GetBytes(input); - long actual = 0; - using (MemoryStream memStream = new MemoryStream(bytes)) - { - StreamRW reader = new StreamRW(memStream); - actual = reader.ReadInt64(); - } - - Assert.AreEqual(input, actual); - } - } -} diff --git a/sources/Test/TestFiles/2_MB-W.ppt b/sources/Test/TestFiles/2_MB-W.ppt deleted file mode 100644 index 798525229cf0920a6ac4ae4736529e97dae04f4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2037248 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%0DdiHM-lliAi)DIMoK& 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 `4ud_V1#?`$zVV31#gz?J!@VrN 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℘*mmDe#r0DC@v>%RmS{J;^QmWU2u$V1e=aj((Y( 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 y2Th`@lOMpU|*T#o=%dqjxeRa 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

0{@T(u!H_X;>+E=(H0XIYsx6cF%=(yo zE6O%LJ+Yc?kBrfpYAv9a^wGz#*!d*YHZ*D-T!uYX%Tp$@g4LSkck_??(FTnr(iMei%$Qk$8p5`vuY5ebYxC_Jlu_^|o|wJJpgIBjsXKxVPD` zavXN#b#$>4O|5bRGBS>aeY!)3pjF+r*Oh>Sa2iw$v)fP&VSt+EBC}jmxww;Eu%SWb zim-Y$H=?^st&#{H`o~_kk!vshFt1i$Aetf`W&8@;3W7mIwaL`PRx=P9AcDtrgIiq(_9|C9SLvCoY-w%0ex|zZ z-N%h>p^#?HJuSp-&EFW<(MW8qm}a=uq<60*Q98o*a;}@-_0qVB>qX<;bQh;So5Tu6Ma*;4-U_mW9>nT}VT@K!yt5#I}dKPa(QVFA$ zPVt+^WR~rZ8QN>CJcXr-JGCA;+^ms7E^$lzX1c=t&eis?t0>=GfVfl(sKX#iIDs<5 zGIkc!+FQa`LSxlr?IVz3T?Qvutxl`rYqs<|DOV;q=ewna`)6F061W7U>bh30_9H6YVq> zRUq-$XUvtKBb{>?D;Jl@7Db<7Gzf6dWm}f#-B~@s-4ePmD&YSBA1FFTL`!!4r3bR1hJ+FGr@)-+f}h>|rqc-Q zsjmwwuoX5(Uh5i0%)Fmfv5kvLM-%|FDJSAjUr^V3hXs#+ci{uz5l5XM@r-iVu&qwk z*{-!e5>Ly9M@V%1lW(nh)m2%P+n~c%yV>6_&GdWp9cYq%zBP-}_ zcNeYof1D)7Y6u`xDI=95GqxshZQEFI19q^pp&*Y?0X|ie5)KQm*xLxoM`E6^T9!zX z3c|y~EbPPNhHH^U1adG1k+4;lpdA}8J?DFKb6f5dS^}h61P_}YQJCXV30~8*QClRm z>g`@r0CWCEai8rQ^~M?tD`^|~YRUMO(z)a??-%0h@2Wb;2$TupDsmX)A5w?zHz1Dn zeXSTOne~QJY4nn&a~flhZ`!Fd+x74zhvpBg5zOLTUubtB43oTt+qm`ZEEMLh;h1*J z^4Z&)iHQq;5-=bRYDmBaPrEC1cee*MX$R0Py}$aP@Hj7PvI;_W_K4tCqtup>KsDL117B_}5-{l(d2e#oIN&UXqau2#H{{VBpS>RAN z&tm*!j(A&J`+1n{m@H`IN08iSF~H}7!_2pqArsXhqDeb;4!=U%*{i5lZuPW=*0Oq$ z^2+mS{{R+TGLAzjJ>PS<+qv9;#7L8F)aeT;b8*%;R%mG$A=viCccp?xN9%a=hEfKZ zXoLB~ z8Ngr0icwC@;H6xV80Nfx<}-z!;`OjgS&}GVsr>?+vygO*u|MIdF>S?bP)F+{T{ZK; z=)9vtICibH`F6?d+k#q_S=kxX2&0m4696|U8a2ZVkPk0Xe&CRytvHjS$l){c>D*~S zh;GX1BqO2q1LgX_;&DB8oY5UJ-NjiVSnO)wx{fj(8cn1I#sL}O8j*}2l8`xjt8QT~ zrlNYO97fp_&(B-|?Og=5w#$V+Am!sMW;Z>yF&JQ%gGCc9NgH8d-P;KnO7AT=d&N#f zl4Xl@0YS;d_W+BGCyMP6#z+il{W?^c#{{=0pcv=6eKPai@Wxg*1GuSr3iV48@lzex z?SSFf#VSEwyGHD)R7m}$!~9g9o{$l$QsX}pnDYdZJqGC1;NVkzu%kf$AAkb9=qo{m z%R6cyG*z!vuv?6e_>#I%5->btiX;sqsvN8=&g{y(&qmLuhqz3&q5!2tw0Y&{00}Xm z#}q*;yc5gm^cne7(hf%&n&s4umYZ0VCpVbu;*{Vq{zj6}qZ)oXM-d+ih?v z%Dsfx+of6<^CgeUWQ|^75--UEh?PnABoSKW>xy=c+uFA=U8=;I2(2rX2_`GX!dX_5 z*SEJV#BnoCH5w0)X>J)&W4E!V6fYolt@w57B(hOqk9h*{fV`#)4iqFn(DA|N*Q&{BaW>U%8)fI{IKOC;MbmIgjJ=i~$J%@A+5PPf7I0RQ*-E))hlFi7d3s z8WTH93@H7^WJuymFUKRvAdu{c6^Mh>Wl{U)XX`bt#U#f$DGyj5B{zj)T-_AcKhKfs+I!oE5;%Siie5 zEOXQX%m-i4Vkie0|jDEa8JaBQydjsw2LV+8E`24VO zrg+rcOlK;qhQP?naTpx}05UtkgRY2vAoTC`4vB|>m>+>y+Esl%v>V5wJ`(7je0D${)1!;(rz{WMnAUO=>hyj^$ zm`@>ygu^qA(z_r;m;#ocQBcd!ftzfO0L$LHmcWyP*8j)cGm9p{XiKo|rB5P${$0El9$MnUK}76r0M z=s(xjK&3I$j3bn){^AH7Kl&h<7|Sj`0go?!q+{0%KseIQNbAXS?mCXWNyt5V6OaBr zxP!wTILD7n`!EFZ{{WHd-2v)YaqH9lzo)5;=rISLAjv2~2N9JY_X>=tT#!J>EQ;i3 zBxgRjC_FG=PQI9gW?b8jcvj6TlrGc1Te#|%Yg(-;G!i;Aa;!pPr14~wfI_-6a#dfk z{za2$w$qxlNmd_A3E?nv1QVVv_8rLWd#e|o*n0t?(B$Yy+@~oAF;P$qh!{0P$t;@- ztMWT?U1;iya_vmh>~v-+L8V%pkj<{4Vp?`%s^YY@Dbk(9WXRBfl27Ch*goZRecKjA zrY`|r0Ae%R#m?a-tOn6M;Csuzd;3kJV&&fZcx5|Y(gkG$Fi}Y>wqUKuVb4JxakZWq ztwB?HE~{a?o5%bjO@6mZb~f$#57~yUgX<~08Y^`zc#e~Ete@*mhP9xyU%dNB=9)#8 zSSIyL?ftFGtXH`EaeFq2JGX!>l>VmqGWwoB`?x;B`AN}}&BG6WU| z+*DSwqLqwM-*WwLr1Gy0wc$PscAl?cruwt(ep`vC)20FA8 zs5AE)7EjynAik*L!?#EP11a?#sye;^fe}%UJT*UPTc7Cdx7M@U*xR>%$~-pivi+~e zv#N*dKO6CXC!zlUr0K{a8)~}^XOZmb+oHY5=`Ok{;Q$!IAEEdE0Bzsi-S5>C+*@p* zgQ#*Cz%_yyO;)QDx2Sl2-@3jn-0kJPwjh`_(1Dc(W+3OgTzVJur-14FL%}>|#@Aa+ zc^3K_9%tmcZMLghKJty--B>Kts}1{A<-4x`0MnWYVuU0zM!oL*m{*=b>lu$x+({Fe ziR4L)^Avyu00J>u_2#(eYaN1u35sh1gp-*yG?~Llw!4iFkFASRL*!O!6AjplR(25MV2@q-t z%o!lTB`-4}+YSWf1z=Ai2;4S;ML?vCamSi?Z66|s%l4jh`=iJ0L*{9};9`_nCKNutl?@uA0d(^kj@ z4lq}v1MWJUpJIK1{{ZLcfW~VTsV10bpz0X_IV?cV56G6vk&k6o{c;V@T5=rso8CpInR~Z51>lm8@HR%+bvT;D z3}o_x;eiDmA3PO+I1Ek$5T$zm08Y8}8k%E(fn4VrS%||aIO;|i0C{1s0sjDxPF~?p zw@*OJz5fz8;8ZSkJFK2!Lyv_lXdn#pwFTDbNz5%SC1OvdxeY;r2%C_ zhHrzOU#KTNf3NB2N`NS6bDX?T`K0LAYrH9@ZKmh8@NMcVScnlXHfPZ)G9S%Jk3x84&BhT>h<}fhd zxEaP>;q1y}4bOBc6NFgH}7AQqA@UcC9M+6LfdUQNNJX_o@2|ur>J8PcrJ#pJ(L5=aZUWX(90HOZ? zOcF>3JeLFhgQh)l<^d9YK6vONgyLrzIB<|bR+>&a za)XbtOjPp6CMN;oVmfmKsls;KC4KNe>(kKgnIsHKoVnsbvp2eYRJI4PRX?L;jzTxL zxWM-6PIxqbKi3nwW5?%@pOWsyos{Pvzn3g$1K5zG4J+m2^~Xr#Om7oOobzG`!1iK6VtQn->H+ru0H?pG zm^BfCG5fN{aXhlfvBa`Y?a)UOg3Mdn;;I5K%iq`m*B<_biU3QPQJmAqko3gH=l~hR zOddI~jZ5;)8bSha4-+5mgTGc}LENaR4T8e=0L)2vD`PDsfeZtya3_MQ$n#b{b7I1gJS~Vemizl>^g1$rfY#x4pVh^bs?sS&88plb$%) zHw|p3xf4HB$2tOd)0UWFgI`Xx2DZX~9f!PEJSC?}Cz(T!omh7E>$7m`K#Xz8>R93G zH+by(?R(NRg4{JK2+N6|ESSBzsM&a+1~n0`U=#Rx8N>@4FAu5BZC8QgnH7lC=&Njt zn9!+sb`n*k_-wD#wQ>m!@^)N$dLI44mAXL${Z|T){kxidcu)%CqqkqK(1Jf|%_BL1 z9+j2(;XM} zzSB~cYirW1(rhhAu=gU^3rRGDa^j4d`J#p@-2=KxtH@&~C(&;2E)s1J7QePRYQV|R z5Kgqq70PzVv2d9g!J6iB1P_l4b?<wm3Uobv8+P|LmC6o;10HAi@EGBG*(5JjI3K`xDh{&Dic z?zZ_QGXDUKeqWUSIOdPDY_;)NCbM5V2GB zd^24i5Wabz!->QrL?E_v%n9&u$Ua}BnV#2ns@c1?HfsxZ4O%j5mUj-(8TNNBO7YEt zdW9@94s(JBvbM=8LAD^o7N`;XRYg8nwA&s_DL)0}S(1FUHP02TAFPe7DMZn4eu_oTt0D&WFx72fzR#=fV0D?Drn{9A_Itcot$jV96{xHG-F(#2m*ZM;L z0Fm!oUt7f^he^8=)`I-dUb#~C+*qYvuD;~*K?dWrLIW!6Ca$*^yjCRzOJ5dyt^Kq2 zebYVd$6y6lYz8}&0#ZhX@M4E&RLI74%cxQPyJ1GM36RF1v`&zAZJ96tsO9gzKDU1s z-tF}(rKfE*{x5GwZxm?l%}uV({TYRbB(&1lrClL2EOW{d%nRC)328g8rPAR3b=iN43uP=m&= z?zg@_V^lxA=|2w8(a_Ps1#8|@u-I(ojp}RTl`1tytrE)bS-TO%5}9K$q*pAPKK+|^ zT?q%MOF0mhz?m^KypSZ$<3pQcxc2*odv`Y*Zvcn{lW^r4f`Cmj89>G3;ceiWU!i<< zeZJ%CC~Rx>KVWbCdOsn%_U@C&HFS1=wdHcADE!p^0`u2aieVO3iZ?fHZ z{;zec`s>EDUL*A`tLq6H%RGoyF<7RGYeJH$=idI)_HNZL zedQmt?fsK#$a!nmE}Ne6i&hZczj1xCLn@1pB(b(Ft2m={-ut)QJEPeG_b%UCu3jyo zC$^+C4Hu~;+)4tC*_Y2Y4hE{Dq|r?k~x*WUjCAbdmZ3HH^z+r#X5{m&hT z$UGxpw|4tWJ9^r+*fn1q*htjnD`wnPIaC&wByU_l+`rX3i=S+_f4zHm6S2M9`z_n| z0{;NG1^5YRZ8NfNyJ~KrYibw^jck$uCt>$4**@a-+S_jKlJ4Pk4YG&Ys}jIWBb|l` zfXW#}P_l? z$Ns>*k`B~;gYF612X^-w$O@`%-E!vIv%RZ!$yAG*F3YN|vJ&OQ@&5pJ@4xvT_4~)} zlIb?Aq4wC4O}lD*~_t>y{GNPJaRXa z?LOGbt=rkM?zVu=Nr6%r$4z6XjjFPuVlnM6eamo5_in1(aKym{MuC8qlz?b@%R_xF zI2e?CcmDuf)AAbHkFZ-^;(mMOKXz{?*TnZ_kIox)0B zGdBy{fKJehY;7_{Wq|}v(?7@;#QekRKR5C}3Gxk(o$K{K*fl#%zMAH%YvUI7+fOO+ zzs9L&b6(p^M)X$fsE)ly+*q3Vj=-d~q>bZ!yB*E1xOTR?l-SN&F%9&*wq}4pVqykt zOn^Ph*?+b7*KJ&O2ox~}RJQaxG$D!opt&v8wMNr{ac{n~@ES09_N#S9+`bd$4fcMs z!4KFP`24Q+_7~mxcCX5;HNUI$wWFhI-FYi^scEBFE*bPx7R@%@F z_akGDf0sw#o+U@>U9X7$0BiB>=9|p^ue-9emhaWCuh{Er?$=!~ZuGRYlWBJl#zj8b zt$mHyQbH8u>kHlAd2{YIyPeN%t=-8)kYEuY0!dOv?8@%i1Y;O>{jX}hr*YYlZ@IQp zaV4Q%7=tY#VSq7^Xxt*Wy%$G*ztf*pQ{Qeh+mA2tKP3B3liBgB{uRHqt??f>)BRJp z)!f+G@O!(x3~A-RW9odXc=asA_8WDy*iA7(EqRSrwwC+PxSrPAr(pK2k+@t@xlPpo z3!7V_*RyO#>_k|h#q)aQ&d2R5-CnkP+i!3-#Ep8$qB)|D7?#zgjvZX}TfHryVRd<5oUwZHU+UuX z_up=Br49Ax!j+i%f%Bq@81^6Rt7_e@@d}2y?dwR~YDj<&(m|;NWb9-#b4Q*h5oY zQ*|AvQT(p1rX5us_Q%KeQY|UGvUc0WX>4qwji^I%0*iLZhBuJxch-|q~*{KI3qo5i$rYDK^D zYf)`3Pfu5BFCXLO)`ZY_{YsmdYE{>GZ-9M$53>A^SFtndZMMQ&lUu9xFNIw&*{MLu4BHDZSG4u(bJo_Z z`y%x}$-Khr4WSHwZ3q%T00VXA3s-2|yN=gpi^}{#RVjQ(NzMv z(|vy+t4PDB(;IcEzhX^+tm$3u%h;M_p4?zohJHXe zJb^%~)Zs_2$Z|e4%g^(|cVtiT#qsrz(2Kp<>Md?zy>%yanw(KQ&{h-3V3D2bnzBzk z(Z-|#(nP3)bS}dTK__Sw@#CKiK`;pvA1@wWy#03i{{ZSOcaGD8xeQC%iWQ{t$6U!$ zy-3PhZvshr*UffsVJf_C}#9JBXp8TT5#>MF3#8vOv zwx}ns5m=Y83}_>YG(Z-G?W|mFlaQy!T4^3+l4lPh-8N~)z9;gISmq6e#(R@VE6VKB z9J3ySr$t2o;X(|1V0+`PPpce!dN{^#`n0Atht03TjllbmBqmNzV3=?Xe4r*Z2OSe{E0HB!rE;5z_#$sUF%IMi_zoIzdUc8MG)V}~>2JbFp>{{Y>; z+@`rY!@GjErp$4!!0V$C$61||@?erF2EKGjC%4V3X zRA|zCd=5I&pO&?_V-?;m$}nqkKm^n#lHnb=YPf#lIyfvt3>6uTx(;j;00Lv<@zh`s zmK`bVVx9ve%?VP*37fr%*BWd9RN3eZ}U}VO69*5CPK=I>_2g}B|W7Y4UoNqgt z^2mEN!&xkANdT2eb0H#5VR{409dU)g5hNO8iq5#PEAA^?3k2}S(#ll@8*vwg(`A2} zyo~7>a!Kj#Py7y4mQ2Mf!vhhma6fV29y#O`ETNz_GRYfB95O14m3i|lmEO)cNXnpo|!!wj&rPMyY{AgV5{ zivIw{TZnMEWx)9QL9|2=2_G@wV8NX6=H~mvcbaScjkGl`ejLChxF7N&NTgVT{zOs6 zWr9lG?j=AWl;uesJpm@1e0cn@&B2HsJbdw0`484>ehV{DwKTRRV>FeXAjnck<3iC! zdtxPyZ{!h&$pyNDgX#e@LHhXT!kCcY8j%3vq&jBt{jE5*P+q?TwX18dwu-w)Zl!d- zn|ikvYLZ82?)0{)#XSoUD%^SDh$DVXF@^`W%#7Ax5J>>mq!4`?NFemo&q+A5Y1EM* z#78)ppD;xTtce*6SQ`C2kY2sz(%7$F^({*uu=G=FHW2vll=!WR@~yuy*2S-mO{8*d*7))g`5ya_-S@xAs`uEjE}zWpVoq#9UhJZQtEd)UHE@wulLP z-se)UTAYfJ8-{YHQ4)lRfdoj|>h!g|QpUOq6I_o|a;*xJMX;x0s#V!Vxc>mqo=dH@ zHArdiJXyAazN=$Cx>$b_&G(m>jq1Q{Si0A)mWV7HLoy?JFf~_+V6G&9DIlCz?k$8( zvmr>WpA8dWJ%b4_BC5?p(=qSK1;? z4nP$oW(wx42q1EtIqQ6@Rj`(hw@IzsYo%kVWv~6*?5oh8y;g>ztJRJjtgTuxm?9kK zSXcu5%P3OzeZoszdv8;c>?U*0R7^?Bt3w!uzhQ_99cVvx?7j!fmbkGcj@w0>`#E;n zGZb4Eva45rt!d-0EHd{+>f0x)wMamY&33U^#LWDUYb-OzPjVNx?H<>0lmqRs2S`#B zsi}~z3DR@4cAG`6URJlptPc}WGBuKC6&Hr3a#(QPVgD_Vv^uU>fdwJf@{rDJOIEpW0yE;$yM)sQ0(isbQ^?R(pQ z_U>F|;tR6#kuo_(>WuOR0qxvOs-6Y`{2={8?X6&<9GzRjlR!X&{ehoDi64_td&k{$aH#A>}?ibom(b0p)5Mi4JR zCnHb5&Xt}TV1+G;h!JWW1eez+$MYV%T+-)*sv zSf9r%R;Sp1Y-N!oPBx`#sYJ@MfWcZB5HR*-t%^90^ya%$WlP6I5ZIUTS1~s8FMpIaV9YJJJ zPJam$qsv#<^eILh+G@GCJV!W9|s_^JEp+;JW_+%D<=T zIL;TmgCK1@sp5Vduu)}_Ueih|{L0AoC!lE|jARn>Yviu-N6R4m30!0mgXuI~+e=cA z8*%gu3?E-iPt{Wutd)sOXiWIv3BIjpG4;nqfi7*BA4&;(P1f?K0)-avNR3`SLtOr~k- zd0<%>sb_`x4_ebf=cW&8;`}*MdLM;)My(UY+*wk1+1D(vtnpi}vUdl%NEeUpIRFax zubZju-zv88J1qSxMq1a5*|H;i10 z2oaV}EwTK1hO#CK%C906IRGOJtnd4F-N(Chb2ATgNy>J~nV}F2jL8PP6S|ld6_gEC z2>$@xr#T~CMJGjXXGgr2D){t8nfb^0wIGJJ5J<8-Pkw|i3ziu#Jjaj+t{9ZuUf%BA zw=WnO?IfDUdsxWMin@(J#Qy+#-8NgFFaYzYrbAsg6NZX=)p+H27+6<{k-xRA{pECK zUgHk-x}}BUI3JI2uu-3nMl@TuDng!^Gu5`)_%jcT0Y57U`-N&bOOJ>K2Pw)j*Cq06 zRpDOF=A|I>J{sPuVM1h1()Wzh+uJO?T}Q;j$yK>y3^MU)n8PjB0BLYAnc%A&e{@J7 z;bfwM%58~1(F9UVlGEo&)?&EbUw^WJCYND7y_7(qNn+G%Hnjwj<|o=Fg_ibBO3 zISQrEA<&sYDz+U_xHGbva64&{nKAt^4mboGnbeRXp+gV<01CP2Cn+v~&}=6GPA zh~`?pq8&t#$i<2@uxzPJ!eDX>VP(k33NXmOeXX=90P}Ldjzw5gmP1)IGqGGp?Tmtj zKmALeIvrzCrg&^i1Wi)WjB(NgTkdWGRX@;@p%TfY-mau0`8C z=+*Puc5?os<^~13S7ip@abNmmYdu4M$bn3&g{w9DFrl8_x!E{#tu*Z;L>V1poB6AV zBZr9iGnmNa3XgKSHv7c7(ze`ZU8cN5u4jk<^XEqAZFb+(>Uk6Mtv@kHV2c<8xtAbgp~xf;-1~I} zMYbRx_<``5B;}M(k--ki@gdsqk}35PN1OrcT6$oeoeM=E{Ed~=kRves$SbgUx~Vlp z8*99yIX|IpS^l^XpKt^cmKSXP(sJi0uzpd3Zp_bB_B7N?pGH1he*pR6m+AP`sJqX7 z`Eft_S`$yp)b5($AgYDtzpCF82F&#Ltoa2k^&K zc=wxWL5{`kjH(=!hP%UFDKdDn#zEQ1D<09w9km6r4hDTQ+;=-{+8oHQ{{V|0Gs_5N zP$lkAXlePJTzY>brD#L=%{8d_vM`oJiDSzaRVFI&GAKXLvwyBKq+aVK8MdE^`IGno z_~3n&X??Q40rC_1#9`(w)pqT?Lo1L7S()V+tUUh! zlLfvZn+3i@j}_(a7#v_^fO>WH!N?UA$8Bm-xJ&X+CW=I4$sY#_RnAF0ITp&2Msb1v z0MFMklE+WS$AOLmIma;ho$9t6w2ht)uNss61{h^{@&F#VApV|&NI7AY2EIQGbH|X* zFGynSM6rTZFoFfZ!2wl44iCRvbnEGgBQZE=kf2R*SzOZh3n;`Y$B@f$>ZJxhP)l(i z=s%(AeG!O(`qvyl%x8tGdPtz<##Lf6`B29Pm(Ma3E7bcD`u_l5LTVtlkDen;Dc!Ck zBmzzW%K$nMMmi9_#B~1vL+|Kf2m{BD#}XqgFuP^S1I4|_Kf57`B=HPA^Zx)*{@#kC zDU2NDh%Ga)D9+y=om>%|kfpfe+po4s{{U}YFP$Ls{$~O=YIVlGjy_T+OmqYK^2Sa( zn8&}r{yw{CH7o(d@SYfs%z%K5jz$N#9Wo9=x@RDt`u_meB0vJ7zmLP^#{*q)ta6hD za(WC7JxBaN1F86f{XMC@$p(>ebD!`DC@{Cx4% zu0L`Z97#Phf$(?z9D4u*j)OS|{rz^<21J2~fDIvZ>A&EF2uS|6H5O`ySX^2yj{0eb?u6X{=U2~N!fChS;_37(~ zVj~icn&NhFp>2)ymfX=?ZTM{!7G5Ez}?iA82o0V*=xsRtk? zeTQw__eZz?09>)#caaG4e{vnqKz6}WDbyOfVgQ^5^XjsFgR|64sJ{AbW{beS;(@ML ztpv+=Szy6qU}EKS>&V@c*<9bo(vte|!IA6c)+ zz%-bd6hErzwYpjrwAy%tI*oi)>Qjw4r*;}}MN%8LnLszTwrjwxJ(+pSG?^^Nr>P(o zZW3wcW@DKoo+OfMi4C~#f2`;0_yM5dieK$-tuFaR4=C99-1zbh&w=0Y$#s`3%?&-i zxNY>FNew!7;Am#nZ05D&u}2d~?M>f@ax^AMN^V$}7gejDz;V+TEO#Q>`%D2s!0SWC zH5!^2t~|og_{HBg@;@8#-yIeI0J(W~-kzUJ7RKx}YAu^~EFznm3xbptYd|BDy6&jC zi-GwzJ#O~h-*5gi-M(4?8SOf7ryf%gS`fyx{^z!<}=q!CIpymz*F&` z;ql>%C&PZb@;^P*`4yiLuD(IC@p{^umAp+TUzf;y#rM~&R64&E(ZI8%y`2isSiLlC z(3XZJ;^tWrHp9LB_P4#%G2%9gRLYux9I~jzhP;*n?k8nxasWrlNEFs-k48Vny#wfe zyxQu7)Gj?wvpm+E+YMx5DweI$t>bjBHE05=MHNdCek$>2%GU>x=%Tx>+qV>xu|NR8 z0DvUK6F|}=)RE)`UgqZKQdEW())53O}G9w;AFy>^(-;gVT!P_=@bbWEOh_)l7mpSvs}3lEbHuh0K^Sax zW7LmAQB7+;HTOVevd92u;8X%|yvg?I)CI%iu21RH{d?P922MDci8#yi z4qK=vpvuR`AJ_B^?sJUi{{V-nHqZ(KjtoH0$B!I#CAJ3vOM8xy6O{qJJ;GFiK+m`O zex9J)TA3r)6S&te9tJjv!jO4!VS=r}BvMaM!0={HnQZ?6T;u&jAp{r)_+mp7L*Qam zcCE^I#rZP0_@2VplN^6=J&;EppnDF!-kG>XMGqVf&|(LV9~=^=rSc7fs#MkNr)=^o zBx_bQT#&!ZuzJy=lDPN5Jw3?u1??&7+B|Ua8eq0N3Mro+DsasYf%)w05+5Ae%w#-J zrHyo|PBJqh-*|u-&*`7({XpHZnQ#Y}@SI4ob1jwg7_oeN{CN9McyS~?Ii&bBfjo!E z);k9bI(|NbUB8q&E?Gf2b1`6wh$CHdJoi(%Lo27!X=;+#Qs2Q^TqMwf8gJl zZ&o=E**-c0LbA>Mlj}dUe5yzcgri3f;8&)$O3oEY!sIqW`!A~JzffUpF*C#u*9=a` z#CwCt1B;Jc{{XgUlWuF(n_2fHasEaGuekaj%cxe3+>SlJ$NLuL%o4zGo+wh?SHE-IgulZUG9hOUltNbjoO^c z4y(qz)naP%tAh#dvjod<%PJ|mw7DCT(ud~5=06-Z-)L0LyTf-LpgvwQnql2P_IdvR zCv7Z!kGwy}sAQ9bU8rB~3hoaMM40v-KdlniF{{UpK@}B4uC*D8fl&d2H&9)zG%v&Uu{xjn@U}8|5 zgbBzGa0javzs7MJkt0F?`Jg{CYo@qU_SY0kU7k6fSs5I^OgcmV0I`?(0~H$5x8A=% zUbgVWZ$Uqe`Q3Qv%CBfiT87prUd^iM92M9~V5!NakvmJ;4`MFayk)Ilw5$IBUC8r- zq#s|77%W;FL*9|BsHU(Cf(JiNI0d}_0NGdkz0*byjCqgv_vH;U!mc0eAF+H+D(L7E zM=)$YvhpEpqqA}TXAVwyC&dDP!0w&Dy7qPfrhZ*Yk?HDYxMsvz7F-80^%4eLw@KkN zwe%x4A8OQjCVg%>+JIeHGkMw1P(-&9-g0T zu(EpO&;I~xO6DLo1l#Wj9d^AGlgBcEaQI@K@(=y2zxN*1*uNuxj+1!^s$~ta@ej8C zg}GY%H4v&zMK=EcVR&zmUW!DCdl5-NEHJ`$l;M3^{{RK1K=aGU;b*)_Z*ZYLOYCx@j5?X?KM$u?>+0)=}BfNJZEFGvq~0Z4y$1+&r@6sj(=!Eq+{X-Q_b4kV+(jn zi2794bUj9XeDUApC=+tr<*QGeP!AzPj;Q+o0K@6{-laT_)hTuR8pH6aK_?`hZqo(s z;s-?UI)1BxpZ<&A)5VL_+{)3!Q^$&Bg&VfWAwdhs{+>9=z;7w?YI^x3J1KS)>e*M1 z0-WxV!!}lawsj_>+R zQ`d$$$9bTyZtTtd-mA?d5(u5e`XqlC1GQ2bQb1dv4AM&`LB>GOE7m)!tYYP}vxN`D zk`I&t@yP8xyJ8XR>+%46Nt{#L4So0_HZ0q{I?@H2WVRxC8C|3iSCQ-NO*q<9pV|Jv zWD#oRg=HlXLH_`zYsLXtr$0UzY0MU8W#nM|<~|r>i^sM;L7;6u_n@b zy-d-lPWxe-LlN@J_-BQC#U&7sho9%-n7O=D>OCIjj;C{= z&{l>@H6#~BiR`_bUFhCB31QiqNu$9p$EuPDDspm1v`dP+L;>&2l|;71vf;^)1c>lEVr|5*JzJPz-EYN9sK&_Ub88z~?4u ztdp4Io+o;hVA=e%<2*6SALN(N{y+C6lNGkn_Mw2tAWN~50~KP?Mk6Lj)cK$Gj{g9;`#;;S_Z^*wx!ybNL9^UMZKNx;wz+S5um1p} z3>m;;SOsH^54GC2f4a7}Zd+R`QW)*r2L=k;PGfT`D$aN#tp7b*_$`p3O~U-J=Y4b(Gs&v;5=z zWBr=t)cdb#+;_il?)%5La*c0l_Q>2vufG)?w3hx%B&pPzW(vIyWd$BR9(21 zEcWF9LasMEI1E7n-PwXVjpafHjio_Q)xN*#Un=uI8M~+Qk0sarU!soA<69+d`%Aaew6|6YyGj(~iC~UnB;}iW_uYQg_Riwpd2h12-*=@0 zsO^Qe2oaqXltNmxj2mK>Yi`23KtKj}Z`xfeXMM%(?SepSoeD@GkPs_1z?oO+U@8e1 zEpYf>jeS+&J{@Ces_~B-+inds@*7%z9RC2+-c1!>GJ?OD{paL9IbT)gI)gqg$XHl-on{TlS`3}OE?%caleSMlW>p)~`5-V7HdfT?`NU+zAD%IhN zw0_&|{{ZOypJn!k?K^AlX2S&=J6W`?MN%C}EgOLgvg#srTxPiJ&U3DJz0Y*k`_AGo zUz4=lhAJii2Wskiiz@>H2Gt5H=={Rg=_86-`d<**=_*@a9E6LDf>g0Xx!R?`u%Xl|s?e&DELcwF>Kj0j0-|JY04moL_y^oxIeT@w+xX&K@q2oG zmd8uyo-^e8N7s1daMZCq4TiqY*4vn`Cec5)#U)s8Q^vYT&Dr1hRx5cQar?L0zT!Jx z-?i^qJK=Rz`+L??b=rW)%iY^6wj8a-w)1o!WoLUev%T2f?d;p`J8-SgY8Vw`qtL8m z(inhXZnipDSDrcK^))ph(rRF-cD*?2Z=1-k>27PMU5%*Y*?BgzXJ*-*8%$|I4|2#g zD(nqWX3{LD)TBJE{R2SU@lYEKx5{0{SvaUSr~(dFM2j#k@YG8t*Wpe|`S|r(oE5G1uz6 ze%)BBLw1e!*i+g^s5S1wC5VoRO78U6nN`$yY5WPaD}N_*eD+q><(n??2{ zyQ>Fq3a`~|6d<_w84(lcF1vPn2ia@<)D}B3o0e1w)pjW}P`i%k3Z`DLrSI)Eul`4G zZ0t4v0B888jCogs`2PU*YkGgMd^)bO4Q)!ADKG5&n_Z{z{{SAjukeo#@h$D!n@d~$ zjY{IJ*sDbt3U$%P=6kR9&i?@H5BB8y&-!<9-1gncAYAP3p|r*BZA)q$?X}M1xInhk zwYK+3QqAr_x>EWjpSbS1XYNAPFZK7Z(2jol@0pyg%+e zc8cV+JYR3&-(t(-aPBoeJ6~%TifVWEyu-jWG_+7?W#0Ini%w>(1voYlQ`lS5RA|IC z>&UA5{{Ue7f%~u9KG*J9Bkp#;$G3e&#$p@=w{*4DD{a}~`mH_04o_P;n6uv-KjeMU z`+I0~UcZb}n_(WhYFnABl&y9vRyPJ9(5GtURsG++zxMwCzg``u*}J~s z99wo4_PI-eZ&-co_X?>fY==P&w#pU^&9uL>e%=1WwtKI*+e^f<$>{|}%NJ854B#-j z%1-ew3Q<8|8^*qh@Cv?X<&##i<(nTL`#ZwC);{f@AJJ<()*Xyc_*}jVC?+e1^ z)liof?Y))~-M-xfTQ}K*Nm|{r{i(Z~owbhU`RwmoVBgzOaiOggDy_KNDrMQd#(67g zK@v^-_GSGK+I~CrY_>dS#dGkSX+?XJ-k-o?;w*|?t7vd+v?+3IVZ2q|-^&$fW> z3eHHC_qRuNV#W8mq1Ej=m)Hi!7n^F^3dR(oh?Z$7x{`3+zK_W0X(`d3_Lpa|6IJo+ zB7YpGS6x$X-<`6*v5E?5QeAevD~p0i?B1;$l2sGPvd<(@N1yjK?}fWv-*005yJ`*K ztJO#r)lTI**|#egw&GraR@r=0v){M6K)Q_Ng_Z&)cQXip7cga%NCpcsfH!yIzDM_O z)!%5)+iCRPG4>S?ucXmZqh=d|jg3E%qRZpfY)Y_In?<$INw>99%WL4j6JJT-ifc1* z665w?b?tu4EO-6I+qb)0eawxbTH^;QR@@kGra^YLMUXFDTS#Xfi93e-$8Fx(bhU5p zBG8dYC}6D8g-aD7LxXZvOBkBR>R+yCco6akw|+yU{^@5^a+HzLpJexSn&W6x+*RFc znw@>ugd_B$OZA6;=sRi;|y(`HuE|M}}OgE2ogr zp(U6l)Yj9ywRDy$G!uD_x|W`_xAdMj4vjo=5!YuGZ{zqHrXwOMtM`30Hhsj=Bs+r!?LJzP`Oq+MY)Ou!GvxVC_0ZL75m ztbHN1AXP!2>miPNcGhHEU=LC1p`6Ij9c%p~ZbD>bX6*b-Uewvt)u}F*K~#-0YG}gI zSBFuzS6LRk1 zwLlA?rkav7Ctp~MM1KfW+E$*QjWz4yDwb|Xu%cb6wF@@lv9s7ziDX*3z##7lEHfx* zQNNKK32np%k%vC{y6k|!h8c)pr{%s!Yjaxu_VbP=ZgaM_+x;@8nQ+Lj$8^eB4qMC6^}AAk=Gr14_#4N#08QG z#YN;hNkcTL$Gnl{Rixw~r4Y{^E0kFzBn>I~Vzct!E00Z5d86(m zi`!ay$mD8wZ(DZB=FXDdSs;ar@pWl}@*yKr=|^w}L?07BhvUNx0B1tc=sbTMQTKk* zyH2F{9=GLe;glF|N^Cs^TN2fZ8ERIvqcuHP1UJhdRzJKkay;B9+QC?coD?-mB zBFOIYb7DQe+fjhH_BOdPSebJ)nG*!oPU+GHSZm)hQ66o90AxRi;Yc5k9PK=VbGTDx z?HKOTub9?l)ylQ&qZToNdaFQ=^Oh`3jmlC#PyiOwwy|ZsyM4ng)28rAaV8DI-L z0$BlH`f~LjpXY@c9T=8|6A$igJ>KkKU>}1zi5CEL1iAkJQ|jmslT`7DKPr49Jg}o0 zYva!l+?Nb_sKNb680q=|J;46};r`tY2nKw5_~voKlsJap@*J~*2gGt6P67Djb^Sk2 z>+k9SGX{SVj@FpaQ;+ph2e-HD(QpC&xFgd7u{`nA$N$&TB6Q}gqp9hW{{S5P zx*k18{yw;Me%f>WaS}M!6JSJUR`16Oj+~_A&e>6rA%$95GmL(N_0Byhpa}{~a2XF% zOip>2#hcBoM^-Pwk)VF;ao9=al1UY0RFPYUS3s=1eX89Z(}JsJ z-owO_enKh3&bTG^)&XYp1Nq~>ezN;sJ5?dtZEx=Mb{_1yy53E%*hO|7yoR*1PCSdp zuWS{q{Uthrv{F`axl$MUUtFIWWZ6MP?XIk35l0cR!gWiU)G9+cY zE1On3&FugbxvI8)ha=060C8I9Zps+Uu7p7zygX@+iBGd=R*tNa&r;66Vq^Z<`3Yvc zaKP}bn53()gYsI}YM4Ul{Hmy-`zlq4v|NcG(sdsi_?#eCgj0ycld19yh!!a>o@>{{T-P9tIw$`2zI7 zkgq`;63$GNVN(#oaLdM{{sStY00AImG&G7D@H8>wLsGKu zD$bKDCphd9=p4?c1QW@QI!XtkLS~@J@lPB$&H#-uV{B;F-snV>+Gk~2q!5A0w_-$aUa@WLDQNmO->6YISb1>cR0&fsaZP#XJGQioRxxr z!Bv>DacufaASM^;;p3MaG&JYq$Kj3_@{bMI>UME!Usq<7d?zpDI=gQ-2jJ4St7EQ` zyp>i-BCh5e1?t2UW0_&v^uD1@JWeJ}F;!Fg(!X-;iW0T?t43$4$qK=4OZKI)UYwCZ zG>>H^XA3Do$&sI_bCr0l0VX_txG5Pl*A5$R3w{2(S7wQmP3Ia)^=0v0r8SRX1^vA` zQXQtV@hyldQipDB%_)bpBeE{FW<-iaKC|yyC2resyS92Tnt}aOnTQpcU7@lQi&k#+ zBO?{MwO$AQF+e6wGBQTB=Y5aW{!}FV=(IPq`pwaQ?jAh^)}K>8)s$R&I$D;b5y>y$ z+lE%v?oiAgJ~B@*Ln3vnM%hio8>v9wyJ?716%av&LRU~1>A}6#Ah7xyHOxrXGBPw^ zJkEF{r`WExy={KKT`KuMW(_oZ3RGg;t*E4m3iq`r&{xz=d&jqREuw6EPh(eMJvbTO zgwV@glvYS~&iAgjynT{qX=nh+AjF-nB2w}c4FEVHngOmLTYFM2L`+FE6IhmA84Q)K z(l)HKH-%oUy4q~D)vJnksU5^rElwY4uC;mxwK8jsSn8(xODs3l=-)+4SE{^5YPF^h z7&~_^6xn!f+d{+WF~kx|5fxUTj+SMEir18G=`=cPUIrnCp@xR44B?YOtlsD%r*COX z@ZQG@aMtaujcu(It2|aNujO|kS4pB){m@yBCg2avLlw&@eFEH2%b4@xBD2g;>w!y; zZ0<%OvOatW{Au{&$9Qz|(f;8!tZQs6b&^Y3&5z;g!IA#}F4daVypU|`R%KT2O0zr> z6B*>u&yt{0_ytz@asL1@Gv><-eD;(5L9kox7TnhMpZFZPfYYA}QzEp*akJ6*bdy)n zJQ~XTI#JoFdfv9Qkk`IHK05U_mVA24)|*`R4R??7b^9w2@>V29Nr zczMTKwVJ!@GeKgwc%B)JIe#H1J)_%cPaFK+_(bGGh*)S)744QvQJHz-~rCj@P% zUW7j1{7ix77@B!WsMavQz53f}teObw*0n!+xtJw&E15gC0Z3q)IT|t`MEJacgNMPw z;azX;wjPgpV?uNP0P%hxbJq~vjmi~ILE!#Bfx?X18#E?V*rb&tP$UkqMKM^}u(GG& z&0^BBlsUv>iT?nXzpJU=3#&FAhzIO*C`qdv?h~~G|v&u>qoET%yavMjI4RC zeanKx=b%6QdU`))B*n4mcx2-IWQKEE%A;ibm7`^K zBy=P&2UpwB`$*z90x3cP9*{&D)}V+hJxXK8jc`J>iqVlNp7dsa7E{pIx4D;8mk}vuP7LAzqe^cGB}b?#z#Fe#F@Oa zYd%D+Rf0Z3gtZS;kTS7q_o8VfR!~En$Ui~{Pe8wJ?a<1GpD=XhraZF5H+6BK5I$q` z&z^WMPst^B_*#y)bmC}sn_bb@%ha+v$tCI5AcU%^P#6s4oSZGYCH{iM3Kc}%&LKzf z!xOk;FwjnN%@OKJ{Q1P$SP?EvXa0lLMEB#>r>j2xsvgXU(M<&5Hz?Ch4SQPyqD zj_+c(;3woEVooH6xYz7XQz|5eSx~P-hg=RO(#k!@w*rTC52@U_)(V5dMmibg#kF%a z5m8K_LG=XFg>YKEZy+nquZZ8R+=~W!n!TiGQDiEPWsWST00D}_&BhE^7YIUX( zn;qqaRG6aKi&cKCh)E_m{{SCrw5ZkIbVADT$e@GCMoIK+m+po>)wMD*ETl%YRWby@ z1W1aU2zyqxL%CR_g(Kw5B6%HTWsMTsv3@&KQ;G{z`p-I7*HKF}kPdDO>nl`fViEE# zj*!RkZ{qeQ)s<9hYV1YqgtHLb;mb+OBF8SQ@U}aZiVew~00a;HAVzw|N2d^zbP-Y+ z?W06+k;g5V!mx-i;TV*-aFG%M;P76900HX5T1nlrYw?YB_0;NhoFKe_H_H5J{5a{$ z255bWq0y4k`3AdJ6L9wOJxxU{2q~Wa!&w~c$gliv9S^<%8TC^C0Ay~c-L;rxNIWvh zqP#Yro(|t|wwgn*Q^4!;5uY48YJS=AOYyu^{FbiT&lr+OtN#G${isHr_yaR(-s3?T z!*MTy6nsk%eN%6@TvrSN0XjPqO>3F*k4#uz^2r1)R_imowp)UUb7TEv_bLsrZKQG{~{_rDph9mx#ua``Ge=J!r zc|V+2tn0S$53{Pp6e39?*nMaADn^iwT&J7!JYJwk$Oq=wP**wk^ar-Kjnd#Yd4O`r zRPgoTfF+0_?Kr0c`fH!gxXLKJ*KHhjG~ZwSiLrs>??iR}1?RRSc>9YHk~xo#h`T|g z13ZY!3}pJUu9-E#>mQNDcCuQXN)c8A!=sJ^K1JbYc}LGTDneuTR6=uc2^0nUAg^dNqoqBApJFXM^c zCYW8jwdMvu027asH?Zf&9@q+cdmr`b>NeCyB1{++!nOPHxFE;Ol5*hk>+W2Lm&KQdvQQGn@cE zBOj^x7U}Z99>e;2?`^0=W4DRrh|g>vh~~@J9}qv|jPVbSxF6T`{{T;1ZM884fy8#W zpnQQ)LFiZJ2SNgpI(uZ}^y!h0L@hK>gN_8pA09p!`6aT-NO=idft@Obhh(rGXA{d3p0H_22xj7j>r~CSl2qj3z6lsX4#zSU`N{3YhUKLX({GT#wpRjv$Vpf%{HK{+^^OMpVIy(@aE?!?SU~JeU?;a( z{DSA&_xJYZ+3Z%;9`x00T`K`g5n{jFJC^&eZY|y1mS1@yKD&^_o~lA@U810d0+h$2 zwf8$s_V#~g+q-tsg&_3+piwSuAtaV&W&xUFGMa<@ar)K|9q?_x*xNWZdw(GFYTGS5 zI*%RJ&*i~8>x$(BxC*eaJbm?jqc*Idxv2A^~eE#6KVhOha zi6=T5W^z+JL;I#!jdRG5hg%75J(n4 zrW_SA1w}L@gEP}WK%rm*eLx#Bz`zlZxE%m2SD^qC&}Fgz0MpmffH_73+)zlG;B?I% z4;996GWiXVLBPlLC;tE+QUT$E7Zm1W&m4vJg)bMO_B7P*c=UCsd6$n@t-8Ob)n10g zdrCX4SCQ&Gia7O#z`tBir;fhWmb*H~+$|f1clA|`+i!8LxKO4>pn^4;Op}-Ejh@+n zvuaq9W_YbgjSNII6IqOL=ahKkY3)hlbnI2P7PD}*A7iR(Hnp1>Dr%OZU9V-Qmc6zG zT0tyJ+wMy$##MP2+_T<2{fGwEDFjBp8XapVOjdMI(>^9^@a0VO$A0|}sIBAw08#kV zHntl>tMa{${{YRry(G07%S**H%PoH`uR-FUUnEI?PveyJ@z^ZZV6nYvQJ1zFGlhi?)10o7VElG?Qb9COncIo2-(wwWFgBI#zJvjmFax;vKANY&_asL2ML$sa~ir!}v zV*~o(KC-()&%QD0VCFK%W8shw z>5_0Vany{D{$9F9LNVC+&m%cj{{ZbtZ~!>#k&pgIP{{WF7 zvCc_iMi~D9;ph!XKb|H=x%p#!Y&j_}#0-!~Bw!vm{+M7t)Or#$#O_h93RK~fl^-HA z(b=3Ra=9Noo@{z&2l@`ago1U!lOT|PEHFb1FOF2v)Lue!BDFjjgGq z3f_+6YfGbH)`n2@l1Q`ZbsAk}nV$cae@2$9{ZhQMqIf3;dhV9SCrkJmkYDT~HJ zKOdaop;Si_F!QRZM1&CTW`{TfH*89+$CnVXu7-1wHf#lf?8$F~<{V3EopYb~?YYtp2rj}(z)3e4PxUO7~7K0a7A zLIq5EPm0H$xaofx@T)Reqi(L}Qwv92AV@V-qgj~)q@{1qy4OttsYX8%%N|0N!2rmU z20VE2#k+E^pYx}T_~WYlUr)WOUcE_Z>Fz=ow5bK%U54$fjIcCf_mFdzIM4yxr3OeK#2ySd^t2aVN9;!#bUslr7F)IFMaAGGOy$iJ1scszW3ZC=HIiGyecig4&|3W2@8jA1^v% zt)XTVG5q7x#Akv*di;849ZG_71_{Uu)Q{`!`g%F#i|K5pL{E_Z8T_%k)Zw_rm?-Q2 z0EhMe0PK3~u?HG^vi|`2NeAcvk-z|Z06Aa*>V4aSg5i%mET}SL7)ZuQ-Z@E@DHW1P zf%(hJAG@;_d}?Svyj| z@$*%YfH=quK=(O~#%2Wh57QH&7_P_CUroodx4DbzzprZTZ@HHK#*c{Ts9U`;$ZHp7 zqei>iu;H*|larMI6Mz5`XNRRf3?TNsp{sVb$^QVPV>kMb>h`YH2Z4CRb=pHIjvZt* z$dIseBFkH|G;YLoD#IfnYR->qc`M>}~lD$>SJN#y7vZvdJIE_JS8AoMfDbwQ8T+`Jc-S9k*@)0>Vc*KQc-2 zIEQ!qK>q+4Zp#DM`g_KtHG85Gw)}=A#DFUXNViIoDuM6}7$^L3pHKoX(vk7P&tut> zC$|&k!|-bQ`d~e;@TdG=n^S*IpH@_-Q)Z=Xw!6u^){V9Pj|JNPOtz~?gyjk{@jXYY zDSF^oR)OjIV3)G&{{a60UZ3?p=EoXzf8l5N+2e*c*Z2jw3{X8}8y`0F&7kbasH}{) z{FZfoiO`TpC5ZPvgtL<+tK=|E*mfuX0PWBI=lyVIzxc28jT@%@9~#_Ilp{wRRXp3x zVWnOs$n904H^!}W3UC6T!0GBbzpDfP0LOj4KmHj10Pla-3YWja&+*#*mA5a7c??pe zhPR5pVtM^&jfP;&Gr`fJqsXekyz!FL>guI;V<^? z(Y}i;4>CykpU!3uJnj2C{{Uj&@#Dz$^}M6Oyyxsc1M*s*mX$8rmZz3$;Ev5^fntMN zAHuF_p0n_oC8My4!%Y-qx$U7`e(UVkJBt_XXKv+KFb}xQ^>2{FF|^QzVL-%Uvi56X z#IpeeO2{V!E_o%S#1#owb%i)FAR@}jxpi2=EEmk0XARdq_v_Qw5KWcyMz5ZEgHGV}6FY)Vl z=NnC(xvr((s=nCqjEHrQJ#QS-+@t3j`KEmZ2=64)!A*6IVlm0w?`ShlvQ%6Nct-X)Xe{Okqk6MbPkk;JoZ+VuB zPqovr8rbE#H1JDlZtNvkqK33>J6H5qE`87K-MDVv!~MqlT)6GjmYqQa+f+iZSAN@T zB}KbHv%9jWxnTmRD|d3cG|5&f2CQOsz#0M>p+EqF66$F+6}uD4w|f?i$g95NR&`so zu9hAspx9EAi^n_Ek~N*9Q3K7~MC6H+wcEAp!Be>H%6AGEZ~>VKij-mifWW{cxLu{8 zjAD!4OM+$>gAhJIks~T(a?202{=d*{>LoimsH{{;C6)yIP>mB!3~@;;CV1>uh2(|^ z(<%rEfPB4;{{U~Pt|^P80C|m0)@;G9Hj5cM8XwrKU6pAzfePeh30B!DjwLP`o z>FxGJ%lmt+wZ$QUw$7|SvO{&El`g6Qc9Shzp2@n6 z*SUMWyEjC067V&;}A+MRZRH~X(8eOlBJ>1BPOw#%lE z#}#i&EqW%~NgH=T@40#9m7}g=eXsSN+;_|OKIyJq@BaYDOIzk#YzzI*c96}q0wOkm z5f)rACGu>WO&;FtjKu&5HjUB%6dy?a#FbJbePQC?X4`|wT7&_7Ut_C-OXL^#x{W+TyP!otME4oDwX$g&?jxCS2b|&X< z{!^e^*E-=q05OupW=%<*)viIMjc)Gu_e!fDZD~?!1qUHPlGB+K9Vy~J<4*dQkqUI< zd34jk1se3WA$1Q7*sFJLby^qve&Pfo-MRQOhMuV3|Tj> zT(z;5*hwY}0zCi?98EY)K*f=w{{R-XzHt>=jiKMwp?_5ty3W&Q4SGP^W=E3b?$5Nh zV#=%_;#(2Q%Nt77$&;?R4{4*?UE~-N(+~gxs-PYV#8!afvRPfAh9_>Y0Q}7~B!kMh zvGiZy*M`wsb+nbX)~9=uR+nh3BiAg}Ya=%+3MnMBSa|zVtV#e-kg^bac|k~ zQ4NS4F(-&4Jf?)4>oQIOQrd0VGP4>A=TXZ%=Ui4-f5Fu7+uNJgD(m(W-r7So8rSu& zurs6;x0ZEDbZSv(LsLb5W+X^h0fL8St#5XC~7Dh&jKAL(E5cg_~(!+Yl1`8D#s z>u-JwmS|e2v(F^;TF#ygg{^+LTtls=RiRkbDVtTKl@?`?E8F{L*>84!*h2o$013)K zHENoYS%{(PRWY{;d(P}$;2VtaCcG#kND~sI#FITKW5(!5Jn_uX(}AqXC8oSqEp5wC z!&RY{)!MbK*tc==NQoW86(0m#qk4;WHUi2fK;`@Z)&>LICZ0L|pC3FUO;5&B^cAVa zJnprnnlHzNqx?}`S1D#hmT93kn(Prt6l*o5aUpJN9B74t*bs9*JWt0Vfh0&GC&%*s zaw3f;wItNh+26fN-8&NBeNMIJZC~VAklKbS=?SWq4O=YDH0=zrvvZ4(1Nn#yi6S_D zf1vp1h>VAiFXQ=Rq`tQCTSDV(?84gguKnqvTJb7gx=<@wu~iV&ui1=@lD**}VjKh1 z;GHohCPrR9c)2cYVy>(?hU0~K3lk8Kh6Y9L{_+Mrzfb5rO=@uz)KeA4@5m!#D;MPy zY*t$nODd%1HsT_y2tsC(Ea3YBFga(hq`U0b_4M%`SRglaflnTosC=i%>T9BD?%ju6 zoZx4kNtiJV&179S-Hs>D_d%tpnDK4yIUdEoY_6*5PU9<;>)yYh+l z)?@zw)4cSWroCv{@=FQczcre0rHN~4;+cC(4{zsUU)>B$5t#wxtae`A33m!7GslK< zPl3)NA4|4NRDlX!pYb1@f{HeMO@dI|q z3KeatpwyTL;U$kgbc&2}&hN=|^97DsZCSXL;)ZvluJP?8DI_yW++6l%+*y?(AzoQ! z?#Nr23hHyBou_!rE&`!X^)<&PeM0M{DHR6^YqnvO*T^c5z@MMb72S(4Sao!o#KlHG zpR+Gy{{ZO>pVVZZS_rLdK~)}7Cz+A>lY|xq8cjTDmLoLGNj%O^Q^cO6 zbswj)vFq>cpHl>!>5c?t;<(gN8;M|BksSaXK{&`G+=4QHp!CID%x8$>jU|-{;BbBV z00NKxgs)W?#t-`c0I#D7nVMsy8ci{xN5@X2=Quy0{S*w3_UZ5GoH5k&$N$j&S)-9K zGQ5EpZ2N)lj=!Ne>JPp>U@`{AoDr5cC?x*?87gs(q%jBo0JaAi$QaM*`g&2eS0f%i zSmI)*8^n?hJkKD#Ib)7^;E+1>{lD+%7!|3<05cvZ5*ASH)i7{JPNA3_;{Y7~(e00a zKw<=}98S_HI4f3BA&7n@XuC|R*P@uyl?yjVooBZ!GIJ*c#}cp~p+4woPN8v*yJC6L z7DigP%LFSOAal#(CNXY!hSuMXMK$QHY%D<%tg~0Eb4j?R{{WgQw!c0-e$u|ewPjk% z$%?dn<^Dx=E}p72@qGJNE!!$cfa2nlz=FzDo*7T9;eyuKU{2`F6*1=qe;BOer2e~> zj>};ulW6>cJx`OTZTZ zNTF)_16{yqNeAJGTPtuDM+)HNJP1Bi^~X{8huHCcd|Hi_NT9S!8x2gg8E@;1kfO}g z+IbyF^q;A<|ox4Svq0X@?4s)3gi_OleEfIYR<**<#O#jhlB;=-NmpDExd1 z#baDndu?j9+e1vb6N_8LJdz#zHZ3$4YP-j^)ZQYt#+=Z{8yKN_>Xq#zg*o?Nja9~3 z!nYzUnc86V{BV`jj~;xy8e;U&?gm4%h|Gi|K*TtRgT^-lHd>Uh$r3bkUV>v#8h6@C%x*1{A!$-&R1ubqmx7KA&|v4_aT(>{W2rPA zIDTG(jw}c9HQl68S%m{&&OaII#BFS;Dos;wMkU4CyBbM2%giKW@lHXr=}l;Eg&i^R zr_5?aKUM}^pnmD4VCVRcQR|9&*Z9e8EYEtTz2l$A=bci7y7{U@6qq|bRQg*{sE1p% zO0d|TDBfXmG>&zS%cfRf8>D-PKp(hPhmNIAq7N1WFj)h#;M#N|0V9ThjWQyj99)|J z0P*%Mm3n&Z?HAZ>FH$t@{z%$YiaV)ZakAUbQ6X5Qu;z40CV17PxynNsr$q~Su#m}(U~4~lWcBe(jt zwG`hbstP}YtfwC;RrW0`(As20vpmy(ahlpC8BD;vq!GqUdFGsnBG$w0b^x5FMEMc` zoH19oCj=JpQxmpRkGD~XIADNtSiYa@BBbet^-BWa}tCa-EBnA`fRU<+u zY6;T}wA)y)Fx;e4oG5wLW(eX0OTT$*KsFG034)=Ig_qcAcG6Z-VT!P>yE-;MBEqGZ z-?o;a<~f$;qq*vj{{S55%+{L7oi@4y#;DLI{*xL+V(Z@=$Va-qf0E9F391NTc zOOsTYA5}dqqDwT@1#@1$pJtLy97$G6)i1%f*135|%tk1ffDS^BPBvB4uW*lU04Al< z2B5N(VFOt_FgwbT?K-fShzi1ZfI|;R9(aSXtu zOFNDfZGyVo2a>KZ!=cyJBloFpenuPcu7+*N|==mTeMA1-8XTl-8i-=La|hNm#j< z8T|hMj(!F@N5((KZ}J<>DMzsJukmaAoZr=OX`8wqroO?}Yk#^uHwvBUj?{wu-ze{Y)o!zmvzl0$tlUx;6k7{@Ln+m=;;lcf5O(*!=zeRr3j7KjHafzxdZb{{V4rKlT3r`Iy@`{=z@z3`M^a?f(Grp58j6&9VDy zSY#n9l`OINHrU0@8Z=wGHus8WE z(8l(^$q%8mleP@8Qt(f*_Pv(m92m9_?_20&RV4r%c-Ps`KehA-R5 zkN)cm!eqTH=!~X!VSNR=agt`7({ZAZ8KM<+)pW0Wy@~RP1Rf=!4Ys8ZU813~@ z#C*R%zwxY(qqepTR*Ea=y5PUVUMPBv-kc$BgG|O@~7#^Z^ z@&n5u9(47BeK74;{{Y$J-c3%S*#7{TwIRMLLbq${f5%3%tBTn` z))Is5KK5&`Q6^#|G6XDu5`dfn2r74VPY=+E^%ID0z!3uVBZuKFK0NT_;y?b+c8-R~ zf9qfJ`^+~UI?&1`)%Zu#9wXs z;sK`vU*ahd7?tc+f$p}+r~nU~pTLeFj9)tc0R4gg0K=GRM;6oUfAVW{upQq1)cu9w z8iupVSsE5P-9Od!HE^^LGDYx+bM5kcxnfLg00V6NW5>r5dmB=A+<#r9`t;$xul~g! z;N4!+rT+l(FTWB-rqX@wosekr}{{RWTqu-~JQ}E9$`x4^FGp+ld%`W}1;#1Q6D-_#XZ^692 zrSA;62$IL-M`l_Nu&rSgtJ#QiSC4ASuxobVhcI)_L{uJph=K+gZq2j|r2{F@)N+Z+ zIY$E{9k=4YKz$G3wpQDHuNFI5iJVuzRnk~wDt z`ryPFSPjSl_yL9zT}rdD9)E}N#<`lwULt#eN%v?`wvu|?<%l2 z2^H0Tau1GplyXSiPJI6WEiqf~t?gxn<(k-&1)N7(9bVGRk^v!D7F&9+#Apzy#}_5R z!EE&uo0u8!2mb)Y0Mic+0*K3>E3RPedS;X zQcLyE2O^$44_sCuT6(1M%k%vI09Xl57O;D?wjts+mDmhDfJ|Vj^Smc3WOa zJaOYB@@%u?7|sU*bpVeZSONfP$29q1lWpD?^^Lj!j(?M`J6NIMtflj96(V$5Fr}0b zzo_{EWe(6F$IB4P8fK&O@$kh3x6$dMX-s}KO1}(}gU9u(z&jP>$zqa_t;CFA^-=&Q z)ueWBslI2&jWDxpNQEB(_FPs)rZ}`xF{iNj&U4TKl1Vu$`VbC(-2R@6aPi>cHw|;f1sP6wvFq=|hAsUD zeeu)SpIjm_Gl-J({e-ViV5cYgki)9v9DiR%1`S9#;tTf&RxCf4VhxN}-*ZsXumOF7d83sw$LrUp>V3Vv@%mtT;0_}aR_x#r%n^(pYNTNF>CiFh{XIs(Om!k7 z5uU@7%ZNYJbSJJzCnOSbI)C;(cF4~iG3AXN@~0{Q>yg)q1Rr(k2t2xvZoasIIpBk5 z0>6)@6}3@P{`ckMUlj^*Yh&?05930GIv^ zn7aVeSw2D`yd7$=Tco>=CMu;i+M&?Lo7gEKkQ z6T>VtU%ZNv&bG?X1N{=U8f3KwW=M)g7JjYq9j>dyH9K_PU8CT)tjnOPe4Aqitu?YW zibuvSX{Z=<7W_kFrATUApee%FAVRXpOx8QH#oW1R-r2#7sUk9^X$G}|DFeA>-LxnS z1j>Tb6*6RlxRMOgL}W2&)U8^TXev~#PK_$aW2r}9N~XJCQYL=mB~5<57%EhbNe?Cw zqVi+OxiKWIiE}4E6vPz-0>`UeN&S6)3`#PS)i{n`+kM8bWEHc*r{V2u%YCBAFcAEQa#Jqdb;GiXjY|f zVMYG{D`N~$&uZ++b?MuJ6@dy@{6#W+mT6uFYVIv;IENx8MJtv^h?>BOfhGY3)^0t6 zy4hJKv#*MYC&&X)&i6mZP1fVd{J7rpEzNBw;p!#vowV}{a&7i@s+yK;Y<2Z4R))3x z3^WwjVG>0olf;#kA|>Fl+6z6k#CxQ=YNDBCHG?^D0(e1V_k@7H$(S<-FDB1FB6#VJ zmEQQpJ+Fjsyc1C8N2=R+ozDoV_=y1;eGboBd~U;6jnyHRYFZ03@dba9Q|-yU0b3*qu<ZJOt~U!H`zSa*`5b{is+Bll{RkaBMRFgX^7&#ltm9(XC2^1ZI*>o8IV7L= z9*#zM&v6p`RSQeDMuIjf}(f<`t%AiPyYa4r>>en5PW$4cT?i3^X@N;t3!`n8#HlDH&qJ++=^JsoNrd zJWOd_-Z%^}z~J#cxc$I6R?jc!04mx2I`tp*^pd8v%g6BHfXL-f9-q&S4^AAd<;XgfF%B6#3GJ)JepArTKzY+TTdQ0;O)S5R}Fn@(TFvSEI%=~=R{5kOW#KDo7$tb!>`Gz-Af$Y6D(dI*{70{*7r6fbNZL4mFV_}UxfOUJwF^0qC!97& zX5v}FHukqfhAaVvByqv&xbv0!pYmm|T@W#34n_uJhPsi{ zKPN-T5>L1E{eMqZ*3eqapD!Ge_{qf482vc`l0x$6faLTYPwRu% zAJ7j+2T@#HR0>H1#|fbE@YDf~zPo9`a1)Q7x(+)U zS&8U9zxn#;IP7EVpyRQni6{L(PhAHcj0wOe{{Rp5_0VzH(n%*L*Fnc)M5K|$Pxa6L z01u9T*VGj{W3d&1Uw)s{+y4Ns=yn`Pt~8(OeGmj-@u`ka{{SyVj>d*L>G}`;K8tNR z;xVDm=s)Y~AmgzTR?ozH4|Dxb^#1^1>!9Gk*AX1#75zU^`Vo=;0AF$HAnT4GFoG$8 z)6=gvF6>C_@;|UeC+#Dok+{G;eSkmsdWI93{4rg-dv#) zWC#t#Bb3Y{XJrc_A*Nd;au3g{bUDXFp5EJ3xy3e-D!sD?9LdWm8*)CA<|#(`FN8L_ z#G5^4zE~}WIoMK$N*gXH(J~W$y4SG7x}d9PBdEq5y;8f|I0h1x%hU1r&Lm!e4Izi7 zem_icr`mooFO;RdWR)oSeq~8#*Wc9ZyvBGUc8bK(K}%<<-s}GWEV}~M7BMyYwDEEsU{_)fSY+rGAu#F^(c*TKi0o`3W|(^sxNYrjDKVQ*|ireDGOJ55r|5!H|^ z@33tLgLs|K!$oyJmmJKo2N|Cj4I=*la_tXAKFpX2+)v@468eO!ESM zF+P}CwQgVzpBhh(EOYg*qJE>?w9t6|(`T=-8oZ0!`vXt->YP=(QU3sZzqu_s6)Xd^ zW=?$-xQ|u`d)x?;r_6sZE`0Dl)wvBM4>|Gqa>akY{{R`bIs-h`Pnj)BrDU>Y(^9_^ zN8FA{1zpCPC)7zPi>k=3%!RTL5F681x_2Q>y3UhcTKqqb2V#X{Zd0C|{0}PPvGRRQ zs(YHgKdwl;lYK%P^e3;`O8T!G*-0kCNGRH!_E+O=rmn+&CD~rvsdSg9q;D_+L#2`tqU-HsTy7WLFD{{ZFbyz|k9SuWdp z>XtteHi=`w;zzg!UVbb5HN~BK>t5BUcGn=UvRcnj#J9{*p5vqz zpb@~1kloltU6wgo+esR{YRs(eJwv3PmLQQ>r8(+7A&(FN!HP_%K0Y5B4g|(5IJ{== z+G?vC$hGZ=-(sAoYpT+!Y_xTB#ySj%UlPc(%{(g2BuaSlz>Yv$Hta!5U^m3hrcFtT z4tz5NUgg0I#E&|804bhkJ#q%m)>Jdq)x;1ur2(rL7^-80sws{%fFi{dMOYvSlu)5~ zw>D;~nCj2t^Vjn{G62NM5l$W|JhaldleE>-k!#rQNcSOG@WrDtJcSV%*dX90nb81M zAd;#*x_T<&HxWQ-K0F*$8|iG@fSVD|Wg+i8$R5+j2!b-|2XPsMm||60LL4z9@+zc& z6C~nt2ZUpp{GOuQX1P6;uS(={Nh`%-n^}5OODFzT(>>dXYHLPcmk#MD!T2C!kuY?p zj~~MWN`Z>cVz%XtPNtcuN?AHsk|eEup|6FG+%P$oZuO~0CFZimt~sn|;YnGzB*5th z`By0T&YIzXMxss}yf4M#qhw8NY+6W5J6pL2CbMT9`5I~QEUeKig`POTL}Bx2$0O(! ziH*c;IrH_`&_Kr+$YRymc?3GBE77E3n$47p@ije}dCwh&Klz63sEKQ*?qkT7>w}O< za~+@s5ySa@U(XPD#ue^d+u#1;mKTb~KPU2?Uc3${HNGKX@>8aos} z9HFk=VpS0E%077yNp52V6aX2GW5+Z2dE=aSKWRX96346BTeZ4|3vDMb<#BwJjf*GcGqJurc0u?nUb5D@-;(uHjX`rI~^6|?Y$My%fcwTlFL4)>%S)2sk0Y~Rgr1D z+Zs?ocV`^)!LRI=itLG-++DeGg`Ud6Hjsd=8-Zw@$^;9BNIRua3YG_Io5+f)*Kfb~ z91>I*A%CQk^oShTm;f4)%pOzs#>;n3EfBG>*xIro)>HBC3yW>zx}H~Ml_SKzMOOcsunf~-K@w2iP^4R&i?z9Pi%k1bK{U2Q#|#ICff2x*%AHISE0Mr z!FaVTuAK{HD+#ri=_I9ta(+Vv!0FUt{lB*hApZc3+}>0F0ABG;I@P0;Y4fhvw;8!@ zO!9;BBjdWDbXRFpl2U1UkW#E9Py|a@XH$ofTh1_IR3pdGsoaE-xK{Y(E3qk8EKA? zMNT%X#Hi)PbJqZJZ;8(yBph{P{^Q@!PT>)P=cWr+sWiePES0E3C_SdVI;7Je&;5;b zMs1~~X2%v-xHss--_>iERd;L!*k%-Dd9j38=F&J~c9yKinfwJw_(l!Wuj3Gq+bx#% z*BJtG)9T@`2nXlHNwk`~F)`(aLXt=vaB?eLx%Vdj0Qi31%EJv& zy#W01@298Wwn}~mLjh?u z6C$$)XIxz~`S#Pr>-eU|T2k23Ph}&aZ^!m9>^6QUw{2vW>&Zi8;_WXe@lPnJA9gw! zWw8ZWqG=XNhE&tE%iJZ?x^Avwps)s@gH?6nYgzr(t8p61Kt|LvS`Y~uXv%+q$ednK zec4}Wt6jF6Mg8u5Rk)A$&DN__@5>-E)|{;I!r|ild1Z=0VH^aN(bSv<`K}5Q2tKVNf(FuNC^>TCJgPD= zguS{G6r9hA7$M{kYW%*eQ<}l~Kw29q8@gJ8Kv%g@%$W_{LdelO;eh~qq2`AqgQqC* z;1Qk#Q|ikc!?^L=-amTN)~o%_w20AJsbb1VlWXNH^L{mtmKD_lH+Cmz_$VBG%9<2_ z(km6poc{nLhG{zZ@voL5>UGdxd8XD}wFT*#8&)HcqK>q?3MwLj+8HKwTUDc%EjbcL z{{ZBI`XDBOy1aP)Gl;0xk>ls$xVC&+xx2B7Tgxk4l14Tf#NUxAn}==M{m5QQZ0;gK z8+OYTAvrS>$%?h=8u*UQI`Omz053KpQR9>F8Ma9);Vy(O9Dne4V;iMSxG zCvKuQZY=TbWs0h#oqJ#ge0Vxn1H1O=i$lizPEAZ!?S4EKqmskNS}RuUG$H{S#acE= zp4u#QBMQsf<0oqgk`YN0X4^LATA!bu7^Rg=A09F|U)=c37;4+v(XJ`(qMg#Es%CYL zXskNRGwtiCb=ozF9be%Yot4Q&k0B)M_+7DhNJPivEv?PYs!G+^+`NsnoEs?QK?{q$vry&$IsL zVeYIN+U&(v10`245`yubUW(6ZiVeiJ6dnY`{{ZPKBg{z1fsVU*nvZjLm>-X??g%s_dsN$K*iw9jcaOv_;M; zAku2ug?}Hqx@0wyOtq~Vdvf#Mhh%eTTw<&DA>l#goI6H zefFIh*_0_&l(8P!BOZtU0Fh8BXrDTK;Qj*~J90=8zLosM`EteWMHK!3{l!_(d1N@%wm^Y01exJ*h z7>i&H>1}DL>fy$D)5{*7{eSgbJ{ej)Uy69wgZMV2D#X-$Crjd9Ek-qtJ_Z3p#=II6 zh+=LgMY6rpTjB->qW=INx_zbC5;)iLKaQss1<^n*s@F*o=%de!>%$kFJuHx(-G*zd z=a7)osfddNB(29aZm{@{M8H>8}Mh>i2NhJl9#zFqM&V5;1Vt|93)=%P3q4;6R+pcdX%mMgM>7338rFy}M z72;MsLj+z~{{V0YTn|U>TatE(!;PCq{{XWe0fG>f9}uB%4;%mnKcF1|^uPefVs?Ni z02q@L%IxushlrqX!x-iXTy;5C3UGaoqJjdQ4i>i++UhLsLIDIuaz~gTNIK$DwmKg| zMz}m~R>A)OPuKqdujny2-lT#E_1KI`Rtz#f>OZHcz!4bUjrt$@fAzgiI~xHY=cvi+`ua&lnh(bn8q>_c3(CKk zpNQqnQ*EkAjwPuLl>UpT)Y)a?r0`a~uSG}H2DYBe`aiirl*T)^|EA_Hhl9zMft}4#O*^NI`*a=1h{CKQwF+BWJ#eQM5g$)$p{PD~FWA%61(WIZ=E>@`t???qdA>a86$@tx| zVoKGexnEx^#?$&L1aX6C|L-0VLZ z(aS|5oh6nvlH6NQAhfFs6?_=`k>faQXVIZag0OsW6!p&nz=PxS^faGb@yEVfwb&V+ zD;w?9QxSl&Z(c^g;(=LcUz{P6hrVz~!OJls<)j22k?{RSSXr2+UOZAgF;iZHd$1Ed zd)Dqk4;dO$G?9i?_(tDMZ8 z{W>)W0FA#dhmTw?qhHU(aI0#RYR$%IzoHS{la@#z5ySOtob^31>9IMA;xjTlS3Cl- z0)oIy6sYQ$utpg_LRYGC!5s&`sSD2n!h9do0%l}ii%u{OND)YL`U8*w!2bYY(2k$b z!V!)LbYC7`Jn%YMBRrHjgC3nTi2(#3^y}08{WK}396$tq-yS~7UgO^$ z_&su1k&o2+n5Wf-LBw`o0Rf5qdUXf%{{ZNKANBY3AY^eA218?CLO*so`(yR~r2Aw4 z0FR=KM(I59iC_Ts_QrGijCCC_bL*p?BX=xjpD*?Rbu2Im>T}R^9;2R^>E(?r$0y{Y zufH{7K^YweI^g>bzKEFD9lI=k<}e2^ej_->Gu2AufC*pzpY-Zuw{pfTR4**!^amXe zzH@?l`kIV(?QyKKoZ$NmoPwcq{Yl1i>H!*@K{Jg`0Re&c$Q?SKzo7pBe_TL5SQ(_? z5-^8Mit4D1v$Pyv!Wt1+ikQY#ZpDp{Mz_X;Cq+<8UX^(>)T1>G-(>-D|^uAOB@c&DR2 z*T*(?Ye0SOezAcg6^wb4bDSVTJ-T zaR3ESc>%|X1yr_Ei5VW=hh*n~0Y;Vc{#e-*@9!gjOc2;^g9AA0jDCO*>(kT~84R&7 z1nrP~c$`Q!LFx$|4-!jtzyNd{zft;*pQr2UHfi(0ZgaM=^Za~9G#O<$IV5Kz3_(Ak z1xt>cfARLmUtPA8@!;d7fIUYpILnvI-NE?ehGqqcI3Cz7$Ph8p`s37X5-X0}MRw1x zf$+v%;-nH7h6ne2h6h4ORLYO{>Fc)0pv3PHRm6pNZlDl7x&zc6m?z=^$Mw%o>*@k) zIbgR)f+>j!E(Q*Bp1B9#+xK6sKJ^vEQhoe%o-AJaaE zXA_BxNLR=7>;8j3>-wCWe_v7o69!L~7FQe6QmQ((az`GjKT~YXGc#ndA=}#|vW{5*duJJ93CCWa>`$N|oXt)LY0%?hI-aFmh3kOJ`$_547S2z< zPem{>5yujuWGp~ak&cY&K_HS;0x^O5{+)er1NFqf*Gx`O00W?4I;myI2OU?7Hb5T# z0QC3uD*(azuv%qIO^oz8Ru}{@x~S~CNN3~g7iEI=n2L{aT&^< zMDtKOUycM*xbgk=N(cfO>m%$R6I3M-yBg z(O(`pV6|(@8)f8>k%T;1Fg^jx0zo`5-|2(XBhb=oPaZ#pC-B21J)}%>)8KOG9Tf6h za{mCh42L9pWMKZICmw{%7&O5oNb1A3wzX@8JD|F`Q4&Ffb`HakGBjX+ClTxd=N{(+ z(rN+WivqdiJb2-Uy;iSIEU1rDXp{v+lis%_N!^!-Bx^PrxnmsEfD6A<*B}GwOitO{ zBk=fx`Nk%S1t@%ee7<8B7mln|tj8MY^c4?qIJdQ{S|^NrRa#WuO)-@ZC2l>*XKdw3 z4F0RJ-IE_4U#G(iOo7|4gIuHeFy&11>5 z-ZHwl(%5s#TH?gqi2R06iSiuughk3WYzC5hYJEEgOj>F?*nk^18# z&AMS(BhGnWIvU0euK3LTvC9Q^1(<**!9Ub&G{`*Q0dZ%61zM6*%tKA~RH2H+Fl^56 zxgz|BIs^YMgMf!&C(5AAj}nR_$8K{i@L$e`2qUq!B`9gq3g6L9nB#l?!g5mo=%T=S zJrrkE18Tg^#GRT{E`__VlwOcDJgrG(*C@TELS2 z2ihq-I{xNFp86O7_Kv|2=l$7>y!I3pmkIg6SOOASV*j9aV&x$#jdJ7Nx+QvdfvgkmqYNGhq%*ShrxdZkZlxu5=M!-L@$VS`TM-Rg|LzpU%^iwaMU zp$xX|)gen457+vd#pcfR=5q?5(ZJ&5e%s{>(Jn;}*5wQv_xKB*6e!8aa3#=)Iw|^7 z=fH-~&d!~(2JcCs`L8C04B*ustC1@$&mrxjZ;Eg0{_4G@vZJ-5Pey!;J%YC=-R+YE z{w0RlMK9?PD;s)eUP-YN^|)6Tl$r8aTGY;Sy`kXsuFMCCd8ZGrUp-_t-^lE_ zh;wB-K|jBV!$Q;>>efJ;XOZFlM(2n)!yqEXtcb;>ZHN2sR_i*I?D#wO&qRV?E2*@EAz?CN1x}B!6=uE0<_DYa&C2WM2Ax!dK9J1f|Ed8&E2%0iMV0LW2wEj51EjAJLQuz? zCQ9v>w22^4QSuhl2*hFvJ;cVbmi#gygpwz}VBOMiLgldw$kFZE*eP{|Dnq0M=0yz&W$MjKY zW>-#mnbWegnssJO;Wi;bWr}eZG~3rX?5IyS9rqck%qlr^RJ^b!a=SqQc6P7yqlgat z3iEx*(nLQ67RxnC|G8OJ)QliuQ9tV?M<@-K#o4N|p;Qw7+#U5dZWjs7@V1Ko?yBv5 zBmLFlTY-hRhuyRzo^fo&8v*_p@0SlnV2 z(pZQS?6qYhmb|mU}g=7b`R$u4w1HJ;!_MjqEEcAPX2eSUjBOn(F|BLm^|&kdfG`1rv9cd+{;k)YcwQ?& zD3iAV0!>W2RkOA6hMCV64o9863+m-pPp&+p;Y4u7C+)O$xr@Ab!PI4#yWziLn3*C9WMXYjfb6W`S^_327>;Ir2`T-8ZtT_J-L2H2)bEd#6^6(6F(~TXb{&`eO64t552!rzy(7;_@sRCTe)(Qw2}!W z{ujP7O|LEzN`B_Sn>Pdbej<9Vv9hSm?;-rVq&?HHpn1;2u2S*(O|npYwUJp%aNYZ| z_v@g%yE*%2@iS-No~0hMecG$7xN}#M?79NG0!k9QH~(QvY;ONgV~)ik>?{+xC2N8k z2jqcK!&V%w0Rv6v#BCnUpJ!&yv|{>wC%ZLjEq?3#?271a8GP^S=zuv)?~zvw>Qi^F z5BVKKWW)ZZM@F6-a!kJ-O2->ZAf71dW?lZBUWNpAR}p#vmYx(EQF1?F?sWBBqC7sL zGoUvM3p;Pg5iIrn2MXf+dm8EJ`Ehc&Khx%G=~(>yeT6p^VfH|N;IjO!#&0h-?`lZL zH&6>?)PQ41soASU=Yl_}+26nVut_KV^C{B(#g~q{S{Jb%FPq2;#C zJPKGE5j|Cam~`KuE}O@$^tBcPRZESgw|NIs=tixRBy|ScsCq>5YOtlQu57vJ*&*Te zRL2S7O4D+qb##hECIJ%zXcmxDix01I&wX8v%`+U7KnNd}-Fx6W-vvxm%3oT&8vYf^ z_sv4ApZ!g)FS%Vt3l(?o?7O~{^w%^Ga|^_|?z%L(eESXj0n*=mSx#Ccc=JJr_}Xe6 ztYPs*_JwuZ{xer(?o@PH+|(1Us?H;bMg9 zYob*5f6EZnLv9hr47j9Qm+LTO~&N%hcAjHpaplQN31|GwN9F-O{h9dIn{76lz6ilF1o z=LTpF1^nC2z@fn`MVdwgXZW+okL0JjyGcyF`n)|8v7>hs<Lon$K76rZw{yDyW1ih;QP~P5d ziD}!!Efn=C6+71P`PK2wzIS~3>OMJKBSl-wPXGpiYeLw;97U-PmvO27>JFy!D){o` z4Vm-+7=j^@`I}j}kqD-C6}4P@7O8@J&40(Sszc97_j9xHS2ju$1XR06FN(Wq)QZhZ zl>3Dx+_RxciQ~g*@ z{$+rc8N5E;{KoO1E_?B4djCr5~!4D3zbvDk|KXZwO)4J{%hxgiG|m;3e?ks zPF`fyjiYEOXoY+gG{Tyx`8Tkk$z^%e%r7>PPB$6*FtlV&ytqJ%Em$I-itW`b z@;TOj8}STT*fBdk%hQKj(LYeYK3COG^x3y{1w@wz;>pEAkt9Wi+j{6*Ep3JmU^HUk zO)xyRX!dHxy0P|s_w}c=h&cfv4Yw5OBxa61Az?SchK4!fLOX1t3y}iYu;;C+emsFw z4%6^e@Rer^V`Ukal`?2}43Ungl}^Qr+@C*Y#lK(p+|qzwVy6GNe#b}uB8a`x&dSfg z`oPq)`u3WHh3_{lM;2i=KYOhzWvG`bjDx(`V<0!~k(3br|-XkGV zQ@On+gxi>6H(&kv;(g}bYec_B4xhV64VY(@&O^SXEAzmGC?yBZ9b10ujC7yY(;182 zj3?w3;6N1q9sF#j`5as@s-1Jb{EAk-S}x@euexp-vNxIB9pq}-L09;QDX%{D$~`Xk zl{pqY##%G|$ErN4)2W#8ALum%KScWu>->>OKH7?f?o)un2N!ydU^3=9M0^NMVyo0)2=R@gJHAiBj>$Et@iDYDNf z6&#BB15mmS)lc~|wdX%6!d9hw(v?w1FU!U|Tt1=Vr&w1JqshB2zp!vY#;WXd*1x^Lg{lhO4?i7@Y+D?J2T^f4oF#nf zT29#u`jxPN_EkW8be2D?eAEO9E-hdrm z7UZ6m*3eDo5~bYbXF(;ey_CNhv3irlb%#qwJ@jM+rklip=WF#JdK#jk3+TQmD-?2T zg7fTiS(c8~$A>|WLkzK_QSj=9gL=`m^}mujIQfcZCB&mqb?a9po>fE=ReuGumP(>+ z;zegpZ>F!U_fmSAB3_}!yg_g4sk-kXi=*1xpJ8-Bs#j2eFzQ?r zZm(uWCZuI-Ap8L0d8ecL>tdP;y7jE#e!96JeqFpTK~_;hLN8i>wq%eLA^ze-g}X|7 zyr*#H%zv;&|NVTjb3zT+ltNN&kl6W_`E$K_Yk}AzdVSJQ5mO$@Oy;nwvms&rOdXBf zx#u)$@cb=g*vj{(4VnDTAYSp$R>lhmu95WhoZXCA{|lT_%5!}!63p+?&paVH3K3o; zjka)ey*oz!1Nj*@Twz-si^aXl% zw~<(%M~H-l^M>X~shj}w-;uz`$KX8|77PEUq(50G zR7g*67sdS9aiJE+dXc#n%G}7%J=X15r5Y__qO{{L84g4VC1}B^8sIS(&z>Gzo>KTY z$bzC|uM{-}I%#_ZmDiP2C5sS6V6AVS8k7G({P#4R^NLsK zMGMX(lTrMcRB8HMvi`6tA343ZXU6L=YO_h^?w4qEovi!l@w11or_5Ls5N@d?FI!^fE=~qbvWBpYGQ|>v%QchO^dr zGW*u*#&4ewz`V7>OvM(9gK0h@vvV7fn`BG{ESBsnDk1|@)Zg-jM=0w)A#D7|kMvRI zKL0>3A|f`jOS1AAez$ShSI0Y~%1v;z6qk$W2evT)8Q6T|v_+X!kh_f* zN>X_2m*Pw2E9vLV3R6_ZhHZrxy+&FR4;YFaD6 zLcF0FK(0trmQ)gVfXC^p`jirRLdm(&>Da~{Wft(MQ_Ncc zdGx7a4Hs*2oz0wt6e!B-h57rIzYLTi{aG<`a`UMM#0vu6Vqs2Xage|D0s6$zg;Xm* z3Qd4S07&9yne$}#rl3rEftC`IKse^8qZoEckZF7x8N+T)+!_d!<1@FEWZu+hswzb) z=tDbXuYU?d>eyILiJ&MLppDuZ*nNqc!eV5UE5_(@FE=@vAEm1R5HA53SZ(IBJy>V+ zdwfTNIy_@I2h;Kzv{|bjdL!U!zTM?@$p-_2 ze5|%}fzMS^gUBRj8+ZC;fLjo?ZEzqbVx`(F>bn;ht?B0n+KE!8*^2n+N2m964fY`a zc*~Eb-FTtMI~8&PW(21?xq}=sQde-vu#E*$TrAnO;zI*;KRfZp^PdgBe=M9$f8Cjw zX+sAYJgSh}E=#!Z`EY8w&S}2GR|%)rY%nl*vT5oK|H3gBNE(`Q(qG)~b9#IkfY0YK<_Tp*DXsXOQIGP#nWZZg`8!tKmDvf z?k6g=VbcWf7o(J7qbjepPlm7cBc{f#6#*H9F*m;!Kwf4C4|h-~32OZVrPPR{1S1_A zOn@P42}Le2+(1PWPv$Pd`DNmJCvP)CVKUHoj&siai$WW9%|eWfzEa4?81KjrDY&Ao z!Pvs^!s(~i5!%EXsJeS4?JSO1p?rs9MU}NMi1Rg|xp2Dgq}$!!v6trnKH4E0`86P} zK(_Va;u6PlM1S80fZA;yI)#Ua2M#2a$&eDG(cEjQjE>z;8iZmlAn0SIqJ3W8ZWLu8 z!Ams9ZN6R@GYgEk4iR{}B*)viF6;;aMf{x35EH^-F?>(Y@Hh=c zrsHTLwqw;z6ZxItA80skL1xM(Xg9q>C_m_Uz^{@85dXwTkI{3brydEDARghd!z(NN zBB}26zyg_8Cg}`U@JQk%*5}jWLcDd1KixpRm?27*F*MknezrOL2ZzOSvbv+Pbe+7z zQOX?G0X9|9iujpozHa??j*@$;ySSIBjnGVGA}qT97FVo!@4nWodsdJW=~%fe_visf z|M5+Ec`4*mG12OkLRh=8T&T`@q=hK+rvMZ1oGc*$NtXoQKu5s5a(uE~eu6$?VS1ls zDrr?Rnb$=Nop5_ss@nB4izAR9t3Nvez&TnuQ6S!))EXK21 ztBlIlHFISTkNgZt{+7XHsMUy){Q-XCf>HP-bJ4J_Nr3v6b=aHLygvh|xIFNperNe| zUeYnmknB)b@x?9^b#C8pp$SX)=_Zyt-1i+){FZe%(rud0uEd73L=^sk1ZOs+7w#mc zAjdS|g#G5sEE^;uZbL2&f7_MWMLhP5Yo%^?Y{D@XH-AmiWoJW3VE{`%-fvm}(TqI8 ztDvg!oaFUwjl?>*AI6P&vBD~aqrt|yhb3#-avK_%0=dLP%nN?+?*Eo`v`_O;Al<;g zAHtDGH|_x4&E96~@yHN1J~aQy2$a6H?JPOl+3os-7J5I2V~Z50`){8q*U}zVf!}`Hbw@ z7?trWow1(2)$(oFIP!RWrP=>>{F|@qkb1w@){SG|GQ3X6ke$)Uqw9ab{G?91%PeM6e?>H>CwF6|M_-I)FxIg2)q?A zF4n!f@R>xW?)!TNBhPSiaeQ5;gUG1o@XsMX62_YM#gGHePvXO3e25CfmExTTWH;d2 zz&0U8UWJJW<*SdHjIEq`!nU0OZ=PoAT%{h)J$neIo)<8*P~!rnGHG&bl1`5)$B_Q= zw@f^mS&0-+c`@8OVUA(1!(3@jd0``F69?a zFDdWfKaT{WI=WVS8{|p<<0X%@(IvZae=Seztf_~vLj^2jipk z$!@jm08scXQC8kmQOufJ2OVSQ#I8*NCfAv+!Fsc$ksIn*S1zKs|w5HaAa zOUd{s=hu_tV3w+7fVXrME5cDZ1zS{)e1omX#jg!Eh}nH1E8-}~b?C%ON`!~5b}R~< z-FeYD9s@I>Dfq(V-Z#{MH)Mmxs(^q}e>hX>;L0nGhF9$FcjI#kuc4$!hZ_5|fk44L z4cDxg-8}+BWduFJQMY%*L*4=f?R?2lLQole#HiL>LW@H9P_t~VU~NPynyLyYLcBWF zT1}(bEda0Y+*SfHNj$E%=eI(5r~^;ON{TS;dvQUhSRP_oBZ#=^t>sFVjoEA6~k*ZQOZ*997*Nsxee+Z+XP}h|17EQ*ZZrO=M2%_i!tTHo2%FVUY#j zYaNZ7T5;bg?@JwPSSYamAz$-S)|HC0o!=-dUA+U09oeYouH*CW)+JbuNFgP?n7Z#??Q~$EYu@v%oG$m)5Lo@zQ=cx;u;>#y{b#Wyf@Lyh(1Fs6t#~n|) z9;hdF1%{{YFv}Vl2qy}M)}JJvM}}vnX8={>*by#4vHJV!EL95anr@+p~74u3}y`IN;N zl7{$dr_#Zeb}}_wW!BM!V~Fxc?x>M`lD_-dY`)Koyu`duy}qx;i-v)YC$QpKN$bsM zu(CxM;gnXbSQb}Y3V1ISA!kV5*FNS zfvS`FOhircj$)d0GOcs3L+W0m`a===U+(>-A0?lB2V3*gTqUEWxcyWq%K*!CbW(!3 zy__~K5+@eyK?wtQ1w@{>ZSW822>T(HFFu(NG(*g!^HQ~Jvs>0yA%?@Xi9t=QQ{<6L zSTk7aM~{~Zj+cv6T)mZA>%qV;WFWy3v{JU znueLO7Vk()U7tf41+_4pDXX_CuFz(;fJN%NHt_`er{NG-UCGsYoYTA;t zVeLcZx%`ety_+qR6OUqu6iTf20>aV>*nR3Ox2(NSoS`!YXT#LncNH23gnnuM;Xj*4 z(AfCsTt;W0leij8llTV>?xc>eRl-hTjIghTQRW%@ouh>So=;M|KpESS=Fh&~>tvuI z`{c=VL1Oxr&VSZlKR=7HQZ@GY4VX1_5H07BrmS27(*)SdYnscaM{F;l?(`ZC*A|Jw z`O2g9(1_^W*(1B~0Dz34cYGb@{8`=n5h{PzhQ_^T*yt1Bkfj*$UfJe&T{52rus9(m zi@{A1WvuxBo4GMaUpejpp+2f?iRl~cQ(pUpT^=8At|_m+D4J}~&kwsV902=y3%H;9 z-La*m&po|KD2v>DHV}@86uN#p+W(Y?2Dq=b3aEy-WQjX&!1<{fmGb)Su;Zz2{paE4 zmD{L~ipMUWN2xXiD;EK^fOTZ)}Gi3>iZh?n&^n-B$`tK4c$m&cnKz`%ml zujasSwC&fh{O#z?Rk~oAYsvZ7KiOI>mK1b=U!rARt$f%UFLf8td6z~h`ajuB8`gbw zP6oKVE!?q#mOhV{UlnX_;w2EF1J#+__22n8(BT&pV$R6UxJMF@i#gv=_48aZ(2WgU zz~lRgMSuV%M=HayGbST?RF-)ut$6T(p@xZ?6I9z!tTDf!G-l-3Ql8E1dp;X#B3D>% zie7uc@_-aN^kMw11KA2f6bSS(50XhAU2v;8xUIKXd+&w$_u#Dj4EaYW*+sY@GSeXN z#!)U)tErU`?P^PUyxjhmn_vg7=O#6s-^ez?SEUdQx%xX0zA#MZ;%WD=&2I;Wd2o+q z-68cYFVp^rSCjB(&3AeVy?)PJ&0$}R_dk|QOpXwcs2fjKF*~cOW?i;HM(R4G0z6nF z1J4h7BeV*BKUT42U-_CSar&;q>JB9^v2doV5uwV|oERBWAqDq(x7>VRqK#-64i!I` z2WTBJckX(sK!!vfihf&~Ffx4t?&cw@96nt7{JHtBiS%Yr#-L9G;3UCrWXxlHf~VT+HmdeArAAt-sGh*`oU@ztwQU-LPioQor|M$F*P5C^sn{MG5h3<* z*IJqc=ff-iE%(@{Ja_+!sxJ8-ip_11mX}`d7c43kxJ&tU*FH|rz1iP)tktED_tg^W zd895nP>E`an7EXnjP3K{>QOlLk=<1r$`!?{{f>bMlGBx^iAVNtTxdtZmkT~srJC;g zR6Gf?1`C^MYa~rPCgH+73wuUaRa5ErR#TJ7ZImfC>e%zuQea$Z(MNAsRI;~xx};{00}?SVbhZ|o z>;qMsuj0U8K78&l$uLq`5^jsIGTX?kE2E(ejblm^R53t^h=A02yzc;?vWL=l{CGQ_9% zsRtp)2G&k4W*pQti<&XKae{QyT73SqQU_52$RY+!*#ynColwa7#rrB|)tcKJ(&zai zudK@T?B!l8t_||!ti0*eZ}n^J+vC{;3%g_p%cE{<8;vs__pVJeZ{SdHmKhqR7Qk}hzvVElcXQ0hY3tvhAFUm*;>{IGkL~|n;El0VcY(y#2h5W=l-xsk60dl ztvL>X3kUq15tDas1M1`Oz7|<(Td0isdy))ilO|R7y7f%PI}XaeiQfmxalF zZLzagXfzX6J|g~TDn8VB(s!rS^s%>jn6#ITMR{6jtr!FPx42HQ#SdxELy-#JxFiZA z$-$f(@z<$T1_B53Oe`)c$NWbL^j+EWj zvmnl~-?Zmc)Vw>vV=cP)(pe)oBcsz-0-Rq=?c)*#dqZpnvY)L)xSrinDm-WZLwozyM- zzSGCVtW3eHAL;%Q%k=82?+(tKXSF9%rlt7;TT8(!IB-q{tHL~|QWCacV~}s-{?O@X zq@xgaLHz+cbthYykofFub*OW)UDg-MkS=@IKem@#xQA#zEB_ggFUO(4i97UZ=_C^p zFJzbTI^VGISoYp1$;qrtcp$k{s!!*pnIQA4%}H?0$x@42uNZ}M&wDexL~k{oD`R7>Cf2bx7(+y94G@aSts*)LpQ2g|eNG|e0z z5o-b)-D&_yC(S7tEVsQ#U7EV3R6(Zv-p?%4WYe)Rh?uD6>^Voiej5x98=#VKY9l+Tom?5)7xau>D!ArwnSvg%j6D^72J zB#?6}aY!By26fci+sl2cG#lz%)*U0x@vuse$-B(_`+&9aY@ab-Tj(xKFUb4pvBs+Y zIx&X{t}h=UPzNq8mJ7hET!eer-#z@e{?z3KFm14c7Y{*iaCM?wzw_SNhb3n6gIyZP zC}ah^FCLDyS*7`9j8CBu7^*Bwc2hLN`Nouz#^J}!3~%S=v6dj(;K7yJ+qXuK6;O&zwtMh|wN0s3!8DoH^j^rxcTb0w>Xw z&5F|1E)A4Pv1C)WJ{vT}g6;t*remwJxGW67WSF!YWmmw6x$$n=5<;2aNNBizll zVd2>z#8Nd=Oxb580W6k^nwkdva`U--&L2%}em@-om1N{u+LWr^hj znC>jj70n)`1iVQJvPLDxpdrau@jpEL*|3qas2)DdMj7& zB`+NQffkffBYzZf*4;`q$sKB4i+Tca%x2>F$}V9d7)^$#H~zC#;vPS~n?@Y#hwR=m zP>GwalqCYQN7+REg}~DMzaVjHk>$c5A{nqD!EQWJ0wByOG=eVZ`47GO3MJt?Ig+_n znSx=_Z5J-|03SJTfle=J=vH4Ao(M`S$Kn1`KShW0jA8%c@+bp!p6|9{26y{?C^{W{ zt}$?fhcyksUapIQ!q)>7HPxjW31I*&S3DW&L)i7n{$-j!`LgrTHAqHL!l0q&x|juE zKS%BIpbPFKq4u*bRx*BTnZcb=;(%S!*!4$se`#!_%*^;Yhx)VlUO_qZ7L9StAN28& zGF7m}{C-xnj_i(#qQMcIGr#~?_<%4dA6K#(GJ5v`=gsS<9Xu3^h1?70zZa3cV_T8k zKapKO%Oz{3g-y%bHl+8PvZ9hKNy1v)lN0m)L7qpw7-Isr);;2&Zq+X;}PaomBL<>1d8Tr%XG|K`NH zmk~BeLT>SwB*0_i}`RURWkH zT_cK0*=NoehPhwN>}s^rA%ua}b666=qe&T33mtzORK#REsb>nMtq7)5iaQa}%7?2K zru!;qR;iFy5U~K7yBHEZV|o`2uhh7E5M~xI2!m~)3IGSdZ(jTCwumV9P-@7z{We9qGI4c)8Bs+)01@^d1Fm|8E(FPC_banNSzu0%{4Crs;YUWy zM#ApG#pX04yAcqjn0nYV{fdS&0nR6Vy8bo>n(;YN!+CfGJd2(w-`)<5YtKtTF3@VI zJ3ZAOxLy8sH|79w07vn$fUh+puBqI_X?f#rRUf*s6MzEO9z@gF_`4dZk#Lr8-<%&U z?h|I?cVi4)sQIXaoQw-326j0|3eQsBM+aguvPlFLo5n6~@{GyS4JX+o359TXEgsia z7ij;mI<6Vd7-M|sQQeb+b2sc`;q@rp`z9$Q09k08ZajCM9$+D~-WZd_OQA7xi2Hz)UkS$Yc#gf@Hi78%zl?^*ZoO1XfcVi=ovKq@AQuWC zMOr%bDgO0^wf=JjLY0fN?K#{j4@DZ*1U#${eAvw;hEvWGWT6F11F$!QWL6gE-H{w{ zf5v61ktu9_OrjevY5!0d?lYn|9I+=OJf?^iZPZN54Yf!q1j>nEYN_{g85;>LY1RuZ zZujbkLPRWM+iANvykm*T**e2-kU8m+a3l^@(h|5Q81k-MdgrNA2BT?2#QwO|?mlg< zxUW36+u|VS`R8P_E+w*GdG4D(U>XPnuChO{5_0f23SzIspyeFQ&$IZ%`{8>tAkE7? z_;U#3`}rpN?_nY?#71v=Cd(^s^%&WDJOH0c9G)dl6}}<4^;%|Z068-y?}H||C;wGZ4%$R~K#f(BX#kX1 zB;HZoz=w_!fPr@aJJmykqb?r-D9FMwSPaH$**N{|rW^n@-l7mckB*`GFxF(O@fQ9o zY9iX=rtn$@sM?ZzH#!R)_Ymd(KrR=lV7jF7pOt{G?*Eceg8AsaZUWzcdeOi(NouIf zLGgO3NG4ikiWQRTz|jo|eaerI84`Smjp-m6Ge;~_ZT2@T+h}!=j3w14J4HW^UMC$R zxd6ku=lN75z2=(d1Q(k#umZ`tl%rqdOc2SMiO@-=R-;83oLx&g5&eXx#b|Vml5DvP z5J;XL(HRyi69caLGPg10Z}p^h%=!guA63)D(8pgA+5?;g#FWG-EF?PY=5>s?*&;KZ z=tDp?FrQl)kn={--`e{g{ln~6ltzR|s6_0?_vLhz2`rYnD~?ePGJHi!HG(yx184Yy z!QcS5S2yA*2m2YLntRVhrX6$3B;fYwU_Ah>lVh+y6{O{MHa#HDB|Iaw+`|S$2&zgiC>aZi0lP7%N`eolwP!JLLT#?T$)4X%=JZnwyn z60{4XRh$zVxV75*wXNsCZHSEJSk|>b&Rv1uJGf8Eq@FRjSaa=5u1v3ufKh2__UQL5 zc$7hPq$c+}q#FMKg&Ad;%N6C%uB$Uq+q(L<;hZMeF zJ_Dxu&7sGEKfjhf^v2&U9D>$UdtFoqWg_}snG|!YsS1EKf0t?IHkjnuW@D;7?tZ^@ z%25o@EtD97Z)R)8NxtwNjfEPz7C~f@b+UX>C+6wC#DAKWE#K7W`RLb*yMz+yeg?*l zKWbSAJwtL>OxuT-7bK#0R=*PQQiWkzcMM<*wdCMGp{P);`f?h^e3ONHr*7Fv3=MsJ z&%uYnGkkUt+ulMih0D*uF~i63^@|z(A9HW7=Y@H_lxg@^OT!a{UZVzXaVB3yB5iYW zY?E?`VwFq*PX7VtKak}(k|N4Pq#)XTuJk)0Z7k9gise{4=u*R9Cw@!Mslg;tJ8YBorBx z1lvY8;Qn@Z;RsMg8a%PxU(t~!<@`5$qo1LI9YDpM(#_P7(AY$i8UWLKhkagw7pfg= zPJxusJLX^4AsHxWE8A^@tpFMWE$aN4F)DgNHn!qzJRDUN`Ql=;2wF!%x_xo6U*s}3 zFlNkVH);+YUjE5W7bY%)XkQmBe{{fCe{^(vNhO0X5e;dCUN{lIX8DHE9)5uyg@Q)k zCzxb6R}FJ0SyB(s_QR4ns%>~T9~2a@h7)A%=)>7NKr}$FW*?>%8PNF3-sw<&Owwhu zS_h^xyIE!u)oq|mSV5pJ%1`t+_322aKbK8DgjeNcO^zXErd)!o_vTGhxxFFK4TU3d z$@wTA;pn*EwQ=b{|FuO9X^gpuGEgVW`>%8Lmz<&}Abr=4)fhJG!RqBlmZSo&M;jm` zrIBvde{N`{SDRwM9VBstwK+QBz+G!kRhTiwpCjw!hu3U^kcc^6fEWMPXq z#;{+qTJ~!*pFJh?U_l$IMSd#t(>!c`;{8b$k1yItq&Cm>rax-C6VYY4jx3C?eUtfx ze>LTg9G}lSbTgIbR^28J(2uh$Fg!S6i;6!@OgU||njuHa^(lq|KHHh4Q*2eY##IwC!(UN*84^*e%#w4fKo_*bGEp}ucD+w^eC`KgJ z718?%Dt|F=@h>6gGrdUU)rb3~JLOKfcK8wll&*}8ZBq8eRV5^bwRYdu!?d;5y4$Ro zo4#>sF*di2Xpp7F)o0w9=X;GH*Dv#2!QBu`##`;|q^xwK0p$prhU#d)S{zNxmqBA>D{2p;oBDtXzC^3$~BhKQ*xZGY;7|{k@Cx;7xG9a z6Ui6n7Ua+~;yVZ`+lv>EUd2nFj3*srL7Lv($l=}oknBl)(IAdV6#cOhH?uU8pjCF8 zgveY^X0F4hPSNU*>1w2UKpL}q@-xphWo7|#*INNZ;?&czV(^r88Z^XDl1Omq{!nq3 zq!y!p54RuQ;E`)~;V)y(NPUrHFzQ31vOcMB?juYAf>XFbIIw<-mItHFRDGhF_(Fom zcUyajt}jnt7gb4*r|8q@8O{35-Vy{h7MCEG-SKPc(!O_hN~|Vdi5yPHKxfpa432c9 zuts-=6l_6Ml0yaa6y!KpoJrP43wME5lbCH> z(32fm^ExM%-T73~Q^=w4q4AX_ah)H}mTchp;fZ2cHG+}o)K#CSa3xJIqp%pnpSS?SQR(VJaIcJ~EJ@ithV zFv{+O|9{*hm6SMph8McWBue)6f@k?)%g=P3(YZV$-c`{Zvg=<>iIy`#>=7SnEhIFB zU!*W)1Sn8#=RB0nDyK5x@L3P`p&9)0LpPy$#KBubAForzIjZFw&LRNMsJ|E{9mXq? z<)jbtX0VLU^l(b1JZ!62imd&%PHY|n)z>JCqO>0fFW}^x9SrBMkL1yAZIJhCC1F#2#Wl*F7tOA$kqZd9c6zI0^R zuB2g-^nZvt?`Ssv@bAaodo&0MVrwf#8#Aa)?b#}7uc}?N#HiUw?4o9=y|qSLt40ut z($dlvp^9n+QM=Fm{XM_uJm)yaKXRWtk#m2p>vg@a_uDC-Y%|lIdQ^&OnR1?Dw0vCr zuJS)e1a1^oI(_XnLn}|SP__riWFhIJ>L16U&4PlG%w)n>6W~es6Mw z#X@8NM3Yg=SjV7IU%+DQ_MoY7egnUf_)*`0-AAFBtMaVd?db;TPt{?F;VrW!C-oxl zZ8Q7kAJ-SX5P4x~T@IqgimO?#WfVJ~*UjWfM_vt#w`>FP;i1s+t|ZDNkdWfa)CL{I z>TvQZ@7!;RTC+j;2fVFczwo;%!h=XlW=4X(Y_n#iHOYsjC-JOOt#3I!c~ zdP!h_X{}EkU7#Sr8hbs4C~@h-ZR;Ao47^)peOoJ?ZQMT)54_p)l@GWMufV@2e;tIC zXRE&A`Y5JwpWH8(d4rfIC8JjRRj@I?xjqu^mm97q5kHa27qL=E_hIgf-!BH?0Uzg7 zzJQdj;TQ1|&fKKqyLurYnt3vDn_ZOtoAFhgLl)9!0mGfwW6i}=QP^6MtAjNZPtsAq z#b>>>$nRE*e?kc=T;b{n$xM54pLHG1%lY^pDCM@?LKeZ%!3v`@QmZ;l$<5ge0tHrb z>dAvCCv~F{BjS$&NyjVt9!gT2+I2G%F~+75S>WfEqBi!$&x^vj#NziKwFh6`+3X*Q z!~v0GQ%nuOZuiiN{M4gl8UEL!?hn%aKK&LQ#;$mOa)2kKP0+KpZX8g1v0$~;BFguX z!u+K|NEgVYxT$q~wcRYRq{flImJw+9vp@3} zC04qNLJyGv$UWvxr0-e-78kHmAZT9e5o=|Z#?6b3MTvPvpOmr^B=$o_FgLbf6bxU3 z#)zuxspD$nlx{6+0ggY0fKusapEj#u|L91dnKs=CBnmJBzZV2)eS(0OI9C}F&(Fs; zvCgxo%`F#8J=pOql(`M!k-rP2S<`MZK*tK&NgOyblmCv91-P}+W02`XAA zYu6zOSf^!nqqtH)0+%#{4gH?q;bEOt?BPFP-vSIYj&~#SOy?(WVJ`>QOTJldr zm`mL3_nCre7oE{2-tc;(n@y9yN4K7k&2m2$O6que%{ngmN$fGPHxy+ySl{CQq*hC@ zb0tcSX2V-c4NXsSQXCR?`7}xhJ~qfdGi9623a{2`Y`HFQr_rxM2Ie=Z*V;2oq`dwQ zG}*rLbk*ReoPPOv#bdwq6mRC!m;%H)vJgYDrH7afMZ)6dn|WocYX>g6$j}m)mD;^R z%_U8P`B*AO3cWE;AEQ<^fhIQg>asD3>AD|!R>UbT6(HpK)d*JSbVIZzzzKhnFnb_~ zayH3O07C-O+fPa};Uuv@x~`*y*>ie)4WfL2*_{HO(H{iat41=N>;=BSiO`~KVGmC* z$F91jf7bVL8z%R0*;ILgdaU;CC7?du3scx0Sosj}U~r8fQie>kR%TDFCuEb(hqX5j zHnX?1w?y$xbUAz@AIIDF)@b#tum2vPZbo2HRc4zPy=DDnfIQq)le_$g=%p}$ArClb zx=nA0s^JD=r_8nd-J^D%!gbSUEla^<8vjBZ~6$_R`QLwaLQI;&^rq$F*Fi$ zq7a0)s@J3pxSfTRA>O><^=I$)mvrRRSpvm%u;H%asV{fjp5l)n?T)yPQnxnWnd$2E zPUYcxlJpMj#AlA6b}9X`FrwX(KBIWn#|kd)+tWZnAtczXoGZ5UcTMY<5#7Rf7LPu! z6-?2(Xs+*XVs+f6ek^rUa^m-Q1T(Ip8)}|c_x>9>?M9}3`sfx+3^$Q!KQ4` zdFUL}SSgjjp+&{DQkqG@E+oB{7)ponHEtJGDbeE2(i(#{cQ!E*I5=$2&H@LdKk*H@ zyq50cWKs*Nrv%f{YIDWA2g5T!+rTo}&9ZqsPR%e^gxa!K!(NFehq1`r#?uX-^@b}m zezdOVvl}OfFIUCoK%EV+?;bY;mT_cx{7P}Oau1z-H|@WQcf$QuwPg5iIe;z@OfUTC zcc9|;S7b!5D-F3{x8b+u>fC5V38@7^pAaCls1ZS4bH^Da=AY3oq5&a)Tl2>OQRHx?D8?<_@V>LM|sD!DR zb@%{ zIt){L5b@0FF&RU%MGPcjE}1;U@m+rYCg%r^!rZQ@o;wX4SYWjqikc*qSv@Z%XgldZs^E6gc4o;1|Z#Yt$iO`Mo z#z@op@F0dFnn}a8*46id;whYZqR4O|3FQ6RlanqH>S$#5BF_RSwX_JBTPsLwIM1P* zOi|OPC(?Sdqa%62-+%rGkG5pan7*Cd$tTkZd4w zkqnmgkq`uil*w~(NQ{rfQmKBPDnDPJ6;P`EUzUwKu=s^KsXmX^W2PVS^amn00en5x zG_KzL_%xkGV z%t?(xR03(v7fr&=&g*({^SKdtPr63F40HfhH7E$F7DZJ()6yh zPHK-xroY%F@L2x6VH;1eG#ItjE1VsYl2qNyMV9)K@d9JYapq%n?$ZpWL83%rB4BPtdfPKIyH!_TmB1AZ~_*wE5`l9DPAP%ym z0g!1&&A?KPgP^lBjefu*W(f!T%C+AXeD28oP~^P&uo(TAg zNk5>cy|n%~9iL%SIpVOBN^zuE_iKpr=mIg8C5gLI>GB5R?o~Ij0b(0WPWvg>-<>uK zSwAfOG8gu;LCbgPb)nL1@4ih|(S08sXwH}Mzs3Y=%LTEAiLE?g8HS>imxOl`^1niE zN~pcsA28&mWy8hpod<0^eHOh~dGXyQ?AjlK-Vmlf%Z$~tywzcu3zSS zKdGRg*1#b8DJs19RsO?xXT$0mJ_-woY1hx);VY`%$}wC8q(;R1Yrce*(pE~srF-I8 zww-E*i%FvJwkbv5pLmwM2hyN1q+^Dneu(e*pNIqy4OfXKcrUI|Ixyo!CG+uy5DIhG zbuDcBB|)s?l{CpRmyX_kPek{2)}^>S4XTTU;Gv7;*$>;V%WprlySNqQsq|hwP?p=I zK&{WpDC#}WPOzN|lO$ZF+#@sB;OFC#YWUg#^ne^NYYooEm$1($k~UJ@mwBP=F(&kU z>hx>)1y0lw+vZQgO*J%B#Z;s+PW%z%hJP?r!$=bjX)&kZlej$H*$M}e(Omk+*>D6J zgtL%Z>)!=l*+}5DTzg1!uK&9EAIR`P)+G+ir;(qmAa}UM%@#DHO=g&h3f0Ij^F>1p z8nRQc#_pL}(fwoNzz*pKB;-6=j=qfabbsI{Xe72lYx@*Sf6=`;y;1#NEEIm0rV|;@ z5`ppm6+#?s7M|t>am|)VQ-BWWzR%8ft~>IMs6>#=Oag9A1O}>52Fo!xGDC5eay?NS z(CT_aA9KSaJ3MArQ~KQD=)x%?TTrVA+fdPr$lIJdJ#_&Lgn#_zTu}8%>{$m5q%G5Q zIFGlKv06;Z6Kmfk{4%q2Q^sFt)F?c8%D+69O+30A(q&rBr**I5sP!uQKTxW&)O-_o zL*x6e=+{t779Zl#ssz_WT0f7=4fiCo;EI#AGozByIkcJSlKlKgM6tqI062FpoS zJL5~lN0yR@Q>n3ctRLY_vW&x&H_zG1@_DF6oKPjIw>;9%1L@V0XaW5iMyT5y5wFRo z@!D7C1{<_VyvPSP)6N_#ukvUS>DVgqJPSxc-90$aiI+$!YNb*$1+34Mv3fRx9`-Wu zy3cD3`R*?LWNLPOr-FmFntonEYZCrx2^=iKDy}7?m^vf8W@_B$s|Yk|TG?>ep|S)c zx(V^q&AYNxSmOAYaaF51jyTW4{;Xdr;zgFolT_{pL>CRw27lKbu2?PUakkCrld78) zFuAWvvmbzIiVIx<$Eup1(A(Ri;jcSWv`J>nvXp*1H?AKo&?XKRxT zfF8nBU4;D1pT$Kl^uqj6F9Ip%$!8si4~sVZo2}#0%u?X<^-9a_+WSBZvPiY8nVrke z;`xMp2DRM6cO0oEnXN+YJtbR%Np{Sy0c*WxcCDi2F%0sVoprq|bnxH@3tO&!In*h- z`^~&guD#8-pW)Y4GPBKaDJY}x5Z#M8{KGfL z1&`X^aH8#+e9KbBI4aVsKbzM|5*7To4D}7TI$(6uCXH9zCIHt{8rjsjQX(I z;i`Yh=WV)UyC&t;@CO-<@dGw(Z1Ugt!ba|iEa^JP(=l4jU%k4KcYIOkawV8_FkfByzE z@M-A;F$=o8MC3wp;VfWkTE-v@^J$bf2ld7vKH>4p)NDt)zv0vHtQ*#}*%D8s#N=zP zc>E9*F^jy@0;0bAOae|DR|`ZutS2HwN>Ef9NEhm->f^D<{R<6~x}ws8ckA6eLAhJ3 zn&~QV;LD6vS5Kem-2d|c!1-U$C{jv?BFellA(Hj&AKTW_e96#P|zF18i;CbXqhr9 z%F1+A^pfmg;{8(A7q2cRnL%ll@z@S6FYOr3;H|)@lWhO|s|O!w zZFs1U1?36cP2aB=m2J}Y4N|(#+1GkpF@NP-xjW2b??0Di^yx!gkZ$^9Bu_(w->FW# zrU4yYb@{cldJrv9A~ySuH+-Wi;$HmFm;C_$1 zxPm9Oh`ll|+o@Pd7-MIg*&Br@q!{>E%7D_elE$?fHVf$F{GRYWe9}p~w@>@rJrYZW z@mSAZ3!rCKPoaaJMfcSlKw8U~V+S1#V@W<*Q@qmsryGAcLo%}%fYN;PXa1<8Lg%Jm z|CScSGzOJF)E_k8%#{G(wkb2MClMJq{JHfh?xF_p61amE2}0Z^3j(GM#H~Q^H`b{C zw8tNTBEPRp1hMd~5isV5TBMGA46d&3+(H3l_cM7RUV<|>ff@G?M0WzUiX{l-0?%;3 zavZoEAYX3(Ukv;oB0h-*S`fa|Frbe}ulv0D3w;cjttbG^h-dRTngsmz|EW)g0*k-l zN;yd75rcl|Gy!aIjOGJG=`3>dBq{|#z0A1bIOI{e*IXRoVmfxq-a$Apn>EEe5Eg56 zG2OL%gj~ndQM)WZ|4CzP6}oZO#}Z_-EH|#^a{_KsPMCUoYZE=W$IyJEzFzpZ(??yg zlPTwh>=)6|kuDWYw-GO&z_V;hke?J~xhc7>?R45c4Jb`Hz$6pY>f_<4-)Hh0_+@Xp zYN^N!A(ZxcUt?(aeSOu>k#F0se~N6X+4`hx5by_gtkLdcR9kF3$xY^!oKb!2{;Z>t zp6Nr`5Q%P=z{NPOGYdg%0Bc%mi{f957uBsU8(A(0mh#Or-2HSzWDqW|<@o11?X%P` zT1cZ6At`|rSIjg;Njc1p-uwJ>An_z(AJ24bi+^=J_zoWTEh~!B+j89RuldXH;F`?K zM#;%hG?~xk<$iTh!{qbq%9`1PH0-%Ja#3m_$yq)q5r;+wCq?f!U3zEIN5YhRCGGmc zKEmOMyz`ZZ9!V6p!xv&<9>f*G{G_ZqqlYteko##S|L#p$Uk{6k!@Plf0(0<64uS!c z`$4W!PS2K(EL0*Af_Qv#MBo|zTHMwKe|VcbuYNbpICmRK!_$V6kBDbQj+(!)27y4d zk~LL*O-Kp_o_BL^1T2k0q8#wzmybj1o$Xwj-d!&o;`~cpAPTV`>Fx)qw? z&q;yXrHkG{GJ63FS7KGOB7!X|Aw}0hW>FSAB;o-^*MI}bB|FnPbjOX3;)Pb8RV+C6 zL-@cso%o3Q*!C7hHr17g4CF@2`MWYAMH~HvoSCc(_6j{69Wd#suE_?zCL{Tokf3CV z`(6~L(Q&wDj``;`V_Sx+=-IjJvyM}^8!1$xlxjf83D3LxPqk&Q6c)S^bL)N@xlDRj zbnA9jbX-$yQyRVC^DLzjCVuNyu=|dXSP=)W(149cH#I(=11|e1CzCUzEDy%BD9gf| zP?Ha5Y4hU&6`E#S@eJl<-w;b~nS96Ko)v?jfMJ!ze)C}E-I!~a)ab61xd7H4(l-Lq z+33##sS6o3IgVY*o)3j2F&rTn1OmO}jczZ;^7(_NVA4J42YBT}^|Bv*1pG$4n65V) zE>EM;;UOlSd6d+y=O$TuH3;r~6z(5h1yXZ&qv>X*C@HB@Oar`%Wa&jRF|Lh@OC?~> z0)6=(6r+;5`T>sSkC6lCfpSr6QH~U5dE)wzn5;fdD#f5ZP-5iRnm~=qOf7^~Lz5YI z-#pkmb^!q%HmZdg=e}p6_}Hc}$Lp%_OV7*_bpPmrOH|8K-wN$9)pJ=O#Z_i<- zD*BLUD+~lfNiVcaH4oa`oL>SQHV-!y4DkFO=@sR4%ZR!9wmO9{>4usWhc=Zk{FQ{- z19%W1msTjbF59IJu1map>V`iE(C@0bjoSf_L96>}3MqBfAYnfCq%)GDp)jbmAZ69= zsl;-@D&u2mPokFaK<7pX%QcZ$Ezl4FBs$D;g0~%($ZI%{2tG; zUyoo%L6=9FG42qyJJi$6;g%0LiavylqM13<=smKGzNetXK0ib})`IYT; zeFb^lp`{uTJEOIy!gEsikUZ^@??+gTLMSozYgg(N@#Lb*zrONg4Tsu3h>Zi%xt^ws zY{jFOH-FiXbI>E92gao1;z>Yx6}Mqrr>E6XmUR!&@Hb->Fjuau&U3g zc)(WmI{f#5kdN5SbcVZ;pR`1~xWhTeQ?%RmcUKms2i$dmCZk+QyT5*V2PS zTB^S>Dw?ecZjeg(DB=hbI-lm4+*YLhWzrbm$(ZG4 zXKN1(zU$t@A&+AsLW^TDM`LGb*{Wc?d4zbaXLMKrS@rWDi$5Y$9qmn@%39S(fKc&b zDOVcrRwUV4K(w*NntCg{M(waOSiMPB*K^q|j0y^k1rahxD7A3s1Tp2U80BX+zJ1`T zTOm?!nwtC;>UMQ@Jbbq==d@taX|;ydGH2#`f)dI_=ji0PPb`&WYjME2)tOnadKLc)tKlLA|KZ^4|jQl31dg0-J7}*)X z$aeSVPfpn`N`WzEksd&p6PAEds=5O2D%#g~0F=Vvs_bI^2`5}TbIa}*6=Xi;f1sG& zYDq+*T&{>4K4HJV9x4}6p3&?mwKX{n3LX@tI`Km7Bn%c{Y(2iJS_Q4|U2vBs0=?J# z=VzGF=AWNP_Qc_Zl?kWA|)hvB{bOgEl#xwrrqu^mTzi}DeQF#5L<Q$0(aJ~wWP?x}wjtQtlv=Q8mBT5M4qMn0{VJKGdXz0o%EL08B~Dl@qz zg2&AadAug)^8Us!nmHo%ul}*_nvvGq;|YuJnJdm5hwT@rMyY#N-W zz8;HJsOa9ff3atK;zcaANFrZrcvU~Vr=R5ok0-d> zugy!~cb6_TtGKBq#WOC=Qj6t~VTIchZ}$hcBQND0n`Jrdv9%ns-@bpTS!7#eKHBEi z*$=}8Kc#xtb7N;$yotT-65rYrLrA()?L!d<;$HFJQ|$*2=3nk;-G*ljaZUh%G{tos z;;|z?l*$rS!(%?*e`)!*y!i)>u&%GKN7r291~u{Vqlzh5foEFuYp1Yw#i|BN<6vWj zNd-e!<4$TOn(h!`=0Bn;F*Eozb)WF_=7*t@hYqryl0a$rz$|~IO6E?FKQBCeNiBxo z^?Bb~_V;`~c>Ap!4h|lbY<*C=vEB$WM`q>4-J;sHop?Dtb$=jw@kMOfy!{U?C8F~x zt&Gywjd-06D1j(J+3;&;jQw#}8v8m1k>Qs-B?cSaYbQXjLDpm!Rw=;BEj@L@vc9`A z%^D6`u{2&xehn^+Y)~kkK;fC6cvN6~eYo#8<$8*6)HNp7h0TO`iYcwF{@b2!9z~R5 zd8i8uBafHSF$!<`B7+hh(&qkT~^qNrT9zDo27rrv;;18l$s8>jI@z{nJeD;FJ%{1t>dF^ zp=zoO)ER1XbNkB}DD8xb=-4vcag#ZLNK8j#O)0UOK=_d(C^)!I8GS$|hpBpRiO5a% zCgWM;hx!I{Fb0IVBv|W!=t*qgXF>g5l$q%hLx&nXZAUDJ6$fBlJ;aWxc4XCk;sytp zHuR-?0ua8G8S>h?5%jL)2+FUgfae^aj1{P66O)rjYj&bWYdVc~JHD^S;8--P*;%00P6cwMl}h0QKjB91O+BQPj&K zualUHk8eEheJMJ`Fo7DzBUUAgHrDJP2Ad0X1Z6OQC=;-#*{100vm6TAVIylT9DTOw ztxI~!)XdC@hq8Kl=$~j4ML|}3ut+uFwVnyd1fI4%%r}3l{_viO(;&SKG`s|hVxK78 zEJ>KoOS$;0VIE`2)(@cv`KmwCpMw7TbYWzUp|z~TXxCHZs+nrQL5WYve}Q_fx3d(P9Qf^iSBI(fMN59A*uVo(`DI&syU*=`Zo+6)^3@K-5;fMEXjMp#8HfxzO?2}K zU~Wkb2~9|))Kq`%VSpT<0>$A`FVXvS(^{DA)JzebqrEc?S+M>1&QeYBIhlAAyadMFs+{eA#@;KkeP!p-)YFR4B4z=??Ja@}8wnQ}OJ2k#1|+tw^rvQ?Kg2iq!Kez`jaN zt2hLlK}B&^6!M!+o0ZOan)3Vnx!J+?Pop8tDSUc#&`80mwN)S;*j!G-55L$%tEY!r zJjeHyU;6=fz2U7EF-ueXt6?rQuRVV}(PAHof;@5yo=zN-porp{KjB{5vD}jHDnB^h z09y3i1$Q?*bIZ`PBmdWCV!mSv#-mA&)|ptw+N4XyHcy{PZoGG)aHU*6slE_hq_J73 z^eWiT)(%U#g@;^UvfYxt4o%)BILd9Er726S+)bCsco~rAT4V~}saekB%GzTgq;Ydp~@3)NDYWAe*`@V~G21tbm9d$n4B+E74jQQ5kL~x3DXR|N| zmJdD# zI(su8`FL*2KFN$L9$e83zb$>p=@)#{U6=V&)2*b298vG*x@ngltBfC|SDGf=yf4-- zC}>2Y<{)vG=<`h^Qm~Wξ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%0D$&b`%3TzTe#-~GnSi)iP|#8z0T&_ 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

c<~!`xWw`%W0UluuN?NF66p)iOMgly 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�Qz{Xd<7vbT?#O_&vns^Rr+p}c*piB}K9bTl5 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

!I_!*Xx# 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