From 8e27afdb47cbb467167cc65b9fa84f25d8c8d6ba Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Mon, 6 Nov 2023 16:58:11 +0100 Subject: [PATCH 01/31] Move czicompress source to subfolder --- .github/actions/cmake-build/action.yml | 7 + .github/workflows/codeql.yml | 6 +- .../{cmake.yml => czicompress_cmake.yml} | 16 +- .reuse/dep5 | 4 +- README.md | 283 +---------------- .clang-format => czicompress/.clang-format | 0 .gitignore => czicompress/.gitignore | 0 .runsettings => czicompress/.runsettings | 0 CMakeLists.txt => czicompress/CMakeLists.txt | 0 .../CMakeSettings.json | 0 CPPLINT.cfg => czicompress/CPPLINT.cfg | 0 LICENSE => czicompress/LICENSE | 0 czicompress/README.md | 291 ++++++++++++++++++ ...D_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt | 0 {app => czicompress/app}/CMakeLists.txt | 0 .../app}/CZICompress_Config.h.in | 0 {app => czicompress/app}/CZIcompress.cpp | 0 .../app}/commandlineargshelper.cpp | 0 .../app}/commandlineargshelper.h | 0 .../app}/czicompress-longpaths.manifest | 0 .../czicompress-longpaths.manifest.license | 0 {capi => czicompress/capi}/CMakeLists.txt | 0 .../capi}/CZICompress_Config.h.in | 0 .../capi}/CZICompress_Resource.rc.in | 0 {capi => czicompress/capi}/capi.cpp | 0 {capi => czicompress/capi}/capi.h | 0 {cmake => czicompress/cmake}/SemVer.cmake | 0 {lib => czicompress/lib}/CMakeLists.txt | 0 .../lib}/CZICompress_Config.h.in | 0 {lib => czicompress/lib}/inc_libCZI.h | 0 {lib => czicompress/lib}/include/IConsoleio.h | 0 {lib => czicompress/lib}/include/IOperation.h | 0 {lib => czicompress/lib}/include/command.h | 0 .../lib}/include/commandlineoptions.h | 0 .../lib}/include/compressionstrategy.h | 0 .../lib}/include/progressinfo.h | 0 .../utils/errorhandling/winerrorformatting.h | 0 .../lib}/include/utils/utf8/utf8converter.h | 0 .../lib}/src/actionwithsubblockstatistics.h | 0 .../lib}/src/commandlineoptions.cpp | 0 {lib => czicompress/lib}/src/consoleio.cpp | 0 {lib => czicompress/lib}/src/consoleio.h | 0 {lib => czicompress/lib}/src/copyczi.cpp | 0 {lib => czicompress/lib}/src/copyczi.h | 0 {lib => czicompress/lib}/src/operation.cpp | 0 {lib => czicompress/lib}/src/operation.h | 0 {lib => czicompress/lib}/src/progressinfo.cpp | 0 .../errorhandling/winerrorformatting.cpp | 0 .../utf8/platforms/posix/utf8converter.cpp | 0 .../utf8/platforms/windows/utf8converter.cpp | 0 {tests => czicompress/tests}/CMakeLists.txt | 0 {tests => czicompress/tests}/libczi_utils.cpp | 0 {tests => czicompress/tests}/libczi_utils.h | 0 .../tests}/test_commandlineparsing.cpp | 0 .../tests}/test_copyoperation.cpp | 0 .../tests}/test_utf8_utils.cpp | 0 56 files changed, 316 insertions(+), 291 deletions(-) rename .github/workflows/{cmake.yml => czicompress_cmake.yml} (91%) rename .clang-format => czicompress/.clang-format (100%) rename .gitignore => czicompress/.gitignore (100%) rename .runsettings => czicompress/.runsettings (100%) rename CMakeLists.txt => czicompress/CMakeLists.txt (100%) rename CMakeSettings.json => czicompress/CMakeSettings.json (100%) rename CPPLINT.cfg => czicompress/CPPLINT.cfg (100%) rename LICENSE => czicompress/LICENSE (100%) create mode 100644 czicompress/README.md rename THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt => czicompress/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt (100%) rename {app => czicompress/app}/CMakeLists.txt (100%) rename {app => czicompress/app}/CZICompress_Config.h.in (100%) rename {app => czicompress/app}/CZIcompress.cpp (100%) rename {app => czicompress/app}/commandlineargshelper.cpp (100%) rename {app => czicompress/app}/commandlineargshelper.h (100%) rename {app => czicompress/app}/czicompress-longpaths.manifest (100%) rename {app => czicompress/app}/czicompress-longpaths.manifest.license (100%) rename {capi => czicompress/capi}/CMakeLists.txt (100%) rename {capi => czicompress/capi}/CZICompress_Config.h.in (100%) rename {capi => czicompress/capi}/CZICompress_Resource.rc.in (100%) rename {capi => czicompress/capi}/capi.cpp (100%) rename {capi => czicompress/capi}/capi.h (100%) rename {cmake => czicompress/cmake}/SemVer.cmake (100%) rename {lib => czicompress/lib}/CMakeLists.txt (100%) rename {lib => czicompress/lib}/CZICompress_Config.h.in (100%) rename {lib => czicompress/lib}/inc_libCZI.h (100%) rename {lib => czicompress/lib}/include/IConsoleio.h (100%) rename {lib => czicompress/lib}/include/IOperation.h (100%) rename {lib => czicompress/lib}/include/command.h (100%) rename {lib => czicompress/lib}/include/commandlineoptions.h (100%) rename {lib => czicompress/lib}/include/compressionstrategy.h (100%) rename {lib => czicompress/lib}/include/progressinfo.h (100%) rename {lib => czicompress/lib}/include/utils/errorhandling/winerrorformatting.h (100%) rename {lib => czicompress/lib}/include/utils/utf8/utf8converter.h (100%) rename {lib => czicompress/lib}/src/actionwithsubblockstatistics.h (100%) rename {lib => czicompress/lib}/src/commandlineoptions.cpp (100%) rename {lib => czicompress/lib}/src/consoleio.cpp (100%) rename {lib => czicompress/lib}/src/consoleio.h (100%) rename {lib => czicompress/lib}/src/copyczi.cpp (100%) rename {lib => czicompress/lib}/src/copyczi.h (100%) rename {lib => czicompress/lib}/src/operation.cpp (100%) rename {lib => czicompress/lib}/src/operation.h (100%) rename {lib => czicompress/lib}/src/progressinfo.cpp (100%) rename {lib => czicompress/lib}/src/utils/errorhandling/winerrorformatting.cpp (100%) rename {lib => czicompress/lib}/src/utils/utf8/platforms/posix/utf8converter.cpp (100%) rename {lib => czicompress/lib}/src/utils/utf8/platforms/windows/utf8converter.cpp (100%) rename {tests => czicompress/tests}/CMakeLists.txt (100%) rename {tests => czicompress/tests}/libczi_utils.cpp (100%) rename {tests => czicompress/tests}/libczi_utils.h (100%) rename {tests => czicompress/tests}/test_commandlineparsing.cpp (100%) rename {tests => czicompress/tests}/test_copyoperation.cpp (100%) rename {tests => czicompress/tests}/test_utf8_utils.cpp (100%) diff --git a/.github/actions/cmake-build/action.yml b/.github/actions/cmake-build/action.yml index 22c6f30..bc4a2b2 100644 --- a/.github/actions/cmake-build/action.yml +++ b/.github/actions/cmake-build/action.yml @@ -20,6 +20,9 @@ inputs: platform: description: 'OS platform' required: true + src-dir: + description: 'The directory with the sources' + required: true runs: using: "composite" steps: @@ -33,11 +36,13 @@ runs: - name: Set toolchain path shell: bash + working-directory: ${{inputs.src-dir}} run: echo "TOOLCHAIN_PATH=${{inputs.path-toolchain}}" >> "$GITHUB_ENV" - name: Install dependencies if: inputs.package == 'ON' shell: bash + working-directory: ${{inputs.src-dir}} run: | # We install the following packages: eigen3, catch2, cli11. Installing them with vcpkg (and caching them) # is faster than downloading and building them from source (which is the default behavior of the CZICompress-build-system). @@ -45,6 +50,7 @@ runs: - name: Configure CMake shell: bash + working-directory: ${{inputs.src-dir}} # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type # @@ -53,5 +59,6 @@ runs: - name: Build software shell: bash + working-directory: ${{inputs.src-dir}} # Build your program with the given configuration run: cmake --build build --config ${{inputs.build-type}} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f1c435e..b7fd5ca 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -17,7 +17,10 @@ permissions: jobs: analyze: - name: Analyze + name: Analyze CPP + defaults: + run: + working-directory: ${{github.workspace}}/czicompress runs-on: ubuntu-latest strategy: @@ -50,6 +53,7 @@ jobs: build-type: Release package: ON platform: x64-linux + src-dir: '${{github.workspace}}/czicompress' - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/cmake.yml b/.github/workflows/czicompress_cmake.yml similarity index 91% rename from .github/workflows/cmake.yml rename to .github/workflows/czicompress_cmake.yml index ff299c1..809142f 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/czicompress_cmake.yml @@ -4,8 +4,10 @@ name: CMake Build on: push: branches: ["main"] # run only when merge with main branch + paths: ["czicompress/**"] pull_request: branches: ["main"] # run only when merge with main branch + paths: ["czicompress/**"] permissions: contents: read @@ -17,6 +19,9 @@ env: jobs: build-tests: + defaults: + run: + working-directory: ${{github.workspace}}/czicompress name: ${{matrix.config.name}} runs-on: ${{matrix.config.os}} @@ -46,7 +51,7 @@ jobs: package: ON, path-cache: '${{ github.workspace }}\vcpkg\installed', path-toolchain: 'C:/vcpkg/scripts/buildsystems/vcpkg.cmake', - os-id: 'windows' + os-id: 'windows', } - { name: ubuntu-release-package-on, @@ -55,7 +60,7 @@ jobs: package: ON, path-cache: '/usr/local/share/vcpkg/installed', path-toolchain: '/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake', - os-id: 'linux' + os-id: 'linux', } steps: @@ -71,17 +76,18 @@ jobs: build-type: ${{env.BUILD_TYPE}} package: ${{matrix.config.package}} platform: ${{matrix.config.platform}} + src-dir: '${{github.workspace}}/czicompress' - name: Run unit tests - working-directory: ${{github.workspace}}/build + working-directory: ${{github.workspace}}/czicompress/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} - name: Prepare licenses - working-directory: ${{github.workspace}} shell: bash run: | + set -e mkdir ./czicompress-${{matrix.config.name}} cp -R ./THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt ./czicompress-${{matrix.config.name}}/ cp -R ./build/app/THIRD_PARTY_LICENSES.txt ./czicompress-${{matrix.config.name}}/ @@ -89,14 +95,12 @@ jobs: # gather the binaries - name: Prepare Linux binaries if: matrix.config.os == 'ubuntu-20.04' - working-directory: ${{github.workspace}} shell: bash run: | cp ./build/app/czicompress ./build/capi/libczicompressc.so ./czicompress-${{matrix.config.name}}/ - name: Prepare Windows binaries if: matrix.config.os == 'windows-latest' - working-directory: ${{github.workspace}} shell: bash run: | cp ./build/app/${{env.BUILD_TYPE}}/*.exe ./build/capi/${{env.BUILD_TYPE}}/libczicompressc.dll ./czicompress-${{matrix.config.name}}/ diff --git a/.reuse/dep5 b/.reuse/dep5 index 7ad3a9f..1b16637 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -3,10 +3,10 @@ Upstream-Name: czicompress Upstream-Contact: Carl Zeiss Microscopy GmbH Source: https://github.com/ZEISS/czicompress -Files: cla_*.txt CMakeSettings.json CODE_OF_CONDUCT.md CONTRIBUTING.md README.md SECURITY.md THIRD_PARTY_LICENSES*.txt +Files: cla_*.txt czicompress/CMakeSettings.json CODE_OF_CONDUCT.md CONTRIBUTING.md README.md SECURITY.md czicompress/THIRD_PARTY_LICENSES*.txt czicompress/README.md Copyright: 2023 Carl Zeiss Microscopy GmbH License: MIT -Files: .github/* .gitignore .editorconfig .runsettings CPPLINT.cfg .clang-format +Files: .github/* .gitignore czicompress/.editorconfig czicompress/.gitignore czicompress/.runsettings czicompress/CPPLINT.cfg czicompress/.clang-format Copyright: 2023 Carl Zeiss Microscopy GmbH License: CC0-1.0 diff --git a/README.md b/README.md index 48d1b06..8c11db1 100644 --- a/README.md +++ b/README.md @@ -7,285 +7,4 @@ Reduce the size of existing CZI files by converting them to zstd-compressed CZI files. -Copies the content of a CZI-file into another CZI-file changing the compression of the image data. -With the 'compress' command, uncompressed image data is converted to Zstd-compressed image data. -With the 'decompress' command, compressed image data is converted to uncompressed data. - -The tool is based on [libczi](https://github.com/ZEISS/libczi.git). - -## Table of contents - -- [Download](#download) -- [Usage](#usage) - - [Examples](#examples) - - [Single file](#single-file) - - [Multiple files (bash shell)](#multiple-files-bash-shell) - - [Multiple files (Powershell)](#multiple-files-powershell) -- [Build and Test](#build-and-test) - - [Pre-requisites](#pre-requisites) - - [Build in Visual Studio](#build-in-visual-studio) - - [Build in command line](#build-in-command-line) - - [Quick build](#quick-build) - - [Build with preferred compiler](#build-with-preferred-compiler) - - [Tests](#tests) -- [Known issues](#known-issues) -- [Guidelines](#guidelines) -- [Versioning](#versioning) -- [Licensing](#licensing) -- [Credits to Third Party Components](#credits-to-third-party-components) -- [Disclaimer](#disclaimer) - -## Download -We have not yet published any Releases. Meanwhile, you can download binaries from the artifacts of the latest [Build workflow run](https://github.com/zeissmicroscopy/czicompress/actions/workflows/cmake.yml?query=branch%3Amain). -Click on the topmost successful run and download the binaries for your platform: -* czicompress-windows-64-release-msvc-package-off for Windows -* czicompress-ubuntu-release-llvm-package-off for Linux - -Clicking on these artifacts will download a ZIP file with the executable. The executable is a stand-alone binary. You can put it anywhere you like. - -## Usage - -Start the executable from the command line, providing the required command line arguments. - -``` -Usage: czicompress.exe [OPTIONS] - -Options: - -h,--help Print this help message and exit - - -c,--command COMMAND - Specifies the mode of operation: 'compress' to convert to a - zstd-compressed CZI, 'decompress' to convert to a CZI - containing only uncompressed data. - - -i,--input SOURCE_FILE - The source CZI-file to be processed. - - -o,--output DESTINATION_FILE - The destination CZI-file to be written. - - -s,--strategy STRATEGY - Choose which subblocks of the source file are compressed. - STRATEGY can be one of 'all', 'uncompressed', - 'uncompressed_and_zstd'. The default is 'uncompressed'. - - -t,--compression_options COMPRESSION_OPTIONS - Specify compression parameters. The default is - 'zstd1:ExplicitLevel=1;PreProcess=HiLoByteUnpack'. - - -w,--overwrite If the output file exists, try to overwrite it. - - --ignore_duplicate_subblocks BOOLEAN - If this option is enabled, the operation will ignore if - duplicate subblocks are encountered in the source document. - Otherwise, an error will be reported. The default is 'on'. - - -Copies the content of a CZI-file into another CZI-file changing the compression -of the image data. -With the 'compress' command, uncompressed image data is converted to -Zstd-compressed image data. This can reduce the file size substantially. With -the 'decompress' command, compressed image data is converted to uncompressed -data. -For the 'compress' command, a compression strategy can be specified with the -'--strategy' option. It controls which subblocks of the source file will be -compressed. The source document may already contain compressed data (possibly -with a lossy compression scheme). In this case it is undesirable to compress the -data with lossless zstd, as that will almost certainly increase the file size. -Therefore, the "uncompressed" strategy compresses only uncompressed subblocks. -The "uncompressed_and_zstd" strategy compresses the subblocks that are -uncompressed OR compressed with Zstd, and the "all" strategy compresses all -subblocks, regardless of their current compression status. Some compression -schemes that can occur in a CZI-file cannot be decompressed by this tool. Data -compressed with such a scheme will be copied verbatim to the destination file, -regardless of the command and strategy chosen. -``` - -### Examples - -#### Single file -~~~ -czicompress -c compress -i MyImage.czi -o MyImage.zstd.czi -~~~ - -#### Multiple files (bash shell) -* Put czicompress on the PATH -~~~cs -export PATH="${PATH}:/dir/of/czicompress" -~~~ -* Put the following lines into a file called czicompress.sh: -~~~sh -#!/bin/bash -f="$1" -output="${f%%czi}zstd.czi" -echo -n "${f} -> ${output}: " -if [[ -f "$output" ]] -then - echo "Output file already exists." - rm -i "$output" -fi - -if [[ -f "$output" ]] -then - exit 1 -fi - -czicompress --command compress -i "$f" -o "$output" -status=$? -if [[ $status -eq 0 ]] -then - echo OK -fi - -exit $status -~~~ -* Then run -~~~sh -find -type f -name '*.czi' -not -iname '*.zstd.czi' -exec bash czicompress.sh '{}' \; -~~~ - -#### Multiple files (Powershell) -* Put czicompress on the PATH -~~~powershell -$env:Path += ";/dir/of/czicompress" -~~~ -* Put the following lines into a file called czicompress.ps1: -~~~powershell -param( - [Parameter(Mandatory=$true)] - [string]$directory, - [Parameter(Mandatory=$false)] - [string]$command = "compress", - [Parameter(Mandatory=$false)] - [string]$fileExtension = "czi", - [Parameter(Mandatory=$false)] - [string]$outputSuffix = ".zstd", - [Parameter(Mandatory=$false)] - [switch]$recursive = $false -) - -$cziCompressExe = "czicompress.exe" - -$filterString = "*.$($fileExtension)" - -if ($recursive) { - $files = Get-ChildItem -Path $directory -Filter $filterString -Recurse -} else { - $files = Get-ChildItem -Path $directory -Filter $filterString -} - -$files = $files | Where-Object { - $_.Name -notlike "*$outputSuffix$filterString" -} - -$files | ForEach-Object { - $outFileName = "$($_.DirectoryName)\$($_.BaseName)$outputSuffix$($_.Extension)" - Write-Host "$($_) -> $($outFileName)" - if ((Test-Path -Path $outFileName)) { - Write-Host "Output file already exists... Removing" - Remove-Item $outFileName - } - if ((Test-Path -Path $outFileName)) { - Write-Host "Unable to remove file" - # This will act as a continue because this code is executed as a script block and not like a classic for-each loop - return - } - - # The backticks here are to escape the double quotes which exist in case we have spaces in file path. - $execArguments = "& $($cziCompressExe) -i `"$_`" -o `"$($outFileName)`" --command $($command)" - iex $execArguments -} - -exit 0 -~~~ -* Then run -~~~powershell -./czicompress.ps1 -Directory . -~~~ - -## Build and Test - -Build either with Visual Studio or with CMake in command line. - -### Pre-requisites - -The `CZICompress` source codes are implemented using C++17 standard and can be compiled on Windows or Linux and with multiple compilers. You can compile codes by using IDE like Microsoft Visual Studio (MSVS), Visual Studio Code (VSCode) or any other IDE that works with `CMake`. Make sure that on your machine you have one of these tools: - -| | Windows | Linux | -| ---------- | ------------------- | ------------- | -| Build tool | CMake | CMake | -| Compilers | MSVC | GNU, LLVM | -| IDE | MSVS, VSCode, CLion | VSCode, CLion | - - -### Build in Visual Studio - -To build `czicompress` in *Microsoft Visual Studio* (or in *Visual Studio Code*) follow these steps: -1. Open `czicompress` folder in Visual Studio. -2. In the *Solution Explorer* select `CMakeLists.txt` file and make right click. -3. In the popped up menu select `Configure czicompress` (`Configure All Projects` in VSCode). -4. When configuration is finished, right click on `CMakeLists.txt` and the popped up menu select `Build` (`Build All Projects` in the VSCode). - -This configures and builds software using default compilers. - -### Build in command line - -#### Quick build - -On Windows or Linux platform, open the command line window in the cloned `czicompress` directory and type this to configure and build the sources: - -```bash -cmake -B build -DCMAKE_BUILD_TYPE=Release -cmake --build build -j 10 -``` - -This configures `Release` build using system default compiler. After the build, for the Windows platform the `czicompress.exe` executable is located in the `.\build\app\Release\` subdirectory of `czicompress` project directory and for the Linux it is located in the `./build/app/` subdirectory of `czicompress` project directory. - -#### Build with preferred compiler - -If you want to compile using a specific preferred compiler, either set environment variables `CXX` for the C++ compiler and `CC` for the C compiler, or specify compilers as `cmake` options. - -**Example 1:** if you want to compile under Linux or WSL ([Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/about) with LLVM compilers, before configuring build with `cmake` set environment variables. -```bash -export CC=/usr/bin/clang && export CXX=/usr/bin/clang++ -cmake -B ./build -DCMAKE_BUILD_TYPE=Release -``` - -**Example 2:** set the preferred C/C++ compilers as `cmake` options -```bash -cmake -B ./build -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -``` - -When the configuration is finished, make a build and via parameter `-j` specify the number of parallel jobs you want to run: -```bash -cmake --build ./build --config Release -j 10 -``` - -After the build, the `czicompress` executable will be in `./build/app/czicompress`. - -### Tests - -After the build, run the tests by executing `./build/tests/czicompress_tests`. - -## Known issues - -When compiling in Visual Studio for the first time, you may need to double compile. The Visual Studio is using "Ninja" for building and for some reason it is reporting a linker error `LINK: Fatal error LNK1168: cannot open app\czicompress.exe for writing` despite the fact that the `czicompress.exe` is created. - - -## Guidelines -[Code of Conduct](./CODE_OF_CONDUCT.md) -[Contributing](./CONTRIBUTING.md) - -## Versioning -For czicompress the [semantic versioning scheme 2.0](https://semver.org/) is to be applied. The version number is defined in the top-level [CMakeLists.txt](./CMakeLists.txt). This is the only place where the version number is defined. When making changes, this version number needs to be updated accordingly. For determining whether a change is a breaking one, the source code level is decisive; i.e. not binary compatibility (of the resulting static or dynamic library) is to be considered, but the case of re-compiling with the changed sources. A borderline case are maybe changes in the CMake-files - here the best judgement is to be applied whether a change in the compilation result will occur. - -## Licensing -Carl Zeiss Microscopy GmbH provides czicompress under the [MIT License](./LICENSE). - -## Credits to Third Party Components -As part of our CI/CD, the following 3rd party components are re-distributed: -- [libCZI + 3rd Party Source Code Components](https://github.com/ZEISS/libczi/tree/main#credits-to-third-party-components) -- [libCZI Linked Dependencies + czicompress Linked Dependencies](./THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt) - -## Disclaimer -ZEISS, ZEISS.com are registered trademarks of Carl Zeiss AG. +[More ...](czicompress/README.md) \ No newline at end of file diff --git a/.clang-format b/czicompress/.clang-format similarity index 100% rename from .clang-format rename to czicompress/.clang-format diff --git a/.gitignore b/czicompress/.gitignore similarity index 100% rename from .gitignore rename to czicompress/.gitignore diff --git a/.runsettings b/czicompress/.runsettings similarity index 100% rename from .runsettings rename to czicompress/.runsettings diff --git a/CMakeLists.txt b/czicompress/CMakeLists.txt similarity index 100% rename from CMakeLists.txt rename to czicompress/CMakeLists.txt diff --git a/CMakeSettings.json b/czicompress/CMakeSettings.json similarity index 100% rename from CMakeSettings.json rename to czicompress/CMakeSettings.json diff --git a/CPPLINT.cfg b/czicompress/CPPLINT.cfg similarity index 100% rename from CPPLINT.cfg rename to czicompress/CPPLINT.cfg diff --git a/LICENSE b/czicompress/LICENSE similarity index 100% rename from LICENSE rename to czicompress/LICENSE diff --git a/czicompress/README.md b/czicompress/README.md new file mode 100644 index 0000000..48d1b06 --- /dev/null +++ b/czicompress/README.md @@ -0,0 +1,291 @@ +# czicompress +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![REUSE status](https://api.reuse.software/badge/github.com/ZEISS/czicompress)](https://api.reuse.software/info/github.com/ZEISS/czicompress) +[![CMake](https://github.com/ZEISS/czicompress/actions/workflows/cmake.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/cmake.yml) +[![CodeQL](https://github.com/ZEISS/czicompress/actions/workflows/codeql.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/codeql.yml) +[![MegaLinter](https://github.com/ZEISS/czicompress/actions/workflows/mega-linter.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/mega-linter.yml) + +Reduce the size of existing CZI files by converting them to zstd-compressed CZI files. + +Copies the content of a CZI-file into another CZI-file changing the compression of the image data. +With the 'compress' command, uncompressed image data is converted to Zstd-compressed image data. +With the 'decompress' command, compressed image data is converted to uncompressed data. + +The tool is based on [libczi](https://github.com/ZEISS/libczi.git). + +## Table of contents + +- [Download](#download) +- [Usage](#usage) + - [Examples](#examples) + - [Single file](#single-file) + - [Multiple files (bash shell)](#multiple-files-bash-shell) + - [Multiple files (Powershell)](#multiple-files-powershell) +- [Build and Test](#build-and-test) + - [Pre-requisites](#pre-requisites) + - [Build in Visual Studio](#build-in-visual-studio) + - [Build in command line](#build-in-command-line) + - [Quick build](#quick-build) + - [Build with preferred compiler](#build-with-preferred-compiler) + - [Tests](#tests) +- [Known issues](#known-issues) +- [Guidelines](#guidelines) +- [Versioning](#versioning) +- [Licensing](#licensing) +- [Credits to Third Party Components](#credits-to-third-party-components) +- [Disclaimer](#disclaimer) + +## Download +We have not yet published any Releases. Meanwhile, you can download binaries from the artifacts of the latest [Build workflow run](https://github.com/zeissmicroscopy/czicompress/actions/workflows/cmake.yml?query=branch%3Amain). +Click on the topmost successful run and download the binaries for your platform: +* czicompress-windows-64-release-msvc-package-off for Windows +* czicompress-ubuntu-release-llvm-package-off for Linux + +Clicking on these artifacts will download a ZIP file with the executable. The executable is a stand-alone binary. You can put it anywhere you like. + +## Usage + +Start the executable from the command line, providing the required command line arguments. + +``` +Usage: czicompress.exe [OPTIONS] + +Options: + -h,--help Print this help message and exit + + -c,--command COMMAND + Specifies the mode of operation: 'compress' to convert to a + zstd-compressed CZI, 'decompress' to convert to a CZI + containing only uncompressed data. + + -i,--input SOURCE_FILE + The source CZI-file to be processed. + + -o,--output DESTINATION_FILE + The destination CZI-file to be written. + + -s,--strategy STRATEGY + Choose which subblocks of the source file are compressed. + STRATEGY can be one of 'all', 'uncompressed', + 'uncompressed_and_zstd'. The default is 'uncompressed'. + + -t,--compression_options COMPRESSION_OPTIONS + Specify compression parameters. The default is + 'zstd1:ExplicitLevel=1;PreProcess=HiLoByteUnpack'. + + -w,--overwrite If the output file exists, try to overwrite it. + + --ignore_duplicate_subblocks BOOLEAN + If this option is enabled, the operation will ignore if + duplicate subblocks are encountered in the source document. + Otherwise, an error will be reported. The default is 'on'. + + +Copies the content of a CZI-file into another CZI-file changing the compression +of the image data. +With the 'compress' command, uncompressed image data is converted to +Zstd-compressed image data. This can reduce the file size substantially. With +the 'decompress' command, compressed image data is converted to uncompressed +data. +For the 'compress' command, a compression strategy can be specified with the +'--strategy' option. It controls which subblocks of the source file will be +compressed. The source document may already contain compressed data (possibly +with a lossy compression scheme). In this case it is undesirable to compress the +data with lossless zstd, as that will almost certainly increase the file size. +Therefore, the "uncompressed" strategy compresses only uncompressed subblocks. +The "uncompressed_and_zstd" strategy compresses the subblocks that are +uncompressed OR compressed with Zstd, and the "all" strategy compresses all +subblocks, regardless of their current compression status. Some compression +schemes that can occur in a CZI-file cannot be decompressed by this tool. Data +compressed with such a scheme will be copied verbatim to the destination file, +regardless of the command and strategy chosen. +``` + +### Examples + +#### Single file +~~~ +czicompress -c compress -i MyImage.czi -o MyImage.zstd.czi +~~~ + +#### Multiple files (bash shell) +* Put czicompress on the PATH +~~~cs +export PATH="${PATH}:/dir/of/czicompress" +~~~ +* Put the following lines into a file called czicompress.sh: +~~~sh +#!/bin/bash +f="$1" +output="${f%%czi}zstd.czi" +echo -n "${f} -> ${output}: " +if [[ -f "$output" ]] +then + echo "Output file already exists." + rm -i "$output" +fi + +if [[ -f "$output" ]] +then + exit 1 +fi + +czicompress --command compress -i "$f" -o "$output" +status=$? +if [[ $status -eq 0 ]] +then + echo OK +fi + +exit $status +~~~ +* Then run +~~~sh +find -type f -name '*.czi' -not -iname '*.zstd.czi' -exec bash czicompress.sh '{}' \; +~~~ + +#### Multiple files (Powershell) +* Put czicompress on the PATH +~~~powershell +$env:Path += ";/dir/of/czicompress" +~~~ +* Put the following lines into a file called czicompress.ps1: +~~~powershell +param( + [Parameter(Mandatory=$true)] + [string]$directory, + [Parameter(Mandatory=$false)] + [string]$command = "compress", + [Parameter(Mandatory=$false)] + [string]$fileExtension = "czi", + [Parameter(Mandatory=$false)] + [string]$outputSuffix = ".zstd", + [Parameter(Mandatory=$false)] + [switch]$recursive = $false +) + +$cziCompressExe = "czicompress.exe" + +$filterString = "*.$($fileExtension)" + +if ($recursive) { + $files = Get-ChildItem -Path $directory -Filter $filterString -Recurse +} else { + $files = Get-ChildItem -Path $directory -Filter $filterString +} + +$files = $files | Where-Object { + $_.Name -notlike "*$outputSuffix$filterString" +} + +$files | ForEach-Object { + $outFileName = "$($_.DirectoryName)\$($_.BaseName)$outputSuffix$($_.Extension)" + Write-Host "$($_) -> $($outFileName)" + if ((Test-Path -Path $outFileName)) { + Write-Host "Output file already exists... Removing" + Remove-Item $outFileName + } + if ((Test-Path -Path $outFileName)) { + Write-Host "Unable to remove file" + # This will act as a continue because this code is executed as a script block and not like a classic for-each loop + return + } + + # The backticks here are to escape the double quotes which exist in case we have spaces in file path. + $execArguments = "& $($cziCompressExe) -i `"$_`" -o `"$($outFileName)`" --command $($command)" + iex $execArguments +} + +exit 0 +~~~ +* Then run +~~~powershell +./czicompress.ps1 -Directory . +~~~ + +## Build and Test + +Build either with Visual Studio or with CMake in command line. + +### Pre-requisites + +The `CZICompress` source codes are implemented using C++17 standard and can be compiled on Windows or Linux and with multiple compilers. You can compile codes by using IDE like Microsoft Visual Studio (MSVS), Visual Studio Code (VSCode) or any other IDE that works with `CMake`. Make sure that on your machine you have one of these tools: + +| | Windows | Linux | +| ---------- | ------------------- | ------------- | +| Build tool | CMake | CMake | +| Compilers | MSVC | GNU, LLVM | +| IDE | MSVS, VSCode, CLion | VSCode, CLion | + + +### Build in Visual Studio + +To build `czicompress` in *Microsoft Visual Studio* (or in *Visual Studio Code*) follow these steps: +1. Open `czicompress` folder in Visual Studio. +2. In the *Solution Explorer* select `CMakeLists.txt` file and make right click. +3. In the popped up menu select `Configure czicompress` (`Configure All Projects` in VSCode). +4. When configuration is finished, right click on `CMakeLists.txt` and the popped up menu select `Build` (`Build All Projects` in the VSCode). + +This configures and builds software using default compilers. + +### Build in command line + +#### Quick build + +On Windows or Linux platform, open the command line window in the cloned `czicompress` directory and type this to configure and build the sources: + +```bash +cmake -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build -j 10 +``` + +This configures `Release` build using system default compiler. After the build, for the Windows platform the `czicompress.exe` executable is located in the `.\build\app\Release\` subdirectory of `czicompress` project directory and for the Linux it is located in the `./build/app/` subdirectory of `czicompress` project directory. + +#### Build with preferred compiler + +If you want to compile using a specific preferred compiler, either set environment variables `CXX` for the C++ compiler and `CC` for the C compiler, or specify compilers as `cmake` options. + +**Example 1:** if you want to compile under Linux or WSL ([Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/about) with LLVM compilers, before configuring build with `cmake` set environment variables. +```bash +export CC=/usr/bin/clang && export CXX=/usr/bin/clang++ +cmake -B ./build -DCMAKE_BUILD_TYPE=Release +``` + +**Example 2:** set the preferred C/C++ compilers as `cmake` options +```bash +cmake -B ./build -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ +``` + +When the configuration is finished, make a build and via parameter `-j` specify the number of parallel jobs you want to run: +```bash +cmake --build ./build --config Release -j 10 +``` + +After the build, the `czicompress` executable will be in `./build/app/czicompress`. + +### Tests + +After the build, run the tests by executing `./build/tests/czicompress_tests`. + +## Known issues + +When compiling in Visual Studio for the first time, you may need to double compile. The Visual Studio is using "Ninja" for building and for some reason it is reporting a linker error `LINK: Fatal error LNK1168: cannot open app\czicompress.exe for writing` despite the fact that the `czicompress.exe` is created. + + +## Guidelines +[Code of Conduct](./CODE_OF_CONDUCT.md) +[Contributing](./CONTRIBUTING.md) + +## Versioning +For czicompress the [semantic versioning scheme 2.0](https://semver.org/) is to be applied. The version number is defined in the top-level [CMakeLists.txt](./CMakeLists.txt). This is the only place where the version number is defined. When making changes, this version number needs to be updated accordingly. For determining whether a change is a breaking one, the source code level is decisive; i.e. not binary compatibility (of the resulting static or dynamic library) is to be considered, but the case of re-compiling with the changed sources. A borderline case are maybe changes in the CMake-files - here the best judgement is to be applied whether a change in the compilation result will occur. + +## Licensing +Carl Zeiss Microscopy GmbH provides czicompress under the [MIT License](./LICENSE). + +## Credits to Third Party Components +As part of our CI/CD, the following 3rd party components are re-distributed: +- [libCZI + 3rd Party Source Code Components](https://github.com/ZEISS/libczi/tree/main#credits-to-third-party-components) +- [libCZI Linked Dependencies + czicompress Linked Dependencies](./THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt) + +## Disclaimer +ZEISS, ZEISS.com are registered trademarks of Carl Zeiss AG. diff --git a/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt b/czicompress/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt similarity index 100% rename from THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt rename to czicompress/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt diff --git a/app/CMakeLists.txt b/czicompress/app/CMakeLists.txt similarity index 100% rename from app/CMakeLists.txt rename to czicompress/app/CMakeLists.txt diff --git a/app/CZICompress_Config.h.in b/czicompress/app/CZICompress_Config.h.in similarity index 100% rename from app/CZICompress_Config.h.in rename to czicompress/app/CZICompress_Config.h.in diff --git a/app/CZIcompress.cpp b/czicompress/app/CZIcompress.cpp similarity index 100% rename from app/CZIcompress.cpp rename to czicompress/app/CZIcompress.cpp diff --git a/app/commandlineargshelper.cpp b/czicompress/app/commandlineargshelper.cpp similarity index 100% rename from app/commandlineargshelper.cpp rename to czicompress/app/commandlineargshelper.cpp diff --git a/app/commandlineargshelper.h b/czicompress/app/commandlineargshelper.h similarity index 100% rename from app/commandlineargshelper.h rename to czicompress/app/commandlineargshelper.h diff --git a/app/czicompress-longpaths.manifest b/czicompress/app/czicompress-longpaths.manifest similarity index 100% rename from app/czicompress-longpaths.manifest rename to czicompress/app/czicompress-longpaths.manifest diff --git a/app/czicompress-longpaths.manifest.license b/czicompress/app/czicompress-longpaths.manifest.license similarity index 100% rename from app/czicompress-longpaths.manifest.license rename to czicompress/app/czicompress-longpaths.manifest.license diff --git a/capi/CMakeLists.txt b/czicompress/capi/CMakeLists.txt similarity index 100% rename from capi/CMakeLists.txt rename to czicompress/capi/CMakeLists.txt diff --git a/capi/CZICompress_Config.h.in b/czicompress/capi/CZICompress_Config.h.in similarity index 100% rename from capi/CZICompress_Config.h.in rename to czicompress/capi/CZICompress_Config.h.in diff --git a/capi/CZICompress_Resource.rc.in b/czicompress/capi/CZICompress_Resource.rc.in similarity index 100% rename from capi/CZICompress_Resource.rc.in rename to czicompress/capi/CZICompress_Resource.rc.in diff --git a/capi/capi.cpp b/czicompress/capi/capi.cpp similarity index 100% rename from capi/capi.cpp rename to czicompress/capi/capi.cpp diff --git a/capi/capi.h b/czicompress/capi/capi.h similarity index 100% rename from capi/capi.h rename to czicompress/capi/capi.h diff --git a/cmake/SemVer.cmake b/czicompress/cmake/SemVer.cmake similarity index 100% rename from cmake/SemVer.cmake rename to czicompress/cmake/SemVer.cmake diff --git a/lib/CMakeLists.txt b/czicompress/lib/CMakeLists.txt similarity index 100% rename from lib/CMakeLists.txt rename to czicompress/lib/CMakeLists.txt diff --git a/lib/CZICompress_Config.h.in b/czicompress/lib/CZICompress_Config.h.in similarity index 100% rename from lib/CZICompress_Config.h.in rename to czicompress/lib/CZICompress_Config.h.in diff --git a/lib/inc_libCZI.h b/czicompress/lib/inc_libCZI.h similarity index 100% rename from lib/inc_libCZI.h rename to czicompress/lib/inc_libCZI.h diff --git a/lib/include/IConsoleio.h b/czicompress/lib/include/IConsoleio.h similarity index 100% rename from lib/include/IConsoleio.h rename to czicompress/lib/include/IConsoleio.h diff --git a/lib/include/IOperation.h b/czicompress/lib/include/IOperation.h similarity index 100% rename from lib/include/IOperation.h rename to czicompress/lib/include/IOperation.h diff --git a/lib/include/command.h b/czicompress/lib/include/command.h similarity index 100% rename from lib/include/command.h rename to czicompress/lib/include/command.h diff --git a/lib/include/commandlineoptions.h b/czicompress/lib/include/commandlineoptions.h similarity index 100% rename from lib/include/commandlineoptions.h rename to czicompress/lib/include/commandlineoptions.h diff --git a/lib/include/compressionstrategy.h b/czicompress/lib/include/compressionstrategy.h similarity index 100% rename from lib/include/compressionstrategy.h rename to czicompress/lib/include/compressionstrategy.h diff --git a/lib/include/progressinfo.h b/czicompress/lib/include/progressinfo.h similarity index 100% rename from lib/include/progressinfo.h rename to czicompress/lib/include/progressinfo.h diff --git a/lib/include/utils/errorhandling/winerrorformatting.h b/czicompress/lib/include/utils/errorhandling/winerrorformatting.h similarity index 100% rename from lib/include/utils/errorhandling/winerrorformatting.h rename to czicompress/lib/include/utils/errorhandling/winerrorformatting.h diff --git a/lib/include/utils/utf8/utf8converter.h b/czicompress/lib/include/utils/utf8/utf8converter.h similarity index 100% rename from lib/include/utils/utf8/utf8converter.h rename to czicompress/lib/include/utils/utf8/utf8converter.h diff --git a/lib/src/actionwithsubblockstatistics.h b/czicompress/lib/src/actionwithsubblockstatistics.h similarity index 100% rename from lib/src/actionwithsubblockstatistics.h rename to czicompress/lib/src/actionwithsubblockstatistics.h diff --git a/lib/src/commandlineoptions.cpp b/czicompress/lib/src/commandlineoptions.cpp similarity index 100% rename from lib/src/commandlineoptions.cpp rename to czicompress/lib/src/commandlineoptions.cpp diff --git a/lib/src/consoleio.cpp b/czicompress/lib/src/consoleio.cpp similarity index 100% rename from lib/src/consoleio.cpp rename to czicompress/lib/src/consoleio.cpp diff --git a/lib/src/consoleio.h b/czicompress/lib/src/consoleio.h similarity index 100% rename from lib/src/consoleio.h rename to czicompress/lib/src/consoleio.h diff --git a/lib/src/copyczi.cpp b/czicompress/lib/src/copyczi.cpp similarity index 100% rename from lib/src/copyczi.cpp rename to czicompress/lib/src/copyczi.cpp diff --git a/lib/src/copyczi.h b/czicompress/lib/src/copyczi.h similarity index 100% rename from lib/src/copyczi.h rename to czicompress/lib/src/copyczi.h diff --git a/lib/src/operation.cpp b/czicompress/lib/src/operation.cpp similarity index 100% rename from lib/src/operation.cpp rename to czicompress/lib/src/operation.cpp diff --git a/lib/src/operation.h b/czicompress/lib/src/operation.h similarity index 100% rename from lib/src/operation.h rename to czicompress/lib/src/operation.h diff --git a/lib/src/progressinfo.cpp b/czicompress/lib/src/progressinfo.cpp similarity index 100% rename from lib/src/progressinfo.cpp rename to czicompress/lib/src/progressinfo.cpp diff --git a/lib/src/utils/errorhandling/winerrorformatting.cpp b/czicompress/lib/src/utils/errorhandling/winerrorformatting.cpp similarity index 100% rename from lib/src/utils/errorhandling/winerrorformatting.cpp rename to czicompress/lib/src/utils/errorhandling/winerrorformatting.cpp diff --git a/lib/src/utils/utf8/platforms/posix/utf8converter.cpp b/czicompress/lib/src/utils/utf8/platforms/posix/utf8converter.cpp similarity index 100% rename from lib/src/utils/utf8/platforms/posix/utf8converter.cpp rename to czicompress/lib/src/utils/utf8/platforms/posix/utf8converter.cpp diff --git a/lib/src/utils/utf8/platforms/windows/utf8converter.cpp b/czicompress/lib/src/utils/utf8/platforms/windows/utf8converter.cpp similarity index 100% rename from lib/src/utils/utf8/platforms/windows/utf8converter.cpp rename to czicompress/lib/src/utils/utf8/platforms/windows/utf8converter.cpp diff --git a/tests/CMakeLists.txt b/czicompress/tests/CMakeLists.txt similarity index 100% rename from tests/CMakeLists.txt rename to czicompress/tests/CMakeLists.txt diff --git a/tests/libczi_utils.cpp b/czicompress/tests/libczi_utils.cpp similarity index 100% rename from tests/libczi_utils.cpp rename to czicompress/tests/libczi_utils.cpp diff --git a/tests/libczi_utils.h b/czicompress/tests/libczi_utils.h similarity index 100% rename from tests/libczi_utils.h rename to czicompress/tests/libczi_utils.h diff --git a/tests/test_commandlineparsing.cpp b/czicompress/tests/test_commandlineparsing.cpp similarity index 100% rename from tests/test_commandlineparsing.cpp rename to czicompress/tests/test_commandlineparsing.cpp diff --git a/tests/test_copyoperation.cpp b/czicompress/tests/test_copyoperation.cpp similarity index 100% rename from tests/test_copyoperation.cpp rename to czicompress/tests/test_copyoperation.cpp diff --git a/tests/test_utf8_utils.cpp b/czicompress/tests/test_utf8_utils.cpp similarity index 100% rename from tests/test_utf8_utils.cpp rename to czicompress/tests/test_utf8_utils.cpp From ae2d7f301077ba3cac9cb36369982c8279be0a5b Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 09:10:28 +0100 Subject: [PATCH 02/31] Add manual trigger for build --- .github/workflows/czicompress_cmake.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/czicompress_cmake.yml b/.github/workflows/czicompress_cmake.yml index 809142f..d3a43b1 100644 --- a/.github/workflows/czicompress_cmake.yml +++ b/.github/workflows/czicompress_cmake.yml @@ -8,6 +8,7 @@ on: pull_request: branches: ["main"] # run only when merge with main branch paths: ["czicompress/**"] + workflow_dispatch: {} permissions: contents: read From 48213e96ae1ed5a156b3f566b77c0df1ce20c2a4 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 10:05:31 +0100 Subject: [PATCH 03/31] Turn off vcpkg for ubuntu --- .github/workflows/czicompress_cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/czicompress_cmake.yml b/.github/workflows/czicompress_cmake.yml index d3a43b1..300edd7 100644 --- a/.github/workflows/czicompress_cmake.yml +++ b/.github/workflows/czicompress_cmake.yml @@ -58,7 +58,7 @@ jobs: name: ubuntu-release-package-on, os: ubuntu-20.04, # we want to use an older version in order to increase likelihood that binaries work on other distros platform: x64-linux, - package: ON, + package: OFF, path-cache: '/usr/local/share/vcpkg/installed', path-toolchain: '/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake', os-id: 'linux', From 6933e9e93497a976b5a689a4a7c29a7c20c8efcf Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 13:06:52 +0100 Subject: [PATCH 04/31] Rename codeql for czicompress --- .github/workflows/codeql.yml | 61 ------------------------------------ 1 file changed, 61 deletions(-) delete mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index b7fd5ca..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -name: "CodeQL" - -on: - push: - branches: ["main"] - pull_request: - # The branches below must be a subset of the branches above - branches: ["main"] - schedule: - - cron: "22 16 * * 4" - -permissions: - actions: read - contents: read - security-events: write - -jobs: - analyze: - name: Analyze CPP - defaults: - run: - working-directory: ${{github.workspace}}/czicompress - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - language: ["cpp"] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - - uses: ./.github/actions/cmake-build - with: - path-cache: '/usr/local/share/vcpkg/installed' - path-toolchain: '/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake' - os-id: 'linux' - build-type: Release - package: ON - platform: x64-linux - src-dir: '${{github.workspace}}/czicompress' - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{matrix.language}}" From 521c24e35dd467787b92cdae2656a04751e21584 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 13:48:23 +0100 Subject: [PATCH 05/31] Update READMEs --- README.md | 18 +++++++++++++++--- czicompress/README.md | 5 ++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8c11db1..1cead74 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,22 @@ # czicompress [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![REUSE status](https://api.reuse.software/badge/github.com/ZEISS/czicompress)](https://api.reuse.software/info/github.com/ZEISS/czicompress) -[![CMake](https://github.com/ZEISS/czicompress/actions/workflows/cmake.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/cmake.yml) -[![CodeQL](https://github.com/ZEISS/czicompress/actions/workflows/codeql.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/codeql.yml) [![MegaLinter](https://github.com/ZEISS/czicompress/actions/workflows/mega-linter.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/mega-linter.yml) Reduce the size of existing CZI files by converting them to zstd-compressed CZI files. -[More ...](czicompress/README.md) \ No newline at end of file +# Command line tool and C/C++ libraries +[![CMake](https://github.com/ZEISS/czicompress/actions/workflows/cmake.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_cmake.yml) +[![CodeQL](https://github.com/ZEISS/czicompress/actions/workflows/codeql.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_codeql.yml) + +[czicompress](czicompress/README.md) is a cross-platform command line tool to compress CZIs. czicompress and its source code are provided under the [MIT license](LICENSE). + +# CziShrink desktop app +[czishrink](czishrink/README.md) is a desktop app to compress CZIs. CziShrink is provided under the [GPLv3 license](czishrink/LICENSE.txt). + +## Guidelines +[Code of Conduct](./CODE_OF_CONDUCT.md) +[Contributing](./CONTRIBUTING.md) + +## Disclaimer +ZEISS, ZEISS.com are registered trademarks of Carl Zeiss AG. \ No newline at end of file diff --git a/czicompress/README.md b/czicompress/README.md index 48d1b06..712816c 100644 --- a/czicompress/README.md +++ b/czicompress/README.md @@ -1,8 +1,8 @@ # czicompress [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![REUSE status](https://api.reuse.software/badge/github.com/ZEISS/czicompress)](https://api.reuse.software/info/github.com/ZEISS/czicompress) -[![CMake](https://github.com/ZEISS/czicompress/actions/workflows/cmake.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/cmake.yml) -[![CodeQL](https://github.com/ZEISS/czicompress/actions/workflows/codeql.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/codeql.yml) +[![CMake](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_cmake.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_cmake.yml) +[![CodeQL](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_codeql.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_codeql.yml) [![MegaLinter](https://github.com/ZEISS/czicompress/actions/workflows/mega-linter.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/mega-linter.yml) Reduce the size of existing CZI files by converting them to zstd-compressed CZI files. @@ -271,7 +271,6 @@ After the build, run the tests by executing `./build/tests/czicompress_tests`. When compiling in Visual Studio for the first time, you may need to double compile. The Visual Studio is using "Ninja" for building and for some reason it is reporting a linker error `LINK: Fatal error LNK1168: cannot open app\czicompress.exe for writing` despite the fact that the `czicompress.exe` is created. - ## Guidelines [Code of Conduct](./CODE_OF_CONDUCT.md) [Contributing](./CONTRIBUTING.md) From 0d31a94d07a7de3887bd6e31dbf15dca3f2e243e Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 13:49:07 +0100 Subject: [PATCH 06/31] Add CziShrink to repo --- .github/dependabot.yml | 11 + .github/workflows/czicompress_codeql.yml | 61 + .github/workflows/czishrink_codeql.yml | 44 + .github/workflows/czishrink_dotnet.yml | 126 ++ .jsonlintrc.yml | 5 + .reuse/dep5 | 8 + LICENSES/GPL-3.0-or-later.txt | 232 +++ czishrink/.gitattributes | 2 + czishrink/.gitignore | 457 +++++ czishrink/.images/.gitattributes | 2 + czishrink/.images/CompressionStatistics.gif | 3 + .../.images/CompressionStatistics.gif.license | 3 + czishrink/.images/CompressionStatistics.png | 3 + .../.images/CompressionStatistics.png.license | 3 + czishrink/.images/CziShrink_DarkTheme.png | 3 + .../.images/CziShrink_DarkTheme.png.license | 3 + czishrink/.images/CziShrink_LightTheme.png | 3 + .../.images/CziShrink_LightTheme.png.license | 3 + czishrink/.images/ErrorFileOptions.png | 3 + .../.images/ErrorFileOptions.png.license | 3 + czishrink/.images/Progress.png | 3 + czishrink/.images/Progress.png.license | 3 + czishrink/.images/ProgressAnimation.gif | 3 + .../.images/ProgressAnimation.gif.license | 3 + czishrink/.images/ShareBadgeExample.png | 3 + .../.images/ShareBadgeExample.png.license | 3 + czishrink/.vscode/launch.json | 26 + czishrink/.vscode/settings.json | 3 + czishrink/.vscode/tasks.json | 70 + czishrink/CodeAnalysis.ruleset | 246 +++ czishrink/Directory.Build.props | 16 + czishrink/Directory.Build.targets | 11 + czishrink/Directory.Packages.props | 40 + czishrink/LICENSE.txt | 674 ++++++++ czishrink/NuGet.config | 6 + czishrink/README.md | 152 ++ czishrink/README.pdf | Bin 0 -> 131 bytes ...D_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt | 1524 +++++++++++++++++ czishrink/icon-source/convert-to-ico.sh | 2 + czishrink/icon-source/netczicompress.svg | 241 +++ czishrink/libczicompressc/LICENSE.txt | 21 + .../libczicompressc/THIRD_PARTY_LICENSES.txt | 128 ++ ...D_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt | 1524 +++++++++++++++++ .../libczicompressc/libczicompressc.nuspec | 16 + .../linux-x64/native/libczicompressc.so | 0 .../win-x64/native/libczicompressc.dll | 0 czishrink/netczicompress.Desktop/Program.cs | 75 + czishrink/netczicompress.Desktop/app.manifest | 25 + .../netczicompress.Desktop.csproj | 27 + czishrink/netczicompress.sln | 37 + czishrink/netczicompress/App.axaml | 28 + czishrink/netczicompress/App.axaml.cs | 34 + czishrink/netczicompress/AppComposer.cs | 99 ++ czishrink/netczicompress/Assets/Logo.png | Bin 0 -> 15845 bytes .../netczicompress/Assets/netczicompress.ico | Bin 0 -> 127985 bytes czishrink/netczicompress/FodyWeavers.xml | 3 + .../Models/AggregateStatistics.cs | 29 + ...syncEnumerableToObservableActionAdapter.cs | 70 + .../Models/Clipboard/ClipboardHelper.cs | 32 + .../Models/Clipboard/ClipboardHelperLinux.cs | 59 + .../Models/Clipboard/ClipboardHelperWin32.cs | 331 ++++ .../Models/Clipboard/IClipboardHelper.cs | 24 + .../Models/CompositeObserver.cs | 55 + .../netczicompress/Models/CompressionLevel.cs | 52 + .../netczicompress/Models/CompressionMode.cs | 17 + .../Models/CompressorMessage.cs | 42 + .../netczicompress/Models/CreateProcessor.cs | 7 + .../netczicompress/Models/CsvLogFileWriter.cs | 88 + .../Models/CsvLoggingStrategy.cs | 56 + .../netczicompress/Models/ExecutionOptions.cs | 11 + .../netczicompress/Models/FileLauncher.cs | 144 ++ .../Models/FileProcessingFailedHandler.cs | 113 ++ .../Models/FolderCompressorDecorator.cs | 35 + .../Models/FolderCompressorExtensions.cs | 37 + .../Models/FolderCompressorParameters.cs | 24 + .../netczicompress/Models/IFileLauncher.cs | 25 + .../netczicompress/Models/IFileProcessor.cs | 29 + .../Models/IFolderCompressor.cs | 24 + .../Models/IFolderCompressorRunObserver.cs | 17 + .../netczicompress/Models/ILoggingStrategy.cs | 24 + .../Models/IObservableAction.cs | 31 + .../Models/IProgramNameAndVersion.cs | 15 + .../Models/MultiThreadedFolderCompressor.cs | 267 +++ .../Models/NoOpFileProcessor.cs | 24 + .../Models/ObservableMixin.CompleteOnError.cs | 46 + .../Models/PInvokeFileProcessor.cs | 222 +++ .../Models/ProcessingOptions.cs | 12 + .../Models/ProgramNameAndVersion.cs | 35 + .../netczicompress/Models/ReportProgress.cs | 11 + .../Models/StatisticsRunObserver.cs | 48 + .../netczicompress/Models/TemporaryFile.cs | 148 ++ .../netczicompress/Models/ThreadCount.cs | 46 + .../Models/TraceLoggerFactory.cs | 118 ++ .../Models/VariousExtensions.cs | 57 + czishrink/netczicompress/Styles/Border.axaml | 25 + .../netczicompress/Styles/Button_Unused.axaml | 51 + .../netczicompress/Styles/ComboBox.axaml | 27 + .../netczicompress/Styles/DataGrid.axaml | 50 + .../netczicompress/Styles/GridSplitter.axaml | 13 + .../netczicompress/Styles/ProgressBar.axaml | 16 + czishrink/netczicompress/Styles/TextBox.axaml | 27 + .../Styles/Themes/ControlThemeButton.axaml | 105 ++ .../Themes/ControlThemeProgressBar.axaml | 197 +++ .../Styles/Themes/DarkThemeBrushes.axaml | 54 + .../Styles/Themes/LightThemeBrushes.axaml | 54 + .../netczicompress/Styles/ToggleSwitch.axaml | 13 + czishrink/netczicompress/Usings.cs | 6 + .../ViewModels/AboutViewModel.cs | 49 + .../ViewModels/AggregateIndicationStatus.cs | 13 + .../AggregateIndicationViewModel.cs | 39 + .../AggregateStatisticsViewModel.cs | 122 ++ .../ViewModels/CompressionTaskViewModel.cs | 64 + .../AggregateStatusToAnimationConverter.cs | 26 + .../AggregateStatusToForegroundConverter.cs | 31 + .../AggregateStatusToIconConverter.cs | 24 + .../AggregateStatusToTextConverter.cs | 28 + .../Converters/BytesToStringConverter.cs | 46 + .../CompressionLevelToDecimalConverter.cs | 30 + .../FloatToCompressionRatioStringConverter.cs | 36 + .../Converters/ForwardOnlyConverter.cs | 23 + .../ThreadCountToDecimalConverter.cs | 30 + .../ViewModels/CurrentTasksViewModel.cs | 54 + .../netczicompress/ViewModels/ErrorItem.cs | 20 + .../ViewModels/ErrorListViewModel.cs | 189 ++ .../ViewModels/IAboutViewModel.cs | 21 + .../ViewModels/IAdvancedOptionsViewModel.cs | 28 + .../IAggregateIndicationViewModel.cs | 15 + .../IAggregateStatisticsViewModel.cs | 41 + .../ViewModels/ICompressionTaskViewModel.cs | 19 + .../ViewModels/ICurrentTasksViewModel.cs | 16 + .../ViewModels/IErrorListViewModel.cs | 23 + .../ViewModels/IExecutionControlViewModel.cs | 17 + .../ViewModels/IFolderInputOutputViewModel.cs | 15 + .../ViewModels/ILogFileViewModel.cs | 23 + .../ViewModels/ISettingsViewModel.cs | 28 + .../ViewModels/ImmutableViewModeBase.cs | 19 + .../ViewModels/LogFileViewModel.cs | 79 + .../ViewModels/MainViewModel.cs | 242 +++ .../ViewModels/OperationMode.cs | 15 + .../ViewModels/ViewModelBase.cs | 14 + .../netczicompress/Views/AboutView.axaml | 41 + .../netczicompress/Views/AboutView.axaml.cs | 15 + .../Views/AdvancedOptionsView.axaml | 72 + .../Views/AdvancedOptionsView.axaml.cs | 12 + .../Views/AggregateIndicationView.axaml | 62 + .../Views/AggregateIndicationView.axaml.cs | 11 + .../Views/AggregateStatisticsView.axaml | 206 +++ .../Views/AggregateStatisticsView.axaml.cs | 45 + .../Views/CompressionResultBadge.axaml | 192 +++ .../Views/CompressionResultBadge.axaml.cs | 12 + .../Views/CurrentTasksView.axaml | 58 + .../Views/CurrentTasksView.axaml.cs | 15 + .../netczicompress/Views/ErrorListView.axaml | 68 + .../Views/ErrorListView.axaml.cs | 66 + .../Views/FolderInputOutputView.axaml | 46 + .../Views/FolderInputOutputView.axaml.cs | 15 + .../netczicompress/Views/FolderPicker.axaml | 27 + .../Views/FolderPicker.axaml.cs | 85 + czishrink/netczicompress/Views/MainView.axaml | 149 ++ .../netczicompress/Views/MainView.axaml.cs | 15 + .../netczicompress/Views/MainWindow.axaml | 35 + .../netczicompress/Views/MainWindow.axaml.cs | 147 ++ .../Views/NotificationEventArgs.cs | 23 + .../netczicompress/Views/SettingsView.axaml | 85 + .../Views/SettingsView.axaml.cs | 15 + .../Views/StartStopBarView.axaml | 26 + .../Views/StartStopBarView.axaml.cs | 15 + .../netczicompress/netczicompress.csproj | 32 + .../netczicompressTests/AppComposerTests.cs | 50 + .../CompressionLevelSpecimenBuilder.cs | 27 + .../CorrectValueObjectAutoDataAttribute.cs | 19 + .../ThreadCountSpecimenBuilder.cs | 27 + .../Customizations/ValidInlineAutoData.cs | 19 + ...cludePropertiesByAttributeSelectionRule.cs | 41 + .../FluentAssertionsConfig.cs | 22 + .../Models/AggregateStatisticsTests.cs | 56 + ...numerableToObservableActionAdapterTests.cs | 131 ++ .../Models/CompositeObserverTests.cs | 70 + .../Models/CompressionLevelTests.cs | 43 + .../Models/CompressorMessageTests.cs | 47 + .../Models/CsvLogFileWriterTests.cs | 103 ++ .../Models/CsvLoggingStrategyTests.cs | 152 ++ .../Models/FileLauncherTests.cs | 279 +++ .../FileProcessingFailedHandlerTests.cs | 88 + .../Models/FolderCompressorDecoratorTests.cs | 52 + .../Models/FolderCompressorExtensionsTests.cs | 98 ++ .../Mocks/FileProcessorMockExtensions.cs | 52 + .../Mocks/FolderCompressorMockExtensions.cs | 53 + .../FolderCompressorRunMockExtensions.cs | 62 + .../Models/Mocks/ObservableExtensions.cs | 29 + .../Models/Mocks/ProgressRecorder.cs | 40 + .../Models/Mocks/Recorder{T}.cs | 34 + .../Models/Mocks/SchedulerMock.cs | 42 + .../MultiThreadedFolderCompressorTests.cs | 622 +++++++ .../Models/NoOpFileProcessorTests.cs | 39 + .../Models/PInvokeFileProcessorTests.cs | 164 ++ .../Models/ProgramNameAndVersionTests.cs | 45 + .../Models/StatisticsRunObserverTests.cs | 109 ++ .../Models/TemporaryFileTests.cs | 239 +++ .../Models/ThreadCountTests.cs | 40 + .../Models/TraceLoggerFactoryTests.cs | 182 ++ czishrink/netczicompressTests/Usings.cs | 22 + .../ViewModels/AboutViewModelTests.cs | 114 ++ .../AggregateIndicationViewModelTests.cs | 106 ++ .../AggregateStatisticsViewModelTests.cs | 102 ++ .../CompressionTaskViewModelTests.cs | 81 + ...ggregateStatusToAnimationConverterTests.cs | 39 + ...gregateStatusToForegroundConverterTests.cs | 60 + .../AggregateStatusToIconConverterTests.cs | 29 + .../AggregateStatusToTextConverterTests.cs | 40 + .../Converters/BytesToStringConverterTests.cs | 57 + ...tToCompressionRatioStringConverterTests.cs | 47 + .../ViewModels/CurrentTasksViewModelTests.cs | 126 ++ .../ViewModels/ErrorItemTests.cs | 67 + .../ViewModels/ErrorListViewModelTests.cs | 445 +++++ .../ViewModels/LogFileViewModelTests.cs | 367 ++++ .../MainViewModelTests.ChildViewModels.cs | 37 + .../ViewModels/MainViewModelTests.Modes.cs | 104 ++ .../MainViewModelTests.OverallStatus.cs | 313 ++++ .../MainViewModelTests.PropertyChanged.cs | 79 + .../MainViewModelTests.StartCommand.cs | 232 +++ .../MainViewModelTests.StopCommand.cs | 96 ++ czishrink/netczicompressTests/mandelbrot.czi | Bin 0 -> 101600 bytes .../netczicompressTests.csproj | 48 + .../libczicompressc.0.5.1-alpha.0.nupkg | 3 + czishrink/stylecop.json | 12 + czishrink/upgrade-libczicompressc.ps1 | 363 ++++ 227 files changed, 18181 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/czicompress_codeql.yml create mode 100644 .github/workflows/czishrink_codeql.yml create mode 100644 .github/workflows/czishrink_dotnet.yml create mode 100644 .jsonlintrc.yml create mode 100644 LICENSES/GPL-3.0-or-later.txt create mode 100644 czishrink/.gitattributes create mode 100644 czishrink/.gitignore create mode 100644 czishrink/.images/.gitattributes create mode 100644 czishrink/.images/CompressionStatistics.gif create mode 100644 czishrink/.images/CompressionStatistics.gif.license create mode 100644 czishrink/.images/CompressionStatistics.png create mode 100644 czishrink/.images/CompressionStatistics.png.license create mode 100644 czishrink/.images/CziShrink_DarkTheme.png create mode 100644 czishrink/.images/CziShrink_DarkTheme.png.license create mode 100644 czishrink/.images/CziShrink_LightTheme.png create mode 100644 czishrink/.images/CziShrink_LightTheme.png.license create mode 100644 czishrink/.images/ErrorFileOptions.png create mode 100644 czishrink/.images/ErrorFileOptions.png.license create mode 100644 czishrink/.images/Progress.png create mode 100644 czishrink/.images/Progress.png.license create mode 100644 czishrink/.images/ProgressAnimation.gif create mode 100644 czishrink/.images/ProgressAnimation.gif.license create mode 100644 czishrink/.images/ShareBadgeExample.png create mode 100644 czishrink/.images/ShareBadgeExample.png.license create mode 100644 czishrink/.vscode/launch.json create mode 100644 czishrink/.vscode/settings.json create mode 100644 czishrink/.vscode/tasks.json create mode 100644 czishrink/CodeAnalysis.ruleset create mode 100644 czishrink/Directory.Build.props create mode 100644 czishrink/Directory.Build.targets create mode 100644 czishrink/Directory.Packages.props create mode 100644 czishrink/LICENSE.txt create mode 100644 czishrink/NuGet.config create mode 100644 czishrink/README.md create mode 100644 czishrink/README.pdf create mode 100644 czishrink/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt create mode 100644 czishrink/icon-source/convert-to-ico.sh create mode 100644 czishrink/icon-source/netczicompress.svg create mode 100644 czishrink/libczicompressc/LICENSE.txt create mode 100644 czishrink/libczicompressc/THIRD_PARTY_LICENSES.txt create mode 100644 czishrink/libczicompressc/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt create mode 100644 czishrink/libczicompressc/libczicompressc.nuspec create mode 100644 czishrink/libczicompressc/runtimes/linux-x64/native/libczicompressc.so create mode 100644 czishrink/libczicompressc/runtimes/win-x64/native/libczicompressc.dll create mode 100644 czishrink/netczicompress.Desktop/Program.cs create mode 100644 czishrink/netczicompress.Desktop/app.manifest create mode 100644 czishrink/netczicompress.Desktop/netczicompress.Desktop.csproj create mode 100644 czishrink/netczicompress.sln create mode 100644 czishrink/netczicompress/App.axaml create mode 100644 czishrink/netczicompress/App.axaml.cs create mode 100644 czishrink/netczicompress/AppComposer.cs create mode 100644 czishrink/netczicompress/Assets/Logo.png create mode 100644 czishrink/netczicompress/Assets/netczicompress.ico create mode 100644 czishrink/netczicompress/FodyWeavers.xml create mode 100644 czishrink/netczicompress/Models/AggregateStatistics.cs create mode 100644 czishrink/netczicompress/Models/AsyncEnumerableToObservableActionAdapter.cs create mode 100644 czishrink/netczicompress/Models/Clipboard/ClipboardHelper.cs create mode 100644 czishrink/netczicompress/Models/Clipboard/ClipboardHelperLinux.cs create mode 100644 czishrink/netczicompress/Models/Clipboard/ClipboardHelperWin32.cs create mode 100644 czishrink/netczicompress/Models/Clipboard/IClipboardHelper.cs create mode 100644 czishrink/netczicompress/Models/CompositeObserver.cs create mode 100644 czishrink/netczicompress/Models/CompressionLevel.cs create mode 100644 czishrink/netczicompress/Models/CompressionMode.cs create mode 100644 czishrink/netczicompress/Models/CompressorMessage.cs create mode 100644 czishrink/netczicompress/Models/CreateProcessor.cs create mode 100644 czishrink/netczicompress/Models/CsvLogFileWriter.cs create mode 100644 czishrink/netczicompress/Models/CsvLoggingStrategy.cs create mode 100644 czishrink/netczicompress/Models/ExecutionOptions.cs create mode 100644 czishrink/netczicompress/Models/FileLauncher.cs create mode 100644 czishrink/netczicompress/Models/FileProcessingFailedHandler.cs create mode 100644 czishrink/netczicompress/Models/FolderCompressorDecorator.cs create mode 100644 czishrink/netczicompress/Models/FolderCompressorExtensions.cs create mode 100644 czishrink/netczicompress/Models/FolderCompressorParameters.cs create mode 100644 czishrink/netczicompress/Models/IFileLauncher.cs create mode 100644 czishrink/netczicompress/Models/IFileProcessor.cs create mode 100644 czishrink/netczicompress/Models/IFolderCompressor.cs create mode 100644 czishrink/netczicompress/Models/IFolderCompressorRunObserver.cs create mode 100644 czishrink/netczicompress/Models/ILoggingStrategy.cs create mode 100644 czishrink/netczicompress/Models/IObservableAction.cs create mode 100644 czishrink/netczicompress/Models/IProgramNameAndVersion.cs create mode 100644 czishrink/netczicompress/Models/MultiThreadedFolderCompressor.cs create mode 100644 czishrink/netczicompress/Models/NoOpFileProcessor.cs create mode 100644 czishrink/netczicompress/Models/ObservableMixin.CompleteOnError.cs create mode 100644 czishrink/netczicompress/Models/PInvokeFileProcessor.cs create mode 100644 czishrink/netczicompress/Models/ProcessingOptions.cs create mode 100644 czishrink/netczicompress/Models/ProgramNameAndVersion.cs create mode 100644 czishrink/netczicompress/Models/ReportProgress.cs create mode 100644 czishrink/netczicompress/Models/StatisticsRunObserver.cs create mode 100644 czishrink/netczicompress/Models/TemporaryFile.cs create mode 100644 czishrink/netczicompress/Models/ThreadCount.cs create mode 100644 czishrink/netczicompress/Models/TraceLoggerFactory.cs create mode 100644 czishrink/netczicompress/Models/VariousExtensions.cs create mode 100644 czishrink/netczicompress/Styles/Border.axaml create mode 100644 czishrink/netczicompress/Styles/Button_Unused.axaml create mode 100644 czishrink/netczicompress/Styles/ComboBox.axaml create mode 100644 czishrink/netczicompress/Styles/DataGrid.axaml create mode 100644 czishrink/netczicompress/Styles/GridSplitter.axaml create mode 100644 czishrink/netczicompress/Styles/ProgressBar.axaml create mode 100644 czishrink/netczicompress/Styles/TextBox.axaml create mode 100644 czishrink/netczicompress/Styles/Themes/ControlThemeButton.axaml create mode 100644 czishrink/netczicompress/Styles/Themes/ControlThemeProgressBar.axaml create mode 100644 czishrink/netczicompress/Styles/Themes/DarkThemeBrushes.axaml create mode 100644 czishrink/netczicompress/Styles/Themes/LightThemeBrushes.axaml create mode 100644 czishrink/netczicompress/Styles/ToggleSwitch.axaml create mode 100644 czishrink/netczicompress/Usings.cs create mode 100644 czishrink/netczicompress/ViewModels/AboutViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/AggregateIndicationStatus.cs create mode 100644 czishrink/netczicompress/ViewModels/AggregateIndicationViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/AggregateStatisticsViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/CompressionTaskViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/Converters/AggregateStatusToAnimationConverter.cs create mode 100644 czishrink/netczicompress/ViewModels/Converters/AggregateStatusToForegroundConverter.cs create mode 100644 czishrink/netczicompress/ViewModels/Converters/AggregateStatusToIconConverter.cs create mode 100644 czishrink/netczicompress/ViewModels/Converters/AggregateStatusToTextConverter.cs create mode 100644 czishrink/netczicompress/ViewModels/Converters/BytesToStringConverter.cs create mode 100644 czishrink/netczicompress/ViewModels/Converters/CompressionLevelToDecimalConverter.cs create mode 100644 czishrink/netczicompress/ViewModels/Converters/FloatToCompressionRatioStringConverter.cs create mode 100644 czishrink/netczicompress/ViewModels/Converters/ForwardOnlyConverter.cs create mode 100644 czishrink/netczicompress/ViewModels/Converters/ThreadCountToDecimalConverter.cs create mode 100644 czishrink/netczicompress/ViewModels/CurrentTasksViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/ErrorItem.cs create mode 100644 czishrink/netczicompress/ViewModels/ErrorListViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/IAboutViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/IAdvancedOptionsViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/IAggregateIndicationViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/IAggregateStatisticsViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/ICompressionTaskViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/ICurrentTasksViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/IErrorListViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/IExecutionControlViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/IFolderInputOutputViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/ILogFileViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/ISettingsViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/ImmutableViewModeBase.cs create mode 100644 czishrink/netczicompress/ViewModels/LogFileViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/MainViewModel.cs create mode 100644 czishrink/netczicompress/ViewModels/OperationMode.cs create mode 100644 czishrink/netczicompress/ViewModels/ViewModelBase.cs create mode 100644 czishrink/netczicompress/Views/AboutView.axaml create mode 100644 czishrink/netczicompress/Views/AboutView.axaml.cs create mode 100644 czishrink/netczicompress/Views/AdvancedOptionsView.axaml create mode 100644 czishrink/netczicompress/Views/AdvancedOptionsView.axaml.cs create mode 100644 czishrink/netczicompress/Views/AggregateIndicationView.axaml create mode 100644 czishrink/netczicompress/Views/AggregateIndicationView.axaml.cs create mode 100644 czishrink/netczicompress/Views/AggregateStatisticsView.axaml create mode 100644 czishrink/netczicompress/Views/AggregateStatisticsView.axaml.cs create mode 100644 czishrink/netczicompress/Views/CompressionResultBadge.axaml create mode 100644 czishrink/netczicompress/Views/CompressionResultBadge.axaml.cs create mode 100644 czishrink/netczicompress/Views/CurrentTasksView.axaml create mode 100644 czishrink/netczicompress/Views/CurrentTasksView.axaml.cs create mode 100644 czishrink/netczicompress/Views/ErrorListView.axaml create mode 100644 czishrink/netczicompress/Views/ErrorListView.axaml.cs create mode 100644 czishrink/netczicompress/Views/FolderInputOutputView.axaml create mode 100644 czishrink/netczicompress/Views/FolderInputOutputView.axaml.cs create mode 100644 czishrink/netczicompress/Views/FolderPicker.axaml create mode 100644 czishrink/netczicompress/Views/FolderPicker.axaml.cs create mode 100644 czishrink/netczicompress/Views/MainView.axaml create mode 100644 czishrink/netczicompress/Views/MainView.axaml.cs create mode 100644 czishrink/netczicompress/Views/MainWindow.axaml create mode 100644 czishrink/netczicompress/Views/MainWindow.axaml.cs create mode 100644 czishrink/netczicompress/Views/NotificationEventArgs.cs create mode 100644 czishrink/netczicompress/Views/SettingsView.axaml create mode 100644 czishrink/netczicompress/Views/SettingsView.axaml.cs create mode 100644 czishrink/netczicompress/Views/StartStopBarView.axaml create mode 100644 czishrink/netczicompress/Views/StartStopBarView.axaml.cs create mode 100644 czishrink/netczicompress/netczicompress.csproj create mode 100644 czishrink/netczicompressTests/AppComposerTests.cs create mode 100644 czishrink/netczicompressTests/Customizations/CompressionLevelSpecimenBuilder.cs create mode 100644 czishrink/netczicompressTests/Customizations/CorrectValueObjectAutoDataAttribute.cs create mode 100644 czishrink/netczicompressTests/Customizations/ThreadCountSpecimenBuilder.cs create mode 100644 czishrink/netczicompressTests/Customizations/ValidInlineAutoData.cs create mode 100644 czishrink/netczicompressTests/ExcludePropertiesByAttributeSelectionRule.cs create mode 100644 czishrink/netczicompressTests/FluentAssertionsConfig.cs create mode 100644 czishrink/netczicompressTests/Models/AggregateStatisticsTests.cs create mode 100644 czishrink/netczicompressTests/Models/AsyncEnumerableToObservableActionAdapterTests.cs create mode 100644 czishrink/netczicompressTests/Models/CompositeObserverTests.cs create mode 100644 czishrink/netczicompressTests/Models/CompressionLevelTests.cs create mode 100644 czishrink/netczicompressTests/Models/CompressorMessageTests.cs create mode 100644 czishrink/netczicompressTests/Models/CsvLogFileWriterTests.cs create mode 100644 czishrink/netczicompressTests/Models/CsvLoggingStrategyTests.cs create mode 100644 czishrink/netczicompressTests/Models/FileLauncherTests.cs create mode 100644 czishrink/netczicompressTests/Models/FileProcessingFailedHandlerTests.cs create mode 100644 czishrink/netczicompressTests/Models/FolderCompressorDecoratorTests.cs create mode 100644 czishrink/netczicompressTests/Models/FolderCompressorExtensionsTests.cs create mode 100644 czishrink/netczicompressTests/Models/Mocks/FileProcessorMockExtensions.cs create mode 100644 czishrink/netczicompressTests/Models/Mocks/FolderCompressorMockExtensions.cs create mode 100644 czishrink/netczicompressTests/Models/Mocks/FolderCompressorRunMockExtensions.cs create mode 100644 czishrink/netczicompressTests/Models/Mocks/ObservableExtensions.cs create mode 100644 czishrink/netczicompressTests/Models/Mocks/ProgressRecorder.cs create mode 100644 czishrink/netczicompressTests/Models/Mocks/Recorder{T}.cs create mode 100644 czishrink/netczicompressTests/Models/Mocks/SchedulerMock.cs create mode 100644 czishrink/netczicompressTests/Models/MultiThreadedFolderCompressorTests.cs create mode 100644 czishrink/netczicompressTests/Models/NoOpFileProcessorTests.cs create mode 100644 czishrink/netczicompressTests/Models/PInvokeFileProcessorTests.cs create mode 100644 czishrink/netczicompressTests/Models/ProgramNameAndVersionTests.cs create mode 100644 czishrink/netczicompressTests/Models/StatisticsRunObserverTests.cs create mode 100644 czishrink/netczicompressTests/Models/TemporaryFileTests.cs create mode 100644 czishrink/netczicompressTests/Models/ThreadCountTests.cs create mode 100644 czishrink/netczicompressTests/Models/TraceLoggerFactoryTests.cs create mode 100644 czishrink/netczicompressTests/Usings.cs create mode 100644 czishrink/netczicompressTests/ViewModels/AboutViewModelTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/AggregateIndicationViewModelTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/AggregateStatisticsViewModelTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/CompressionTaskViewModelTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToAnimationConverterTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToForegroundConverterTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToIconConverterTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToTextConverterTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/Converters/BytesToStringConverterTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/Converters/FloatToCompressionRatioStringConverterTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/CurrentTasksViewModelTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/ErrorItemTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/ErrorListViewModelTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/LogFileViewModelTests.cs create mode 100644 czishrink/netczicompressTests/ViewModels/MainViewModelTests.ChildViewModels.cs create mode 100644 czishrink/netczicompressTests/ViewModels/MainViewModelTests.Modes.cs create mode 100644 czishrink/netczicompressTests/ViewModels/MainViewModelTests.OverallStatus.cs create mode 100644 czishrink/netczicompressTests/ViewModels/MainViewModelTests.PropertyChanged.cs create mode 100644 czishrink/netczicompressTests/ViewModels/MainViewModelTests.StartCommand.cs create mode 100644 czishrink/netczicompressTests/ViewModels/MainViewModelTests.StopCommand.cs create mode 100644 czishrink/netczicompressTests/mandelbrot.czi create mode 100644 czishrink/netczicompressTests/netczicompressTests.csproj create mode 100644 czishrink/packages_local/libczicompressc.0.5.1-alpha.0.nupkg create mode 100644 czishrink/stylecop.json create mode 100644 czishrink/upgrade-libczicompressc.ps1 diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1fb8a36 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# yamllint disable rule:document-start +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" + directory: "/czishrink" + schedule: + interval: "weekly" + day: "wednesday" diff --git a/.github/workflows/czicompress_codeql.yml b/.github/workflows/czicompress_codeql.yml new file mode 100644 index 0000000..6dceeb9 --- /dev/null +++ b/.github/workflows/czicompress_codeql.yml @@ -0,0 +1,61 @@ +--- +name: "CodeQL (czicompress)" + +on: + push: + branches: ["main"] + pull_request: + # The branches below must be a subset of the branches above + branches: ["main"] + schedule: + - cron: "22 16 * * 4" + +permissions: + actions: read + contents: read + security-events: write + +jobs: + analyze: + name: Analyze CPP + defaults: + run: + working-directory: ${{github.workspace}}/czicompress + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: ["cpp"] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - uses: ./.github/actions/cmake-build + with: + path-cache: '/usr/local/share/vcpkg/installed' + path-toolchain: '/usr/local/share/vcpkg/scripts/buildsystems/vcpkg.cmake' + os-id: 'linux' + build-type: Release + package: ON + platform: x64-linux + src-dir: '${{github.workspace}}/czicompress' + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/czishrink_codeql.yml b/.github/workflows/czishrink_codeql.yml new file mode 100644 index 0000000..1004c0f --- /dev/null +++ b/.github/workflows/czishrink_codeql.yml @@ -0,0 +1,44 @@ +--- +name: "CodeQL" + +on: + # push: + # branches: [main] + # paths: ["czishrink/**", ".github/workflows/czishrink_codeql.yml"] + # pull_request: + # branches: [main] + # paths: ["czishrink/**", ".github/workflows/czishrink_codeql.yml"] + workflow_dispatch: {} + +permissions: read-all + +jobs: + analyze: + name: Analyze CziShrink + defaults: + run: + working-directory: ${{github.workspace}}/czishrink + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: ['csharp'] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + queries: security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + with: + working-directory: czishrink + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/czishrink_dotnet.yml b/.github/workflows/czishrink_dotnet.yml new file mode 100644 index 0000000..bc3b88d --- /dev/null +++ b/.github/workflows/czishrink_dotnet.yml @@ -0,0 +1,126 @@ +--- +# 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 +permissions: + pull-requests: write + contents: read + +name: .NET + +on: + push: + branches: ["main"] + paths: ["czishrink/**"] + pull_request: + branches: ["main"] + paths: ["czishrink/**"] + workflow_dispatch: {} + +jobs: + build: + defaults: + run: + working-directory: czishrink + name: ${{matrix.config.name}} + runs-on: ${{matrix.config.os}} + + strategy: + fail-fast: false + matrix: + config: + - { + name: windows, + os: windows-latest, + osfamily: win, + } + - { + name: ubuntu, + os: ubuntu-latest, + osfamily: linux, + } + + steps: + - uses: actions/checkout@v3 + with: + lfs: true + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 7.0.x + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore -c Release + + - name: Test + run: > + dotnet test + -c Release + --no-build + --verbosity normal + --logger trx + --results-directory "TestResults" + -p:CollectCoverage=true + -p:CoverletOutputFormat=cobertura + -p:CoverletOutput=${{ github.workspace }}/TestResults/coverage.cobertura.xml + -p:ExcludeByAttribute=GeneratedCodeAttribute%2cObsoleteAttribute + -p:ExcludeByFile=**/*.axaml%2c**/*.g.cs + -p:Exclude='[netczicompress]netczicompress.Views.*' + + - name: Upload dotnet test results + uses: actions/upload-artifact@v3 + with: + name: dotnet-results-${{ matrix.config.name }} + path: TestResults + # Use always() to also publish test results when there are test failures + if: ${{ always() }} + + - name: Code Coverage Report + uses: irongut/CodeCoverageSummary@v1.3.0 + if: matrix.config.name == 'ubuntu' + with: + filename: TestResults/coverage.cobertura.xml + badge: true + fail_below_min: false + format: markdown + hide_branch_rate: false + hide_complexity: false + indicators: true + output: both + thresholds: '60 80' + + - name: Add Coverage PR Comment + uses: marocchino/sticky-pull-request-comment@v2 + if: github.event_name == 'pull_request' && matrix.config.name == 'ubuntu' + with: + recreate: true + path: code-coverage-results.md + + - name: Publish + if: github.event_name == 'push' + run: > + dotnet publish netczicompress.Desktop/netczicompress.Desktop.csproj + -c Release + -a x64 + --self-contained + -p:PublishSingleFile=true + -p:PublishReadyToRun=true + -p:PublishReadyToRunShowWarnings=true + -o ${{ github.workspace }}/publish + + - name: Get Version from Directory.Build.props + id: getversion + uses: m-ringler/get-xpath@v3 + with: + xml-file: czishrink/Directory.Build.props + xpath: 'concat(//VersionPrefix/text(), "-", //VersionSuffix/text())' + + - name: Upload published binaries + uses: actions/upload-artifact@v3 + if: github.event_name == 'push' + with: + name: CziShrink_${{ steps.getversion.outputs.info }}_${{ matrix.config.osfamily}}-x64 + path: publish diff --git a/.jsonlintrc.yml b/.jsonlintrc.yml new file mode 100644 index 0000000..0ad9eb6 --- /dev/null +++ b/.jsonlintrc.yml @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +# +# SPDX-License-Identifier: MIT +--- +comments: true diff --git a/.reuse/dep5 b/.reuse/dep5 index 1b16637..9c3ee9d 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -10,3 +10,11 @@ License: MIT Files: .github/* .gitignore czicompress/.editorconfig czicompress/.gitignore czicompress/.runsettings czicompress/CPPLINT.cfg czicompress/.clang-format Copyright: 2023 Carl Zeiss Microscopy GmbH License: CC0-1.0 + +Files: czishrink/*.axaml czishrink/*.json czishrink/*.nuspec czishrink/*.txt czishrink/*.ico czishrink/*.png czishrink/*.props czishrink/*.targets czishrink/*.sh czishrink/*.svg czishrink/*.csproj czishrink/*.gitattributes czishrink/*.nupkg czishrink/*.md czishrink/*.pdf czishrink/*.config czishrink/*.ruleset czishrink/*.ps1 czishrink/*.czi czishrink/*.xml czishrink/*.sln czishrink/*.manifest README.md +Copyright: 2023 Carl Zeiss Microscopy GmbH +License: GPL-3.0-or-later + +Files: czishrink/*/mandelbrot.czi +Copyright: 2023 Carl Zeiss Microscopy GmbH +License: CC0-1.0 \ No newline at end of file diff --git a/LICENSES/GPL-3.0-or-later.txt b/LICENSES/GPL-3.0-or-later.txt new file mode 100644 index 0000000..f6cdd22 --- /dev/null +++ b/LICENSES/GPL-3.0-or-later.txt @@ -0,0 +1,232 @@ +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for software and other kinds of works. + +The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS + +0. Definitions. + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. “Licensees” and “recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work or a work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on the Program. + +To “propagate” a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. + +1. Source Code. +The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. + +A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. + +The “System Libraries” of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A “Major Component”, in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +2. Basic Permissions. +All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +3. Protecting Users' Legal Rights From Anti-Circumvention Law. +No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. + +4. Conveying Verbatim Copies. +You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. + +5. Conveying Modified Source Versions. +You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to “keep intact all notices”. + + c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. + +6. Conveying Non-Source Forms. +You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: + + a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. + + d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. + +A “User Product” is either (1) a “consumer product”, which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, “normally used” refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. + +7. Additional Terms. +“Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or authors of the material; or + + e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further restrictions” within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. + +8. Termination. +You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. + +9. Acceptance Not Required for Having Copies. +You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. + +10. Automatic Licensing of Downstream Recipients. +Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. + +An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. + +11. Patents. +A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. + +In the following three paragraphs, a “patent license” is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To “grant” such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. “Knowingly relying” means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. + +A patent license is “discriminatory” if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. + +12. No Surrender of Others' Freedom. +If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. + +13. Use with the GNU Affero General Public License. +Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. + +14. Revised Versions of this License. +The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. + +15. Disclaimer of Warranty. +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. Limitation of Liability. +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +17. Interpretation of Sections 15 and 16. +If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to sign a “copyright disclaimer” for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . + +The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . diff --git a/czishrink/.gitattributes b/czishrink/.gitattributes new file mode 100644 index 0000000..9f77107 --- /dev/null +++ b/czishrink/.gitattributes @@ -0,0 +1,2 @@ +*.nupkg filter=lfs diff=lfs merge=lfs -text +*.pdf filter=lfs diff=lfs merge=lfs -text diff --git a/czishrink/.gitignore b/czishrink/.gitignore new file mode 100644 index 0000000..5777b2a --- /dev/null +++ b/czishrink/.gitignore @@ -0,0 +1,457 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* +coverage/ + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +# *.nupkg +!packages_local/libczicompress.*.*.*.nupkg + +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# JetBrains Rider +.idea/ +*.sln.iml + +## +## Visual Studio Code +## +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json diff --git a/czishrink/.images/.gitattributes b/czishrink/.images/.gitattributes new file mode 100644 index 0000000..5f9b1ec --- /dev/null +++ b/czishrink/.images/.gitattributes @@ -0,0 +1,2 @@ +*.gif filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text diff --git a/czishrink/.images/CompressionStatistics.gif b/czishrink/.images/CompressionStatistics.gif new file mode 100644 index 0000000..39f9b7f --- /dev/null +++ b/czishrink/.images/CompressionStatistics.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:726be9bd09cacdfd49e01b58f6d80156a5efc67a30627c39c23efb2de7a0d1f5 +size 133010 diff --git a/czishrink/.images/CompressionStatistics.gif.license b/czishrink/.images/CompressionStatistics.gif.license new file mode 100644 index 0000000..cfacb07 --- /dev/null +++ b/czishrink/.images/CompressionStatistics.gif.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/czishrink/.images/CompressionStatistics.png b/czishrink/.images/CompressionStatistics.png new file mode 100644 index 0000000..922dbd7 --- /dev/null +++ b/czishrink/.images/CompressionStatistics.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b40420e046216d79b71590b7f483710bbd145dd9faab31f27f8aed727afcf27 +size 37074 diff --git a/czishrink/.images/CompressionStatistics.png.license b/czishrink/.images/CompressionStatistics.png.license new file mode 100644 index 0000000..cfacb07 --- /dev/null +++ b/czishrink/.images/CompressionStatistics.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/czishrink/.images/CziShrink_DarkTheme.png b/czishrink/.images/CziShrink_DarkTheme.png new file mode 100644 index 0000000..c13d78a --- /dev/null +++ b/czishrink/.images/CziShrink_DarkTheme.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30e8a384b90d46641b5fc90f71811ddf1253be598a25b95a2fc9da36f827c183 +size 58582 diff --git a/czishrink/.images/CziShrink_DarkTheme.png.license b/czishrink/.images/CziShrink_DarkTheme.png.license new file mode 100644 index 0000000..cfacb07 --- /dev/null +++ b/czishrink/.images/CziShrink_DarkTheme.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/czishrink/.images/CziShrink_LightTheme.png b/czishrink/.images/CziShrink_LightTheme.png new file mode 100644 index 0000000..c928c86 --- /dev/null +++ b/czishrink/.images/CziShrink_LightTheme.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c29c18cb7ca1fa56cf428552f53700a87c3e8a40e1b503a535d9e6d9a445a26 +size 58426 diff --git a/czishrink/.images/CziShrink_LightTheme.png.license b/czishrink/.images/CziShrink_LightTheme.png.license new file mode 100644 index 0000000..cfacb07 --- /dev/null +++ b/czishrink/.images/CziShrink_LightTheme.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/czishrink/.images/ErrorFileOptions.png b/czishrink/.images/ErrorFileOptions.png new file mode 100644 index 0000000..aa9fd64 --- /dev/null +++ b/czishrink/.images/ErrorFileOptions.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:722d6b25afc98772bc955585e812447d7d8e38fe69b0b15751417e920f0201c3 +size 29773 diff --git a/czishrink/.images/ErrorFileOptions.png.license b/czishrink/.images/ErrorFileOptions.png.license new file mode 100644 index 0000000..cfacb07 --- /dev/null +++ b/czishrink/.images/ErrorFileOptions.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/czishrink/.images/Progress.png b/czishrink/.images/Progress.png new file mode 100644 index 0000000..9a0cdf2 --- /dev/null +++ b/czishrink/.images/Progress.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6c38065cdaabfbc3583032eb687c3d595087d90ec60a99315614d194188d001 +size 41259 diff --git a/czishrink/.images/Progress.png.license b/czishrink/.images/Progress.png.license new file mode 100644 index 0000000..cfacb07 --- /dev/null +++ b/czishrink/.images/Progress.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/czishrink/.images/ProgressAnimation.gif b/czishrink/.images/ProgressAnimation.gif new file mode 100644 index 0000000..58f839d --- /dev/null +++ b/czishrink/.images/ProgressAnimation.gif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e025a74d53bf41cdff7a48aa17516e811a43b92aa4c48fcc8c6c62f804b1865 +size 1208046 diff --git a/czishrink/.images/ProgressAnimation.gif.license b/czishrink/.images/ProgressAnimation.gif.license new file mode 100644 index 0000000..cfacb07 --- /dev/null +++ b/czishrink/.images/ProgressAnimation.gif.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/czishrink/.images/ShareBadgeExample.png b/czishrink/.images/ShareBadgeExample.png new file mode 100644 index 0000000..479e66a --- /dev/null +++ b/czishrink/.images/ShareBadgeExample.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0ffafc2a5a56e8e30c349828f7e3a7d213776444e4febdfa9bc4698b35110f8 +size 5055 diff --git a/czishrink/.images/ShareBadgeExample.png.license b/czishrink/.images/ShareBadgeExample.png.license new file mode 100644 index 0000000..cfacb07 --- /dev/null +++ b/czishrink/.images/ShareBadgeExample.png.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH + +SPDX-License-Identifier: GPL-3.0-or-later diff --git a/czishrink/.vscode/launch.json b/czishrink/.vscode/launch.json new file mode 100644 index 0000000..42947bb --- /dev/null +++ b/czishrink/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/netczicompress.Desktop/bin/x64/Debug/net7.0/CziShrink", + "args": [], + "cwd": "${workspaceFolder}/netczicompress.Desktop", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/czishrink/.vscode/settings.json b/czishrink/.vscode/settings.json new file mode 100644 index 0000000..d09b546 --- /dev/null +++ b/czishrink/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dotnet.defaultSolution": "netczicompress.sln" +} \ No newline at end of file diff --git a/czishrink/.vscode/tasks.json b/czishrink/.vscode/tasks.json new file mode 100644 index 0000000..2b17345 --- /dev/null +++ b/czishrink/.vscode/tasks.json @@ -0,0 +1,70 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/netczicompress.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/netczicompress.Desktop/netczicompress.Desktop.csproj", + "-c", "Release", + "--use-current-runtime", "true", + "--self-contained", + "-p:PublishSingleFile=true", + "-p:PublishReadyToRun=true", + "-p:PublishReadyToRunShowWarnings=true" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "Watch (Tests)", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "test", + "--project", + "netczicompressTests" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "Test with Coverage", + "command": "dotnet", + "type": "process", + "args": [ + "test", + "/p:CollectCoverage=true", + "/p:CoverletOutputFormat=lcov%2ccobertura", + "/p:CoverletOutput=${workspaceFolder}/coverage/", + "/p:ExcludeByAttribute=GeneratedCodeAttribute%2cObsoleteAttribute", + "/p:ExcludeByFile=**/*.axaml%2c**/*.g.cs", + "/p:Exclude=[netczicompress]netczicompress.Views.*" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "Generate coverage report", + "command": "reportgenerator", + "type": "shell", + "args": [ + "-reports:${workspaceFolder}/coverage/coverage.info", + "-targetdir:${workspaceFolder}/coverage/covstats" + ], + "problemMatcher": [] + } + ] +} diff --git a/czishrink/CodeAnalysis.ruleset b/czishrink/CodeAnalysis.ruleset new file mode 100644 index 0000000..f0d70a0 --- /dev/null +++ b/czishrink/CodeAnalysis.ruleset @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/Directory.Build.props b/czishrink/Directory.Build.props new file mode 100644 index 0000000..4066629 --- /dev/null +++ b/czishrink/Directory.Build.props @@ -0,0 +1,16 @@ + + + net7.0 + latest + enable + True + enable + true + $(MSBuildThisFileDirectory)CodeAnalysis.ruleset + True + true + + 1.0.0 + alpha.45 + + diff --git a/czishrink/Directory.Build.targets b/czishrink/Directory.Build.targets new file mode 100644 index 0000000..7976089 --- /dev/null +++ b/czishrink/Directory.Build.targets @@ -0,0 +1,11 @@ + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/czishrink/Directory.Packages.props b/czishrink/Directory.Packages.props new file mode 100644 index 0000000..626af95 --- /dev/null +++ b/czishrink/Directory.Packages.props @@ -0,0 +1,40 @@ + + + 4.18.0 + 11.0.5 + 19.5.1 + 19.2.69 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/czishrink/LICENSE.txt b/czishrink/LICENSE.txt new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/czishrink/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/czishrink/NuGet.config b/czishrink/NuGet.config new file mode 100644 index 0000000..1475046 --- /dev/null +++ b/czishrink/NuGet.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/czishrink/README.md b/czishrink/README.md new file mode 100644 index 0000000..7ceeaea --- /dev/null +++ b/czishrink/README.md @@ -0,0 +1,152 @@ +# CziShrink +An open source and cross-platform GUI for [CziCompress](https://github.com/zeissmicroscopy/czicompress), made with Avalonia UI and .NET. + +## Table of contents + + - [Overview](#overview) + - [Download](#download) + - [System Requirements](#system-requirements) + - [Theme Support](#theme-support) + - [I/O](#io) + - [Parameters](#parameters) + - [Operations](#operations) + - [Progress Update](#progress-update) + - [Compression Statistics](#compression-statistics) + - [Sharing](#sharing) + - [Errors](#errors) + - [Logs](#logs) + - [FAQ](#faq) + - [Known Issues](#known-issues) + - [Potential Future Enhancements](#potential-future-enhancements) + - [Feedback](#feedback) + +## Overview + +## System Requirements + +### Windows + - Windows 10 or later +### Linux + - Supported OS: + - Debian 9 (Stretch) and higher + - Ubuntu 16.04 and higher + - X11 + - Clipboad functionality requires xclip to be installed + +## Theme Support +Dark and a light themes are set automatically based on your OS settings. +There is currently no way to manually set this. + +![Light Theme](.images/CziShrink_LightTheme.png)![Dark Theme](.images/CziShrink_DarkTheme.png) + +## I/O + +Input and output folders are selected, and the tool will operate on all CZI-documents within the input folder. +The intent of the tool is to be a bulk-compression utility. + +## Parameters + +### Operations + +| Operation | Description | +|-----------|-------------| +| Compress uncompressed data |Compresses only uncompresed subblocks and copies others.| +| Compress uncompressed and Zstd-compressed data|Compresses subblocks that were orginally uncompressed or compressed with zstd. | +| Compress all data |Compresses all subblocks regardless of current compression method (if possible). | +| Decompress all data |Decompresses all possible subblocks. | +| Dry Run |Finds all CZI files in the input folder but does not create any output CZI files. Like the other operations, it creates a CSV report in the output folder.| + +## Progress Update +CZI Shrink scans the input folder for CZI files. +And whenever it finds one, the file is immediately queued for processing. +CZI Shrink will usually process several files in parallel, depending on the state of the queue and the number of CPUs in your computer. +Progress is displayed for all files that are currently being processed. + +![Progress Updates](.images/Progress.png) + +## Compression Statistics + +Compression statistics are updated during the operation. +They provide some insight about what is going on. + +![Compression Statistics](.images/CompressionStatistics.png) + +### Sharing + +Upon completing an operation a new button should appear which will copy a bitmap to your clipboard containing a badge showing off how much the application was able to compress your files. + +![Compression Statistics Badge](.images/ShareBadgeExample.png) + +## Errors + +Files that are unable to be (un)compressed will appear in the bottom dialog with their error message. These entries can also be found in the [log file](#logs). +The error list also allows navigating directly to the files in question. + +![Error List](.images/ErrorFileOptions.png) + + +## Logs + +Logs are saved in the output directory. +The filename of this log is constructed as `CziShrink__`. +The timestamp itself is in the form of `T` while the `COMPRESSION_OPTION` will correspond +to the operation selected in the UI. + +Logs are produced during operation, however flushing of buffer to file does not occur as fast +as possible, so you may notice/experience a latency between the tasks that appear on the UI and +the actual entries written. +In some cases you may see no log output until the operation has completed if a reasonably constrained input directory is chosen. + +### Format +| InputFile | SizeInput | SizeOutput | SizeRatio | SizeDelta | TimeToProcess | Status | ErrorMessage| +| ----------|-----------|------------|-----------|-----------|---------------|--------|-------------| +| Sample_file.czi | 696480 | 564992 |0.811210659| 131488 | 00:00:00.0672709 |SUCCESS|| +| Sample_file2.czi| 1107888032 | 0 | 0 | -1 | |ERROR | Illegal data detected at offset 235352768 -> Invalid SubBlock-magic | +## FAQ +- Is the compression lossy? + - The tool uses [Zstandard](https://github.com/facebook/zstd) which is a fast and lossless compression algorithm. +- What happens to files that the tool is not able to compress? Are they still copied to the output folder? + - The file is not copied to the output folder and a log entry detailing the error is produced. + + +## Known Issues + +- Folder enumeration fails on network drives if connection drops. +- Non-X-Y-subblock CZIs will produce an error when attempting to compress. +- Multi-file CZI can't be converted. + - Ensure file is not currently opened in ZEN. +- Error encountered while compression file rarely causes creation of a corrupted output czi file. + +## Potential Future Enhancements + +This is a list of possible future enhancements based on feedback. +Any item on this list may be added or removed at any point in time based on prioritization. +There is no guarantee that any items on this list will be added, rather it is just to show what topics are on the radar. +- Localization +- Cache/Remember previously used options (folders, compression, etc) per-user + +## Contributing + +### Notes for Contributors +* Test changes at least on Linux and Windows. +* Note that the app will change appearance when the system theme is changed (Windows: Settings -> Personalization -> Colors -> Choose your colors: Light/Dark/Custom). Make sure that GUI changes look good both in the Light and in the Dark Theme. See https://docs.avaloniaui.net/docs/next/guides/styles-and-resources/how-to-use-theme-variants + +## How to upgrade libczicompressc automatically +1. Create a 'classic' personal access token at https://github.com/settings/tokens, authorize it for the ZEISS organization via the "Configure SSO" button, and store it in a GITHUB_TOKEN environment variable. +2. Open a Powershell terminal and run [./upgrade-libczicompressc.ps1](./upgrade-libczicompressc.ps1). Run `get-help ./upgrade-libczicompressc.ps1` for more info. + +## How to upgrade libczicompressc manually +1. Build libczicompressc.so on linux-x64 and libczicompressc.dll on win-x64 in release mode, or (preferred) get them from the [github CI build](https://github.com/zeissmicroscopy/czicompress/actions/workflows/cmake.yml). +1. Put the binaries into [libczicompressc/runtimes/linux-x64/native](libczicompressc/runtimes/linux-x64/native) and [libczicompressc/runtimes/win-x64/native](libczicompressc/runtimes/win-x64/native) +1. Update the nuspec file [libczicompressc/libczicompressc.nuspec](libczicompressc/libczicompressc.nuspec): + * `package/metadata/version` must be the 'ProductVersion' of libczicompressc.dll (explorer: Properties/Details) + * `package/metadata/repository[@commit]` must be the git commit from which the binaries were built +1. [Install nuget if necessary](https://learn.microsoft.com/en-us/nuget/install-nuget-client-tools#cli-tools). +1. Open a shell in [libczicompressc](libczicompressc), and run `path/to/nuget pack libczicompressc.nuspec` +1. Move the resulting nupkg into [packages_local](packages_local) and delete the old package from there. +1. Change the version of libczicompressc in [Directory.Packages.props](Directory.Packages.props) +1. If major or minor version has changed, change the expected version number in [PInvokeFileProcessor](https://github.com/m-ringler/netczicompress/blob/a254a4a919120ad61aad707f39749709f0a36e1a/netczicompress/Models/PInvokeFileProcessor.cs#L124). +1. Rebuild netczicompress.sln +1. Run netczicompressTests +1. Undo git changes to the libczicompressc.dll and libczicompressc.so files (no need to commit them, they are in the nupkg). +1. Commit the remaining changes with message "Upgrade libczicompressc to new version: x.y.z" diff --git a/czishrink/README.pdf b/czishrink/README.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ee9c7f5cd6892feb45bb6e573853e9977c180ac4 GIT binary patch literal 131 zcmWN?NfN>!5CFhCuiyiQW!RG6%&-VmDk%qZ@b%i4zUrIDe96An$%j(+v2KsX+yDNd zEzhUovu1S}F-MWy(fV*u*fZD)1!F}j2-;J#1ll;031LqKlkqLJN})I)0>2wdCC7-G NwBYfr(t0r7i62BAD4GBO literal 0 HcmV?d00001 diff --git a/czishrink/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt b/czishrink/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt new file mode 100644 index 0000000..6686074 --- /dev/null +++ b/czishrink/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt @@ -0,0 +1,1524 @@ +============= Free and Open Source Software Disclosure Statement ============== + +For czicompress + +This product includes copyrighted software from third parties that may be +distributed under different licenses than this product as given below. + +In case we missed to list a specific software and/or underlying license, +we appreciate your upfront notice. + +This product may include copyrighted software that is distributed under a +license that requires us to provide the source code of that software and +instructions on how to build and install it, such as the GNU GPL or GNU +LGPL or other reciprocal licenses. Unless otherwise stated the original +source code of such software has not been modified. As recipient and user of +this product you may obtain the complete source code for such copyrighted +software as well as build and install instructions from us for a period of +three years starting with the reception of the product and valid for as +long as we offer spare parts or customer support for that product model +and at no charge. + +In any of such cases, please send an email to opensource@zeiss.com +referencing your product's name, version, and manufacturing ZEISS entity. + + +List of third-party software: +----------------------------------- + +=============================================================================== + +------------------------------------------------------------------------------- +| Catch2 3.4.0 (used under Boost Software License 1.0) https://discord.gg/4CWS9zD +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Boost Software License - Version 1.0 +==================================== + + +August 17th, 2003 +----------------- + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by this +license (the "Software") to use, reproduce, display, distribute, execute, and +transmit the Software, and to prepare derivative works of the Software, and to +permit third-parties to whom the Software is furnished to do so, all subject to +the following: +The copyright notices in the Software and this entire statement, including the +above license grant, this restriction and the following disclaimer, must be +included in all copies of the Software, in whole or in part, and all derivative +works of the Software, unless such copies or derivative works are solely in the +form of machine-executable object code generated by a source language processor. +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES +OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------- +| CLIUtils/CLI11 v2.3.2 (used under BSD 3-clause "New" or "Revised" License) https://cliutils.gitlab.io/CLI11Tutorial +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2017-2023 University of Cincinnati, developed by Henry Schreiner under NSF AWARD 1414736. All Rights Reserved. +Copyright (c) 2012 - 2017, Lars Bilke. All Rights Reserved. +Copyright (c) 2017-2023 University of Cincinnati, developed by Henry Schreiner. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Copyright (c) , +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +| D3.js 3.5.17 (used under BSD 3-clause "New" or "Revised" License) http://d3js.org/ +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2010 Stanford Visualization Group. All Rights Reserved. +Copyright (c) 2011 Jason Davies. All Rights Reserved. +Copyright 2001 Robert Penner. All Rights Reserved. +Copyright (c) 2008-2012 Charles Karney. +Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State. +Copyright (c) 2010 SimpleGeo and Stamen Design. All Rights Reserved. +Copyright (c) 2010-2016 Michael Bostock. All Rights Reserved. +Copyright (C) 2010-2013 Raymond Hill. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Copyright (c) 2010, Stanford Visualization Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of Stanford University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + +------------------------------------------------------------------------------- +| gmdh 1.0 (used under MIT License) https://github.com/bauman-team/GMDH +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright 2001-2009 Kitware, Inc. +Copyright (C) 2018 David Hyde. +Copyright (C) 2009 Benjamin Schindler. +Copyright 2012 Continuum Analytics, Inc. All Rights Reserved. +Copyright (C) 2002-2007 Yves Renard This file is a part of GETFEM++. +Copyright (C) 2021 Chip Kerchner (chip. kerchner@ibm.com). +Copyright (C) 2011 Timothy E. Holy. +Copyright (c) 2021 NVIDIA CORPORATION. All Rights Reserved. +Copyright (C) 2020 Jens Wehner. +Copyright 2017 The TensorFlow Authors. All Rights Reserved. +Copyright 2013-2016 Florent Pruvost. +Copyright (C) EDF R. +Copyright (C) 2012 Desire Nuentsa Wakam. +Copyright (C) 2009-2013 Jitse Niesen. +Copyright (C) 2009 Ilya Baran. +(C) William Swanson, Paul Fultz define PYBIND11_EVAL0(...) __VA_ARGS__ define PYBIND11_EVAL1(...) PYBIND11_EVAL0(PYBIND11_EVAL0(PYBIND11_EVAL0(__VA_ARGS__))) define PYBIND11_EVAL2( +Copyright (C) 2018 Mehdi Goli Codeplay Software Ltd. +Copyright (C) 2013 Pavel Holoborodko. +Copyright (c) 2016-2017, 2020 Wenzel Jakob. All Rights Reserved. +Copyright : EDF 2001 $Header$. +Copyright (C) 2014 yoco. +Copyright (C) 2013 Jean Ceccato. +Copyright (c) 2005 by Timothy A. Davis. All Rights Reserved. +Copyright (C) 2011-2013 Chen-Pang He. +Copyright (C) 2006-2011, 2015 Benoit Jacob. +copyright (c) 2012-2014 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria, Univ. Bordeaux. All Rights Reserved. +Copyright (c) 2019 Dawid Pilarski. +Copyright (C) 2013 Pierre Zoppitelli. +Copyright (C) 2020 Everton Constantino. +Copyright (C) 2019 David Tellenbach. +(C) Desire NUENTSA WAKAM, INRIA include include include include include include include +Copyright (C) 2008 Gael Guennebaud Adapted from FindEigen. cmake:. +Copyright (c) 2016 Sergey Lyskov and Wenzel Jakob. +(C) Desire NUENTSA WAKAM, INRIA include include include include include include ifd +Copyright (c) Fabian Giesen, 2016. All Rights Reserved. +Copyright (c) 1994 by Xerox Corporation. All Rights Reserved. +Copyright (C) 2012 Alexey Korepanov. +Copyright (C) 1998-2010, 2015-2016. +Copyright (c) 2020 Wenzel Jakob and Henry Schreiner. +Copyright (C) 2015-2016 Eugene Brevdo. +Copyright (C) 2011 Andreas Platen. +Copyright (C) 2017 Viktor Csomor. +Copyright (C) 2020 Antonio Sanchez. +Copyright (c) 2017 Henry F. Schreiner. +Copyright (C) 2017 Kyle Macfarlan. +Copyright (C) 2012-2013 D. +Copyright (C) 2009 Thomas Capricelli This code initially comes from MINPACK whose original authors are:. +Copyright (C) 2008 Daniel Gomez Ferro. +Copyright (C) 2013 Nicolas Carre. +Copyright (C) 2012 David Harmon. +Copyright (c) 2014-2015 Open Source Robotics Foundation. All Rights Reserved. +Copyright (c) 2021 Laramie Leavitt (Google LLC). +Copyright (C) 2014, 2016 Benoit Steiner (benoit. steiner.goog@gmail.com). +Copyright (c) 2019 Roland Dreier. +copyright (c) 2009-2014 The University of Tennessee and The University of Tennessee Research Foundation. All Rights Reserved. +Copyright (C) 2016 Dmitry Vyukov. +Copyright (c) 2007 Allen Winter. +Copyright (C) 2016 Mehdi Goli, Codeplay Software Ltd. +Copyright (c) 2006-2007 Montel Laurent. +Copyright (C) 2009 Mark Borgerding mark a borgerding net. +Copyright (C) 2014, 2016 Pedro Gonnet (pedro. gonnet@gmail.com). +Copyright (C) 2009 Ricard Marxer. +Copyright (c) 2016 Sergey Lyskov. +Copyright 2003-2009 Mark Borgerding template struct kiss_cpx_fft. +Copyright 2012-2013 Mathieu Faverge. +Copyright (C) 2016 Igor Babuschkin. +Copyright (C) 2009 Kenneth Riddile. +Copyright (C) 1989, 1991, 1999, 2007 Free Software Foundation, Inc. +Copyright (C) 2005 the Regents of the University of Minnesota. +Copyright (c) 2007 Alexander Neundorf. +Copyright 2018-2019, 2022 Google LLC. All Rights Reserved. +Copyright (C) 2019 Joel Holdsworth. +Copyright (c) 1998-2003 by the University of Florida. All Rights Reserved. +Copyright (c) 2001, 2011 Intel Corporation. All Rights Reserved. Permition is granted to use, copy, distribute and prepare derivative works of this library for any purpose and without fee, provided, that the above. +Copyright (C) 2020 Jan van Dijk. +Copyright (C) 2009 Rohit Garg. +Copyright (c) 2017 Borja Zarco (Google LLC). +Copyright (C) 2009-2010 Thomas Capricelli. +Copyright (C) 2020 Arm Limited and Contributors. +Copyright (C) 2015 Vijay Vasudevan. +Copyright (C) 2009 Claire Maurice. +Copyright (C) 2017 Codeplay Software Limited. +Copyright (C) 2014-2015 Jianwei Cui. +Copyright (C) 2009-2013 Hauke Heibel. +Copyright (C) 2010 Vincent Lejeune. +Copyright (C) 2008-2009 Guillaume Saupin. +Copyright (C) 2020 Sebastien Boisvert. +Copyright (C) 2009 Mathieu Gautier. +Copyright (C) 2013 Christoph Hertzberg. +Copyright (C) 2008 Gael Guennebaud NOTE The functions of this file have been adapted from the GMM++ library. +Copyright (C) 2012-2013 Desire Nuentsa. +Copyright (C) 2017 Gagan Goel. +Copyright (C) 2014-2017 Benoit Steiner. +Copyright (C) 2020-2021 C. Antonio Sanchez. +Copyright (c) 2018 Hudson River Trading LLC. +Copyright (C) 2014-2015 Navdeep Jaitly. +Copyright (C) 2009 Thomas Capricelli include. +Copyright (c) 2016-2017 Jason Rhinelander. +Copyright (c) 2016 Pim Schellart. +Copyright (C) 2012 The Android Open Source Project. +Copyright (C) 2016, 2018-2019 Rasmus Munk Larsen. +Copyright (c) 2016 Ivan Smirnov. +Copyright (C) 2007 Michael Olbrich. +Copyright (c) 2020 The Eigen Authors. +Copyright (c) 2009 Boudewijn Rempt. +Copyright (C) 2012 desire Nuentsa. +(C) Copyright .. +Copyright 2012 Cedric Castagnede. +Copyright (C) 2012 Desire Nuentsa This code initially comes from MINPACK whose original authors are:. +Copyright (C) 2008-2019 Gael Guennebaud. +Copyright (C) 2012 Giacomo Po. +Copyright (c) 2016 Ben North. +copyright: (c) 2013 by Ihor Kalnytskyi +Copyright (C) 2018 Andy Davis. +Copyright 1984-1985, 1987, 1992, 2000 by Stephen L. Moshier Direct inquiries to 30 Frost Street, Cambridge, MA 02140 const Scalar zero = 0; const Scalar one = 1; const Scalar nan = Moshier Permission has been kindly provided by the original author to incorporate the Cephes software into the Eigen codebase: Moshier Direct inquiries to 30 Frost Street, Cambridge, MA 02140. +Copyright 2012-2016 Inria. All Rights Reserved. +Copyright (C) 2008-2009 Gael Guennebaud NOTE The class IterationController has been adapted from the iteration. +Copyright (C) 2013 Christian Seiler. +Copyright (C) 2009, 2012 Keir Mierle. +Copyright (c) 2016 Klemens D. Morgenstern. +Copyright (c) 2010 Intel Corp. All Rights Reserved. +Copyright (C) 2015 Gael Guennebaud Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditio. +Copyright (C) 2008-2016 Konstantinos Margaritis. +Copyright (c) 2006 Timothy A. Davis. +Copyright (c) 2011-2014 Willow Garage, Inc. +Copyright (C) 2016 Tobias Wood. +Copyright (C) 2011-2012, 2014 Kolja Brix. +Copyright (C) 2010 Manuel Yguel. +Copyright (C) 2015 Tal Hadad. +Copyright (C) 1997-2001 Authors: Andrew Lumsdaine Lie-Quan Lee This file is part of the Iterative Template Library. +Copyright (C) 2015 Ke Yang. +Copyright 2005-2010, 2013, 2015, 2018-2021 Google Inc. All Rights Reserved. +Copyright (C) 2018 Eugene Zhulenev. +Copyright (C) 2007 Julien Pommier. +Copyright (C) 2018 Wave Computing, Inc. Written by:. +Copyright 2016-2018 Codeplay Software Ltd. +Copyright (C) 2012 Desire Nuentsa The algorithm of this. +Copyright (c) 2016 Klemens Morgenstern and. +Copyright (C) 2012 Desire NUENTSA WAKAM. +Copyright (c) 2012 Erik Edlund Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions a. +Copyright (C) 2016 Rasmus Munk Larsen (rmlarsen@google. com). +Copyright (C) 2018 Deven Desai. +Copyright (C) 2020 Chris Schoutrop. +Copyright (C) 2010 Daniel Lowengrub. +Copyright 2012-2013 Emmanuel Agullo. +Copyright (C) 2014 Eric Martin. +Copyright (c) 2016 Trent Houliston and. +Copyright (C) 2020 Everton Constantino (everton. constantino@ibm.com). +Copyright (c) 2019 Pranav. +Copyright (C) 2013 Gauthier Brun. +Copyright (c) 2021 The Pybind Development Team. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License +=============== + +Copyright (c) + +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. + +------------------------------------------------------------------------------- +| libeigen/eigen 3.4.0 (used under (GNU Lesser General Public License v2.1 or later AND BSD 3-clause "New" or "Revised" License AND Mozilla Public License 2.0)) https://gitlab.com/libeigen/eigen.git +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (C) 2018 David Hyde. +Copyright (C) 2009 Benjamin Schindler. +Copyright (C) 2021 Chip Kerchner (chip. kerchner@ibm.com). +Copyright (C) 2011 Timothy E. Holy. +Copyright (c) 2021 NVIDIA CORPORATION. All Rights Reserved. +Copyright (C) 2020 Jens Wehner. +Copyright 2017 The TensorFlow Authors. All Rights Reserved. +Copyright 2013-2016 Florent Pruvost. +Copyright (C) EDF R. +Copyright (C) 2012 Desire Nuentsa Wakam. +Copyright (C) 2009-2013 Jitse Niesen. +Copyright (C) 2009 Ilya Baran. +Copyright (C) 2018 Mehdi Goli Codeplay Software Ltd. +Copyright (C) 2013 Pavel Holoborodko. +Copyright : EDF 2001 $Header$. +Copyright (C) 2014 yoco. +Copyright (C) 2013 Jean Ceccato. +Copyright (c) 2005 by Timothy A. Davis. All Rights Reserved. +Copyright (C) 2011-2013 Chen-Pang He. +Copyright (C) 2006-2011, 2015 Benoit Jacob. +copyright (c) 2012-2014 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria, Univ. Bordeaux. All Rights Reserved. +Copyright (C) 2013 Pierre Zoppitelli. +Copyright (C) 2020 Everton Constantino. +Copyright (C) 2019 David Tellenbach. +Copyright (C) 2008 Gael Guennebaud Adapted from FindEigen. cmake:. +Copyright (c) Fabian Giesen, 2016. All Rights Reserved. +Copyright (c) 1994 by Xerox Corporation. All Rights Reserved. +Copyright (C) 2012 Alexey Korepanov. +Copyright (C) 1998-2010, 2015-2016. +Copyright (C) 2015-2016 Eugene Brevdo. +Copyright (C) 2011 Andreas Platen. +Copyright (C) 2017 Viktor Csomor. +Copyright (C) 2020 Antonio Sanchez. +Copyright 2003-2009 Mark Borgerding. +Copyright (C) 2017 Kyle Macfarlan. +Copyright (C) 2012-2013 D. +Copyright (C) 2008 Daniel Gomez Ferro. +Copyright (C) 2013 Nicolas Carre. +Copyright (C) 2012 David Harmon. +Copyright (c) 2014-2015 Open Source Robotics Foundation. All Rights Reserved. +Copyright (C) 2014, 2016 Benoit Steiner (benoit. steiner.goog@gmail.com). +Copyright (C) 2015 Gael Guennebaud Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions. +copyright (c) 2009-2014 The University of Tennessee and The University of Tennessee Research Foundation. All Rights Reserved. +Copyright (C) 2016 Dmitry Vyukov. +Copyright (c) 2007 Allen Winter. +Copyright (C) 2016 Mehdi Goli, Codeplay Software Ltd. +Copyright (c) 2006-2007 Montel Laurent. +Copyright (C) 2009 Mark Borgerding mark a borgerding net. +Copyright (C) 2014, 2016 Pedro Gonnet (pedro. gonnet@gmail.com). +Copyright (C) 2009 Ricard Marxer. +Copyright 2012-2013 Mathieu Faverge. +Copyright (C) 2016 Igor Babuschkin. +Copyright (C) 2009 Kenneth Riddile. +Copyright (C) 1989, 1991, 1999, 2007 Free Software Foundation, Inc. +Copyright (C) 2005 the Regents of the University of Minnesota. +Copyright (c) 2007 Alexander Neundorf. +Copyright (C) 2019 Joel Holdsworth. +Copyright (c) 1998-2003 by the University of Florida. All Rights Reserved. +Copyright (C) 2002-2007 Yves Renard. +Copyright (C) 2020 Jan van Dijk. +Copyright (C) 2008-2019, 20013, 20015 Gael Guennebaud. +Copyright (C) 2009 Rohit Garg. +Copyright (C) 2009-2010 Thomas Capricelli. +Copyright (C) 2020 Arm Limited and Contributors. +Copyright (C) 2015 Vijay Vasudevan. +Copyright (C) 2009 Claire Maurice. +Copyright (C) 2017 Codeplay Software Limited. +Copyright (C) 2014-2015 Jianwei Cui. +Copyright 1984-1985, 1987, 1992, 2000 by Stephen L. Moshier Direct inquiries to 30 Frost Street, Cambridge, MA 02140 Moshier Direct inquiries to 30 Frost Street, Cambridge, MA 02140 const Scalar zero = 0; const Scalar one = 1; const Scalar nan = NumTr Moshier. +Copyright (C) 2009-2013 Hauke Heibel. +Copyright (C) 2010 Vincent Lejeune. +Copyright (C) 2008-2009 Guillaume Saupin. +Copyright (C) 2020 Sebastien Boisvert. +Copyright (C) 2009 Mathieu Gautier. +Copyright (C) 2013 Christoph Hertzberg. +Copyright (C) 2012-2013 Desire Nuentsa. +Copyright (C) 2017 Gagan Goel. +Copyright (C) 2014-2017 Benoit Steiner. +Copyright (C) 2020-2021 C. Antonio Sanchez. +Copyright (C) 2014-2015 Navdeep Jaitly. +Copyright (C) 1997-2001 Authors: Andrew Lumsdaine Lie-Quan Lee. +Copyright (C) 2012 The Android Open Source Project. +Copyright (C) 2016, 2018-2019 Rasmus Munk Larsen. +Copyright (c) 2001, 2011 Intel Corporation. All Rights Reserved. +Copyright (C) 2007 Michael Olbrich. +Copyright (c) 2020 The Eigen Authors. +Copyright (c) 2009 Boudewijn Rempt. +Copyright (C) 2012 desire Nuentsa. +Copyright 2012 Cedric Castagnede. +Copyright (C) 2012 Giacomo Po. +Copyright (C) 2018 Andy Davis. +Copyright 2012-2016 Inria. All Rights Reserved. +Copyright (C) 2013 Christian Seiler. +Copyright (C) 2009, 2012 Keir Mierle. +Copyright 2007-2009 Kitware, Inc. +Copyright (C) 2008-2016 Konstantinos Margaritis. +Copyright (c) 2006 Timothy A. Davis. +Copyright (c) 2011-2014 Willow Garage, Inc. +Copyright (C) 2016 Tobias Wood. +Copyright (C) 2011-2012, 2014 Kolja Brix. +Copyright (C) 2010 Manuel Yguel. +Copyright (C) 2015 Tal Hadad. +Copyright (C) 2015 Ke Yang. +Copyright (C) 2018 Eugene Zhulenev. +Copyright (C) 2007 Julien Pommier. +Copyright (C) 2018 Wave Computing, Inc. Written by:. +Copyright 2016-2018 Codeplay Software Ltd. +Copyright (C) 2012 Desire NUENTSA WAKAM. +Copyright (C) 2016 Rasmus Munk Larsen (rmlarsen@google. com). +Copyright (C) 2018 Deven Desai. +Copyright (C) 2020 Chris Schoutrop. +(C) Desire NUENTSA WAKAM, INRIA +Copyright (C) 2010 Daniel Lowengrub. +Copyright 2012-2013 Emmanuel Agullo. +Copyright (C) 2014 Eric Martin. +Copyright (C) 2020 Everton Constantino (everton. constantino@ibm.com). +Copyright (C) 2013 Gauthier Brun. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +GNU Lesser General Public License +================================= + +Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Everyone is permitted to copy and distribute verbatim copies + + of this license document, but changing it is not allowed. + + [This is the first released version of the Lesser GPL. It also counts + + as the successor of the GNU Library Public License, version 2, hence + + the version number 2.1.] + + +Preamble +-------- + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Lesser General Public License, applies to some specially +designated software packages--typically libraries--of the Free Software +Foundation and other authors who decide to use it. You can use it too, but we +suggest you first think carefully about whether this license or the ordinary +General Public License is the better strategy to use in any particular case, +based on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. +Our General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you wish); +that you receive source code or can get it if you want it; that you can change +the software and use pieces of it in new free programs; and that you are informed +that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to +deny you these rights or to ask you to surrender these rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a +fee, you must give the recipients all the rights that we gave you. You must make +sure that they, too, receive or can get the source code. If you link other code +with the library, you must provide complete object files to the recipients, so +that they can relink them with the library after making changes to the library +and recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and +(2) we offer you this license, which gives you legal permission to copy, +distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no +warranty for the free library. Also, if the library is modified by someone else +and passed on, the recipients should know that what they have is not the original +version, so that the original author's reputation will not be affected by +problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free +program. We wish to make sure that a company cannot effectively restrict the +users of a free program by obtaining a restrictive license from a patent holder. +Therefore, we insist that any patent license obtained for a version of the +library must be consistent with the full freedom of use specified in this +license. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License. This license, the GNU Lesser General Public License, +applies to certain designated libraries, and is quite different from the ordinary +General Public License. We use this license for certain libraries in order to +permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared +library, the combination of the two is legally speaking a combined work, a +derivative of the original library. The ordinary General Public License therefore +permits such linking only if the entire combination fits its criteria of freedom. +The Lesser General Public License permits more lax criteria for linking other +code with the library. + +We call this license the "Lesser" General Public License because it does Less to +protect the user's freedom than the ordinary General Public License. It also +provides other free software developers Less of an advantage over competing +non-free programs. These disadvantages are the reason we use the ordinary General +Public License for many libraries. However, the Lesser license provides +advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the +widest possible use of a certain library, so that it becomes a de-facto standard. +To achieve this, non-free programs must be allowed to use the library. A more +frequent case is that a free library does the same job as widely used non-free +libraries. In this case, there is little to gain by limiting the free library to +free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs +enables a greater number of people to use a large body of free software. For +example, permission to use the GNU C Library in non-free programs enables many +more people to use the whole GNU operating system, as well as its variant, the +GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' +freedom, it does ensure that the user of a program that is linked with the +Library has the freedom and the wherewithal to run that program using a modified +version of the Library. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, whereas the latter must be combined with the library in order +to run. + + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +--------------------------------------------------------------- + +0. This License Agreement applies to any software library or other program which +contains a notice placed by the copyright holder or other authorized party saying +it may be distributed under the terms of this Lesser General Public License (also +called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as +to be conveniently linked with application programs (which use some of those +functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been +distributed under these terms. A "work based on the Library" means either the +Library or any derivative work under copyright law: that is to say, a work +containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, +translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making +modifications to it. For a library, complete source code means all the source +code for all modules it contains, plus any associated interface definition files, +plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running a program using the +Library is not restricted, and output from such a program is covered only if its +contents constitute a work based on the Library (independent of the use of the +Library in a tool for writing it). Whether that is true depends on what the +Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to the +absence of any warranty; and distribute a copy of this License along with the +Library. + +You may charge a fee for the physical act of transferring a copy, and you may at +your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus +forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all of +these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices stating + that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no charge to all + third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a table of + data to be supplied by an application program that uses the facility, other + than as an argument passed when the facility is invoked, then you must make + a good faith effort to ensure that, in the event an application does not + supply such function or table, the facility still operates, and performs + whatever part of its purpose remains meaningful. + + (For example, a function in a library to compute square roots has a purpose + that is entirely well-defined independent of the application. Therefore, + Subsection 2d requires that any application-supplied function or table used + by this function must be optional: if the application does not supply it, + the square root function must still compute square roots.) + + These requirements apply to the modified work as a whole. If identifiable + sections of that work are not derived from the Library, and can be + reasonably considered independent and separate works in themselves, then + this License, and its terms, do not apply to those sections when you + distribute them as separate works. But when you distribute the same + sections as part of a whole which is a work based on the Library, the + distribution of the whole must be on the terms of this License, whose + permissions for other licensees extend to the entire whole, and thus to + each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest your + rights to work written entirely by you; rather, the intent is to exercise + the right to control the distribution of derivative or collective works + based on the Library. + + In addition, mere aggregation of another work not based on the Library with + the Library (or with a work based on the Library) on a volume of a storage + or distribution medium does not bring the other work under the scope of + this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. (If a +newer version than version 2 of the ordinary GNU General Public License has +appeared, then you can specify that version instead if you wish.) Do not make any +other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so +the ordinary GNU General Public License applies to all subsequent copies and +derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into +a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, +under Section 2) in object code or executable form under the terms of Sections 1 +and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a +designated place, then offering equivalent access to copy the source code from +the same place satisfies the requirement to distribute the source code, even +though third parties are not compelled to copy the source along with the object +code. + +5. A program that contains no derivative of any portion of the Library, but is +designed to work with the Library by being compiled or linked with it, is called +a "work that uses the Library". Such a work, in isolation, is not a derivative +work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions of +the Library), rather than a "work that uses the library". The executable is +therefore covered by this License. Section 6 states terms for distribution of +such executables. + +When a "work that uses the Library" uses material from a header file that is part +of the Library, the object code for the work may be a derivative work of the +Library even though the source code is not. Whether this is true is especially +significant if the work can be linked without the Library, or if the work is +itself a library. The threshold for this to be true is not precisely defined by +law. + +If such an object file uses only numerical parameters, data structure layouts and +accessors, and small macros and small inline functions (ten lines or less in +length), then the use of the object file is unrestricted, regardless of whether +it is legally a derivative work. (Executables containing this object code plus +portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the +object code for the work under the terms of Section 6. Any executables containing +that work also fall under Section 6, whether or not they are linked directly with +the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work +that uses the Library" with the Library to produce a work containing portions of +the Library, and distribute that work under terms of your choice, provided that +the terms permit modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is +used in it and that the Library and its use are covered by this License. You must +supply a copy of this License. If the work during execution displays copyright +notices, you must include the copyright notice for the Library among them, as +well as a reference directing the user to the copy of this License. Also, you +must do one of these things: + + a) Accompany the work with the complete corresponding machine-readable + source code for the Library including whatever changes were used in the + work (which must be distributed under Sections 1 and 2 above); and, if the + work is an executable linked with the Library, with the complete + machine-readable "work that uses the Library", as object code and/or source + code, so that the user can modify the Library and then relink to produce a + modified executable containing the modified Library. (It is understood that + the user who changes the contents of definitions files in the Library will + not necessarily be able to recompile the application to use the modified + definitions.) + + b) Use a suitable shared library mechanism for linking with the Library. A + suitable mechanism is one that (1) uses at run time a copy of the library + already present on the user's computer system, rather than copying library + functions into the executable, and (2) will operate properly with a + modified version of the library, if the user installs one, as long as the + modified version is interface-compatible with the version that the work was + made with. + + c) Accompany the work with a written offer, valid for at least three years, + to give the same user the materials specified in Subsection 6a, above, for + a charge no more than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy from a + designated place, offer equivalent access to copy the above specified + materials from the same place. + + e) Verify that the user has already received a copy of these materials or + that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable from +it. However, as a special exception, the materials to be distributed need not +include anything that is normally distributed (in either source or binary form) +with the major components (compiler, kernel, and so on) of the operating system +on which the executable runs, unless that component itself accompanies the +executable. + +It may happen that this requirement contradicts the license restrictions of other +proprietary libraries that do not normally accompany the operating system. Such a +contradiction means you cannot use both them and the Library together in an +executable that you distribute. + +7. You may place library facilities that are a work based on the Library +side-by-side in a single library together with other library facilities not +covered by this License, and distribute such a combined library, provided that +the separate distribution of the work based on the Library and of the other +library facilities is otherwise permitted, and provided that you do these two +things: + + a) Accompany the combined library with a copy of the same work based on the + Library, uncombined with any other library facilities. This must be + distributed under the terms of the Sections above. + + b) Give prominent notice with the combined library of the fact that part of + it is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to copy, +modify, sublicense, link with, or distribute the Library is void, and will +automatically terminate your rights under this License. However, parties who have +received copies, or rights, from you under this License will not have their +licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the Library +or its derivative works. These actions are prohibited by law if you do not accept +this License. Therefore, by modifying or distributing the Library (or any work +based on the Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying the Library +or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor to +copy, distribute, link with or modify the Library subject to these terms and +conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed on +you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of this +License. If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as a +consequence you may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by all those +who receive copies directly or indirectly through you, then the only way you +could satisfy both it and this License would be to refrain entirely from +distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, and the +section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system which is implemented by public license practices. Many people +have made generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that system; it is +up to the author/donor to decide if he or she is willing to distribute software +through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the +Lesser General Public License from time to time. Such new versions will be +similar in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies a +version number of this License which applies to it and "any later version", you +have the option of following the terms and conditions either of that version or +of any later version published by the Free Software Foundation. If the Library +does not specify a license version number, you may choose any version ever +published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author to +ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing and +reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE +LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED +IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" +WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL +ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE +LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, +SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY +TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF +THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + +END OF TERMS AND CONDITIONS + + +How to Apply These Terms to Your New Libraries +---------------------------------------------- + +If you develop a new library, and you want it to be of the greatest possible use +to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under these +terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey the +exclusion of warranty; and each file should have at least the "copyright" line +and a pointer to where the full notice is found. + + one line to give the library's name and an idea of what it does. + + Copyright (C) year name of author + + This library is free software; you can redistribute it and/or + + modify it under the terms of the GNU Lesser General Public + + License as published by the Free Software Foundation; either + + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + + but WITHOUT ANY WARRANTY; without even the implied warranty of + + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + + License along with this library; if not, write to the Free Software + + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a +sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in + + the library `Frob' (a library for tweaking knobs) written + + by James Random Hacker. + + signature of Ty Coon, 1 April 1990 + + Ty Coon, President of Vice + +That's all there is to it! + + +Copyright (c) , +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +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. + +------------------------------------------------------------------------------- +| libzstd-dev 1.5.4 (used under BSD 3-clause "New" or "Revised" License) https://github.com/Cyan4973/zstd +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) Meta Platforms, Inc. and affiliates. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Copyright (c) , +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +| libzstd-dev 1.5.5 (used under BSD 3-clause "New" or "Revised" License) https://github.com/Cyan4973/zstd +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Copyright (c) , +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +| roboticstoolbox-python 1.0.2 (used under MIT License) https://github.com/petercorke/robotics-toolbox-python +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License +=============== + +Copyright (c) + +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. + +------------------------------------------------------------------------------- +| zstd 1.5.5 (used under BSD 3-clause "New" or "Revised" License) http://www.zstd.net +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) Martin Liska, SUSE, Meta Platforms, Inc. and affiliates. +Copyright (C) 2012-2016 Yann Collet. +Copyright (c) Yann Collet, Meta Platforms, Inc. and affiliates. +Copyright 2020 Jan Tojnar. +Copyright (c) 1995-2006, 2010-2011 Jean-loup Gailly. +Copyright (c) 2018-present lzutao +Copyright (C) 2004-2017 Mark Adler. +Copyright (c) 2003-2008 Yuta Mori. All Rights Reserved. +Copyright (c) Meta Platforms, Inc. and affiliates. You can contact the author at : and affiliates and affiliates. All Rights Reserved. +Copyright (C) 1989, 1991, 2000-2016 Free Software Foundation, Inc. +Copyright (c) 2016 Tino Reichardt. All Rights Reserved. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Copyright (c) , +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- + +Generated on Fri, 28 Jul 2023 15:38:21 +0200 using Black Duck 2023.4.2. \ No newline at end of file diff --git a/czishrink/icon-source/convert-to-ico.sh b/czishrink/icon-source/convert-to-ico.sh new file mode 100644 index 0000000..2955790 --- /dev/null +++ b/czishrink/icon-source/convert-to-ico.sh @@ -0,0 +1,2 @@ +#!/bin/bash +magick mogrify -path . -format ico -density 600 -define icon:auto-resize=256,128,64,48,40,32,24,16 -background none netczicompress.svg \ No newline at end of file diff --git a/czishrink/icon-source/netczicompress.svg b/czishrink/icon-source/netczicompress.svg new file mode 100644 index 0000000..03de992 --- /dev/null +++ b/czishrink/icon-source/netczicompress.svg @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/czishrink/libczicompressc/LICENSE.txt b/czishrink/libczicompressc/LICENSE.txt new file mode 100644 index 0000000..1749d76 --- /dev/null +++ b/czishrink/libczicompressc/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Carl Zeiss Microscopy GmbH + +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. diff --git a/czishrink/libczicompressc/THIRD_PARTY_LICENSES.txt b/czishrink/libczicompressc/THIRD_PARTY_LICENSES.txt new file mode 100644 index 0000000..1b833ba --- /dev/null +++ b/czishrink/libczicompressc/THIRD_PARTY_LICENSES.txt @@ -0,0 +1,128 @@ +============= Free and Open Source Software Disclosure Statement ============== + +For libCZI + +This project includes copyrighted software from third parties that may be +distributed under different licenses than this project as given below. + +In case we missed to list a specific software and/or underlying license, +we appreciate your upfront notice. + +This project may include copyrighted software that is distributed under a +license that requires us to provide the source code of that software and +instructions on how to build and install it, such as the GNU GPL or GNU +LGPL or other reciprocal licenses. Unless otherwise stated the original +source code of such software has not been modified. As recipient and user of +this project you may obtain the complete source code for such copyrighted +software as well as build and install instructions from us for a period of +three years starting with the reception of the project and at no charge. + +In any of such cases, please send an email to opensource@zeiss.com +referencing this project's name and version. + + +List of third-party software: +----------------------------------- + +=============================================================================== + +------------------------------------------------------------------------------- +| jxrlib 1.1 (used under BSD 3-clause "New" or "Revised" License) https://github.com/4creators/jxrlib +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright 2013 Microsoft Corporation + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Copyright (c) , +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +| md5sum (used under RSA MD4 or MD5 Message-Digest Algorithm License) +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +RSA MD4 or MD5 Message-Digest Algorithm License +=============================================== + +License to copy and use this software is granted provided that it is identified +as the "RSA Data Security, Inc. MD4 or MD5 Message-Digest Algorithm" in all +material mentioning or referencing this software or this function. + +License is also granted to make and use derivative works provided that such works +are identified as "derived from the RSA Data Security, Inc. MD4 or MD5 +Message-Digest Algorithm" in all material mentioning or referencing the derived +work. + +RSA Data Security, Inc. makes no representations concerning either the +merchantability of this software or the suitability of this software for any +particular purpose. It is provided "as is" without express or implied warranty of +any kind. + +These notices must be retained in any copies of any part of this documentation +and/or software. + +------------------------------------------------------------------------------- +| pugixml 1.10 (used under MIT License) http://pugixml.org/ +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (C) 2006-2019, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) +Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +This library is distributed under the MIT License: + +Copyright (c) 2006-2019 Arseny Kapoulkine + +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 + + +--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- + +Generated on Thu, 29 Sep 2022 07:40:42 +0200 using Black Duck 2022.4.2. \ No newline at end of file diff --git a/czishrink/libczicompressc/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt b/czishrink/libczicompressc/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt new file mode 100644 index 0000000..6686074 --- /dev/null +++ b/czishrink/libczicompressc/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt @@ -0,0 +1,1524 @@ +============= Free and Open Source Software Disclosure Statement ============== + +For czicompress + +This product includes copyrighted software from third parties that may be +distributed under different licenses than this product as given below. + +In case we missed to list a specific software and/or underlying license, +we appreciate your upfront notice. + +This product may include copyrighted software that is distributed under a +license that requires us to provide the source code of that software and +instructions on how to build and install it, such as the GNU GPL or GNU +LGPL or other reciprocal licenses. Unless otherwise stated the original +source code of such software has not been modified. As recipient and user of +this product you may obtain the complete source code for such copyrighted +software as well as build and install instructions from us for a period of +three years starting with the reception of the product and valid for as +long as we offer spare parts or customer support for that product model +and at no charge. + +In any of such cases, please send an email to opensource@zeiss.com +referencing your product's name, version, and manufacturing ZEISS entity. + + +List of third-party software: +----------------------------------- + +=============================================================================== + +------------------------------------------------------------------------------- +| Catch2 3.4.0 (used under Boost Software License 1.0) https://discord.gg/4CWS9zD +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Boost Software License - Version 1.0 +==================================== + + +August 17th, 2003 +----------------- + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by this +license (the "Software") to use, reproduce, display, distribute, execute, and +transmit the Software, and to prepare derivative works of the Software, and to +permit third-parties to whom the Software is furnished to do so, all subject to +the following: +The copyright notices in the Software and this entire statement, including the +above license grant, this restriction and the following disclaimer, must be +included in all copies of the Software, in whole or in part, and all derivative +works of the Software, unless such copies or derivative works are solely in the +form of machine-executable object code generated by a source language processor. +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES +OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------- +| CLIUtils/CLI11 v2.3.2 (used under BSD 3-clause "New" or "Revised" License) https://cliutils.gitlab.io/CLI11Tutorial +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2017-2023 University of Cincinnati, developed by Henry Schreiner under NSF AWARD 1414736. All Rights Reserved. +Copyright (c) 2012 - 2017, Lars Bilke. All Rights Reserved. +Copyright (c) 2017-2023 University of Cincinnati, developed by Henry Schreiner. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Copyright (c) , +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +| D3.js 3.5.17 (used under BSD 3-clause "New" or "Revised" License) http://d3js.org/ +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2010 Stanford Visualization Group. All Rights Reserved. +Copyright (c) 2011 Jason Davies. All Rights Reserved. +Copyright 2001 Robert Penner. All Rights Reserved. +Copyright (c) 2008-2012 Charles Karney. +Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State. +Copyright (c) 2010 SimpleGeo and Stamen Design. All Rights Reserved. +Copyright (c) 2010-2016 Michael Bostock. All Rights Reserved. +Copyright (C) 2010-2013 Raymond Hill. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Copyright (c) 2010, Stanford Visualization Group +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of Stanford University nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE + +------------------------------------------------------------------------------- +| gmdh 1.0 (used under MIT License) https://github.com/bauman-team/GMDH +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright 2001-2009 Kitware, Inc. +Copyright (C) 2018 David Hyde. +Copyright (C) 2009 Benjamin Schindler. +Copyright 2012 Continuum Analytics, Inc. All Rights Reserved. +Copyright (C) 2002-2007 Yves Renard This file is a part of GETFEM++. +Copyright (C) 2021 Chip Kerchner (chip. kerchner@ibm.com). +Copyright (C) 2011 Timothy E. Holy. +Copyright (c) 2021 NVIDIA CORPORATION. All Rights Reserved. +Copyright (C) 2020 Jens Wehner. +Copyright 2017 The TensorFlow Authors. All Rights Reserved. +Copyright 2013-2016 Florent Pruvost. +Copyright (C) EDF R. +Copyright (C) 2012 Desire Nuentsa Wakam. +Copyright (C) 2009-2013 Jitse Niesen. +Copyright (C) 2009 Ilya Baran. +(C) William Swanson, Paul Fultz define PYBIND11_EVAL0(...) __VA_ARGS__ define PYBIND11_EVAL1(...) PYBIND11_EVAL0(PYBIND11_EVAL0(PYBIND11_EVAL0(__VA_ARGS__))) define PYBIND11_EVAL2( +Copyright (C) 2018 Mehdi Goli Codeplay Software Ltd. +Copyright (C) 2013 Pavel Holoborodko. +Copyright (c) 2016-2017, 2020 Wenzel Jakob. All Rights Reserved. +Copyright : EDF 2001 $Header$. +Copyright (C) 2014 yoco. +Copyright (C) 2013 Jean Ceccato. +Copyright (c) 2005 by Timothy A. Davis. All Rights Reserved. +Copyright (C) 2011-2013 Chen-Pang He. +Copyright (C) 2006-2011, 2015 Benoit Jacob. +copyright (c) 2012-2014 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria, Univ. Bordeaux. All Rights Reserved. +Copyright (c) 2019 Dawid Pilarski. +Copyright (C) 2013 Pierre Zoppitelli. +Copyright (C) 2020 Everton Constantino. +Copyright (C) 2019 David Tellenbach. +(C) Desire NUENTSA WAKAM, INRIA include include include include include include include +Copyright (C) 2008 Gael Guennebaud Adapted from FindEigen. cmake:. +Copyright (c) 2016 Sergey Lyskov and Wenzel Jakob. +(C) Desire NUENTSA WAKAM, INRIA include include include include include include ifd +Copyright (c) Fabian Giesen, 2016. All Rights Reserved. +Copyright (c) 1994 by Xerox Corporation. All Rights Reserved. +Copyright (C) 2012 Alexey Korepanov. +Copyright (C) 1998-2010, 2015-2016. +Copyright (c) 2020 Wenzel Jakob and Henry Schreiner. +Copyright (C) 2015-2016 Eugene Brevdo. +Copyright (C) 2011 Andreas Platen. +Copyright (C) 2017 Viktor Csomor. +Copyright (C) 2020 Antonio Sanchez. +Copyright (c) 2017 Henry F. Schreiner. +Copyright (C) 2017 Kyle Macfarlan. +Copyright (C) 2012-2013 D. +Copyright (C) 2009 Thomas Capricelli This code initially comes from MINPACK whose original authors are:. +Copyright (C) 2008 Daniel Gomez Ferro. +Copyright (C) 2013 Nicolas Carre. +Copyright (C) 2012 David Harmon. +Copyright (c) 2014-2015 Open Source Robotics Foundation. All Rights Reserved. +Copyright (c) 2021 Laramie Leavitt (Google LLC). +Copyright (C) 2014, 2016 Benoit Steiner (benoit. steiner.goog@gmail.com). +Copyright (c) 2019 Roland Dreier. +copyright (c) 2009-2014 The University of Tennessee and The University of Tennessee Research Foundation. All Rights Reserved. +Copyright (C) 2016 Dmitry Vyukov. +Copyright (c) 2007 Allen Winter. +Copyright (C) 2016 Mehdi Goli, Codeplay Software Ltd. +Copyright (c) 2006-2007 Montel Laurent. +Copyright (C) 2009 Mark Borgerding mark a borgerding net. +Copyright (C) 2014, 2016 Pedro Gonnet (pedro. gonnet@gmail.com). +Copyright (C) 2009 Ricard Marxer. +Copyright (c) 2016 Sergey Lyskov. +Copyright 2003-2009 Mark Borgerding template struct kiss_cpx_fft. +Copyright 2012-2013 Mathieu Faverge. +Copyright (C) 2016 Igor Babuschkin. +Copyright (C) 2009 Kenneth Riddile. +Copyright (C) 1989, 1991, 1999, 2007 Free Software Foundation, Inc. +Copyright (C) 2005 the Regents of the University of Minnesota. +Copyright (c) 2007 Alexander Neundorf. +Copyright 2018-2019, 2022 Google LLC. All Rights Reserved. +Copyright (C) 2019 Joel Holdsworth. +Copyright (c) 1998-2003 by the University of Florida. All Rights Reserved. +Copyright (c) 2001, 2011 Intel Corporation. All Rights Reserved. Permition is granted to use, copy, distribute and prepare derivative works of this library for any purpose and without fee, provided, that the above. +Copyright (C) 2020 Jan van Dijk. +Copyright (C) 2009 Rohit Garg. +Copyright (c) 2017 Borja Zarco (Google LLC). +Copyright (C) 2009-2010 Thomas Capricelli. +Copyright (C) 2020 Arm Limited and Contributors. +Copyright (C) 2015 Vijay Vasudevan. +Copyright (C) 2009 Claire Maurice. +Copyright (C) 2017 Codeplay Software Limited. +Copyright (C) 2014-2015 Jianwei Cui. +Copyright (C) 2009-2013 Hauke Heibel. +Copyright (C) 2010 Vincent Lejeune. +Copyright (C) 2008-2009 Guillaume Saupin. +Copyright (C) 2020 Sebastien Boisvert. +Copyright (C) 2009 Mathieu Gautier. +Copyright (C) 2013 Christoph Hertzberg. +Copyright (C) 2008 Gael Guennebaud NOTE The functions of this file have been adapted from the GMM++ library. +Copyright (C) 2012-2013 Desire Nuentsa. +Copyright (C) 2017 Gagan Goel. +Copyright (C) 2014-2017 Benoit Steiner. +Copyright (C) 2020-2021 C. Antonio Sanchez. +Copyright (c) 2018 Hudson River Trading LLC. +Copyright (C) 2014-2015 Navdeep Jaitly. +Copyright (C) 2009 Thomas Capricelli include. +Copyright (c) 2016-2017 Jason Rhinelander. +Copyright (c) 2016 Pim Schellart. +Copyright (C) 2012 The Android Open Source Project. +Copyright (C) 2016, 2018-2019 Rasmus Munk Larsen. +Copyright (c) 2016 Ivan Smirnov. +Copyright (C) 2007 Michael Olbrich. +Copyright (c) 2020 The Eigen Authors. +Copyright (c) 2009 Boudewijn Rempt. +Copyright (C) 2012 desire Nuentsa. +(C) Copyright .. +Copyright 2012 Cedric Castagnede. +Copyright (C) 2012 Desire Nuentsa This code initially comes from MINPACK whose original authors are:. +Copyright (C) 2008-2019 Gael Guennebaud. +Copyright (C) 2012 Giacomo Po. +Copyright (c) 2016 Ben North. +copyright: (c) 2013 by Ihor Kalnytskyi +Copyright (C) 2018 Andy Davis. +Copyright 1984-1985, 1987, 1992, 2000 by Stephen L. Moshier Direct inquiries to 30 Frost Street, Cambridge, MA 02140 const Scalar zero = 0; const Scalar one = 1; const Scalar nan = Moshier Permission has been kindly provided by the original author to incorporate the Cephes software into the Eigen codebase: Moshier Direct inquiries to 30 Frost Street, Cambridge, MA 02140. +Copyright 2012-2016 Inria. All Rights Reserved. +Copyright (C) 2008-2009 Gael Guennebaud NOTE The class IterationController has been adapted from the iteration. +Copyright (C) 2013 Christian Seiler. +Copyright (C) 2009, 2012 Keir Mierle. +Copyright (c) 2016 Klemens D. Morgenstern. +Copyright (c) 2010 Intel Corp. All Rights Reserved. +Copyright (C) 2015 Gael Guennebaud Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditio. +Copyright (C) 2008-2016 Konstantinos Margaritis. +Copyright (c) 2006 Timothy A. Davis. +Copyright (c) 2011-2014 Willow Garage, Inc. +Copyright (C) 2016 Tobias Wood. +Copyright (C) 2011-2012, 2014 Kolja Brix. +Copyright (C) 2010 Manuel Yguel. +Copyright (C) 2015 Tal Hadad. +Copyright (C) 1997-2001 Authors: Andrew Lumsdaine Lie-Quan Lee This file is part of the Iterative Template Library. +Copyright (C) 2015 Ke Yang. +Copyright 2005-2010, 2013, 2015, 2018-2021 Google Inc. All Rights Reserved. +Copyright (C) 2018 Eugene Zhulenev. +Copyright (C) 2007 Julien Pommier. +Copyright (C) 2018 Wave Computing, Inc. Written by:. +Copyright 2016-2018 Codeplay Software Ltd. +Copyright (C) 2012 Desire Nuentsa The algorithm of this. +Copyright (c) 2016 Klemens Morgenstern and. +Copyright (C) 2012 Desire NUENTSA WAKAM. +Copyright (c) 2012 Erik Edlund Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions a. +Copyright (C) 2016 Rasmus Munk Larsen (rmlarsen@google. com). +Copyright (C) 2018 Deven Desai. +Copyright (C) 2020 Chris Schoutrop. +Copyright (C) 2010 Daniel Lowengrub. +Copyright 2012-2013 Emmanuel Agullo. +Copyright (C) 2014 Eric Martin. +Copyright (c) 2016 Trent Houliston and. +Copyright (C) 2020 Everton Constantino (everton. constantino@ibm.com). +Copyright (c) 2019 Pranav. +Copyright (C) 2013 Gauthier Brun. +Copyright (c) 2021 The Pybind Development Team. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License +=============== + +Copyright (c) + +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. + +------------------------------------------------------------------------------- +| libeigen/eigen 3.4.0 (used under (GNU Lesser General Public License v2.1 or later AND BSD 3-clause "New" or "Revised" License AND Mozilla Public License 2.0)) https://gitlab.com/libeigen/eigen.git +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (C) 2018 David Hyde. +Copyright (C) 2009 Benjamin Schindler. +Copyright (C) 2021 Chip Kerchner (chip. kerchner@ibm.com). +Copyright (C) 2011 Timothy E. Holy. +Copyright (c) 2021 NVIDIA CORPORATION. All Rights Reserved. +Copyright (C) 2020 Jens Wehner. +Copyright 2017 The TensorFlow Authors. All Rights Reserved. +Copyright 2013-2016 Florent Pruvost. +Copyright (C) EDF R. +Copyright (C) 2012 Desire Nuentsa Wakam. +Copyright (C) 2009-2013 Jitse Niesen. +Copyright (C) 2009 Ilya Baran. +Copyright (C) 2018 Mehdi Goli Codeplay Software Ltd. +Copyright (C) 2013 Pavel Holoborodko. +Copyright : EDF 2001 $Header$. +Copyright (C) 2014 yoco. +Copyright (C) 2013 Jean Ceccato. +Copyright (c) 2005 by Timothy A. Davis. All Rights Reserved. +Copyright (C) 2011-2013 Chen-Pang He. +Copyright (C) 2006-2011, 2015 Benoit Jacob. +copyright (c) 2012-2014 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria, Univ. Bordeaux. All Rights Reserved. +Copyright (C) 2013 Pierre Zoppitelli. +Copyright (C) 2020 Everton Constantino. +Copyright (C) 2019 David Tellenbach. +Copyright (C) 2008 Gael Guennebaud Adapted from FindEigen. cmake:. +Copyright (c) Fabian Giesen, 2016. All Rights Reserved. +Copyright (c) 1994 by Xerox Corporation. All Rights Reserved. +Copyright (C) 2012 Alexey Korepanov. +Copyright (C) 1998-2010, 2015-2016. +Copyright (C) 2015-2016 Eugene Brevdo. +Copyright (C) 2011 Andreas Platen. +Copyright (C) 2017 Viktor Csomor. +Copyright (C) 2020 Antonio Sanchez. +Copyright 2003-2009 Mark Borgerding. +Copyright (C) 2017 Kyle Macfarlan. +Copyright (C) 2012-2013 D. +Copyright (C) 2008 Daniel Gomez Ferro. +Copyright (C) 2013 Nicolas Carre. +Copyright (C) 2012 David Harmon. +Copyright (c) 2014-2015 Open Source Robotics Foundation. All Rights Reserved. +Copyright (C) 2014, 2016 Benoit Steiner (benoit. steiner.goog@gmail.com). +Copyright (C) 2015 Gael Guennebaud Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions. +copyright (c) 2009-2014 The University of Tennessee and The University of Tennessee Research Foundation. All Rights Reserved. +Copyright (C) 2016 Dmitry Vyukov. +Copyright (c) 2007 Allen Winter. +Copyright (C) 2016 Mehdi Goli, Codeplay Software Ltd. +Copyright (c) 2006-2007 Montel Laurent. +Copyright (C) 2009 Mark Borgerding mark a borgerding net. +Copyright (C) 2014, 2016 Pedro Gonnet (pedro. gonnet@gmail.com). +Copyright (C) 2009 Ricard Marxer. +Copyright 2012-2013 Mathieu Faverge. +Copyright (C) 2016 Igor Babuschkin. +Copyright (C) 2009 Kenneth Riddile. +Copyright (C) 1989, 1991, 1999, 2007 Free Software Foundation, Inc. +Copyright (C) 2005 the Regents of the University of Minnesota. +Copyright (c) 2007 Alexander Neundorf. +Copyright (C) 2019 Joel Holdsworth. +Copyright (c) 1998-2003 by the University of Florida. All Rights Reserved. +Copyright (C) 2002-2007 Yves Renard. +Copyright (C) 2020 Jan van Dijk. +Copyright (C) 2008-2019, 20013, 20015 Gael Guennebaud. +Copyright (C) 2009 Rohit Garg. +Copyright (C) 2009-2010 Thomas Capricelli. +Copyright (C) 2020 Arm Limited and Contributors. +Copyright (C) 2015 Vijay Vasudevan. +Copyright (C) 2009 Claire Maurice. +Copyright (C) 2017 Codeplay Software Limited. +Copyright (C) 2014-2015 Jianwei Cui. +Copyright 1984-1985, 1987, 1992, 2000 by Stephen L. Moshier Direct inquiries to 30 Frost Street, Cambridge, MA 02140 Moshier Direct inquiries to 30 Frost Street, Cambridge, MA 02140 const Scalar zero = 0; const Scalar one = 1; const Scalar nan = NumTr Moshier. +Copyright (C) 2009-2013 Hauke Heibel. +Copyright (C) 2010 Vincent Lejeune. +Copyright (C) 2008-2009 Guillaume Saupin. +Copyright (C) 2020 Sebastien Boisvert. +Copyright (C) 2009 Mathieu Gautier. +Copyright (C) 2013 Christoph Hertzberg. +Copyright (C) 2012-2013 Desire Nuentsa. +Copyright (C) 2017 Gagan Goel. +Copyright (C) 2014-2017 Benoit Steiner. +Copyright (C) 2020-2021 C. Antonio Sanchez. +Copyright (C) 2014-2015 Navdeep Jaitly. +Copyright (C) 1997-2001 Authors: Andrew Lumsdaine Lie-Quan Lee. +Copyright (C) 2012 The Android Open Source Project. +Copyright (C) 2016, 2018-2019 Rasmus Munk Larsen. +Copyright (c) 2001, 2011 Intel Corporation. All Rights Reserved. +Copyright (C) 2007 Michael Olbrich. +Copyright (c) 2020 The Eigen Authors. +Copyright (c) 2009 Boudewijn Rempt. +Copyright (C) 2012 desire Nuentsa. +Copyright 2012 Cedric Castagnede. +Copyright (C) 2012 Giacomo Po. +Copyright (C) 2018 Andy Davis. +Copyright 2012-2016 Inria. All Rights Reserved. +Copyright (C) 2013 Christian Seiler. +Copyright (C) 2009, 2012 Keir Mierle. +Copyright 2007-2009 Kitware, Inc. +Copyright (C) 2008-2016 Konstantinos Margaritis. +Copyright (c) 2006 Timothy A. Davis. +Copyright (c) 2011-2014 Willow Garage, Inc. +Copyright (C) 2016 Tobias Wood. +Copyright (C) 2011-2012, 2014 Kolja Brix. +Copyright (C) 2010 Manuel Yguel. +Copyright (C) 2015 Tal Hadad. +Copyright (C) 2015 Ke Yang. +Copyright (C) 2018 Eugene Zhulenev. +Copyright (C) 2007 Julien Pommier. +Copyright (C) 2018 Wave Computing, Inc. Written by:. +Copyright 2016-2018 Codeplay Software Ltd. +Copyright (C) 2012 Desire NUENTSA WAKAM. +Copyright (C) 2016 Rasmus Munk Larsen (rmlarsen@google. com). +Copyright (C) 2018 Deven Desai. +Copyright (C) 2020 Chris Schoutrop. +(C) Desire NUENTSA WAKAM, INRIA +Copyright (C) 2010 Daniel Lowengrub. +Copyright 2012-2013 Emmanuel Agullo. +Copyright (C) 2014 Eric Martin. +Copyright (C) 2020 Everton Constantino (everton. constantino@ibm.com). +Copyright (C) 2013 Gauthier Brun. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +GNU Lesser General Public License +================================= + +Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Everyone is permitted to copy and distribute verbatim copies + + of this license document, but changing it is not allowed. + + [This is the first released version of the Lesser GPL. It also counts + + as the successor of the GNU Library Public License, version 2, hence + + the version number 2.1.] + + +Preamble +-------- + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Lesser General Public License, applies to some specially +designated software packages--typically libraries--of the Free Software +Foundation and other authors who decide to use it. You can use it too, but we +suggest you first think carefully about whether this license or the ordinary +General Public License is the better strategy to use in any particular case, +based on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. +Our General Public Licenses are designed to make sure that you have the freedom +to distribute copies of free software (and charge for this service if you wish); +that you receive source code or can get it if you want it; that you can change +the software and use pieces of it in new free programs; and that you are informed +that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to +deny you these rights or to ask you to surrender these rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a +fee, you must give the recipients all the rights that we gave you. You must make +sure that they, too, receive or can get the source code. If you link other code +with the library, you must provide complete object files to the recipients, so +that they can relink them with the library after making changes to the library +and recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and +(2) we offer you this license, which gives you legal permission to copy, +distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no +warranty for the free library. Also, if the library is modified by someone else +and passed on, the recipients should know that what they have is not the original +version, so that the original author's reputation will not be affected by +problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free +program. We wish to make sure that a company cannot effectively restrict the +users of a free program by obtaining a restrictive license from a patent holder. +Therefore, we insist that any patent license obtained for a version of the +library must be consistent with the full freedom of use specified in this +license. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License. This license, the GNU Lesser General Public License, +applies to certain designated libraries, and is quite different from the ordinary +General Public License. We use this license for certain libraries in order to +permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared +library, the combination of the two is legally speaking a combined work, a +derivative of the original library. The ordinary General Public License therefore +permits such linking only if the entire combination fits its criteria of freedom. +The Lesser General Public License permits more lax criteria for linking other +code with the library. + +We call this license the "Lesser" General Public License because it does Less to +protect the user's freedom than the ordinary General Public License. It also +provides other free software developers Less of an advantage over competing +non-free programs. These disadvantages are the reason we use the ordinary General +Public License for many libraries. However, the Lesser license provides +advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the +widest possible use of a certain library, so that it becomes a de-facto standard. +To achieve this, non-free programs must be allowed to use the library. A more +frequent case is that a free library does the same job as widely used non-free +libraries. In this case, there is little to gain by limiting the free library to +free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs +enables a greater number of people to use a large body of free software. For +example, permission to use the GNU C Library in non-free programs enables many +more people to use the whole GNU operating system, as well as its variant, the +GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' +freedom, it does ensure that the user of a program that is linked with the +Library has the freedom and the wherewithal to run that program using a modified +version of the Library. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, whereas the latter must be combined with the library in order +to run. + + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +--------------------------------------------------------------- + +0. This License Agreement applies to any software library or other program which +contains a notice placed by the copyright holder or other authorized party saying +it may be distributed under the terms of this Lesser General Public License (also +called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as +to be conveniently linked with application programs (which use some of those +functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been +distributed under these terms. A "work based on the Library" means either the +Library or any derivative work under copyright law: that is to say, a work +containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, +translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making +modifications to it. For a library, complete source code means all the source +code for all modules it contains, plus any associated interface definition files, +plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by +this License; they are outside its scope. The act of running a program using the +Library is not restricted, and output from such a program is covered only if its +contents constitute a work based on the Library (independent of the use of the +Library in a tool for writing it). Whether that is true depends on what the +Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to the +absence of any warranty; and distribute a copy of this License along with the +Library. + +You may charge a fee for the physical act of transferring a copy, and you may at +your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus +forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all of +these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices stating + that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no charge to all + third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a table of + data to be supplied by an application program that uses the facility, other + than as an argument passed when the facility is invoked, then you must make + a good faith effort to ensure that, in the event an application does not + supply such function or table, the facility still operates, and performs + whatever part of its purpose remains meaningful. + + (For example, a function in a library to compute square roots has a purpose + that is entirely well-defined independent of the application. Therefore, + Subsection 2d requires that any application-supplied function or table used + by this function must be optional: if the application does not supply it, + the square root function must still compute square roots.) + + These requirements apply to the modified work as a whole. If identifiable + sections of that work are not derived from the Library, and can be + reasonably considered independent and separate works in themselves, then + this License, and its terms, do not apply to those sections when you + distribute them as separate works. But when you distribute the same + sections as part of a whole which is a work based on the Library, the + distribution of the whole must be on the terms of this License, whose + permissions for other licensees extend to the entire whole, and thus to + each and every part regardless of who wrote it. + + Thus, it is not the intent of this section to claim rights or contest your + rights to work written entirely by you; rather, the intent is to exercise + the right to control the distribution of derivative or collective works + based on the Library. + + In addition, mere aggregation of another work not based on the Library with + the Library (or with a work based on the Library) on a volume of a storage + or distribution medium does not bring the other work under the scope of + this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. (If a +newer version than version 2 of the ordinary GNU General Public License has +appeared, then you can specify that version instead if you wish.) Do not make any +other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so +the ordinary GNU General Public License applies to all subsequent copies and +derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into +a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, +under Section 2) in object code or executable form under the terms of Sections 1 +and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a +designated place, then offering equivalent access to copy the source code from +the same place satisfies the requirement to distribute the source code, even +though third parties are not compelled to copy the source along with the object +code. + +5. A program that contains no derivative of any portion of the Library, but is +designed to work with the Library by being compiled or linked with it, is called +a "work that uses the Library". Such a work, in isolation, is not a derivative +work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions of +the Library), rather than a "work that uses the library". The executable is +therefore covered by this License. Section 6 states terms for distribution of +such executables. + +When a "work that uses the Library" uses material from a header file that is part +of the Library, the object code for the work may be a derivative work of the +Library even though the source code is not. Whether this is true is especially +significant if the work can be linked without the Library, or if the work is +itself a library. The threshold for this to be true is not precisely defined by +law. + +If such an object file uses only numerical parameters, data structure layouts and +accessors, and small macros and small inline functions (ten lines or less in +length), then the use of the object file is unrestricted, regardless of whether +it is legally a derivative work. (Executables containing this object code plus +portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the +object code for the work under the terms of Section 6. Any executables containing +that work also fall under Section 6, whether or not they are linked directly with +the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work +that uses the Library" with the Library to produce a work containing portions of +the Library, and distribute that work under terms of your choice, provided that +the terms permit modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is +used in it and that the Library and its use are covered by this License. You must +supply a copy of this License. If the work during execution displays copyright +notices, you must include the copyright notice for the Library among them, as +well as a reference directing the user to the copy of this License. Also, you +must do one of these things: + + a) Accompany the work with the complete corresponding machine-readable + source code for the Library including whatever changes were used in the + work (which must be distributed under Sections 1 and 2 above); and, if the + work is an executable linked with the Library, with the complete + machine-readable "work that uses the Library", as object code and/or source + code, so that the user can modify the Library and then relink to produce a + modified executable containing the modified Library. (It is understood that + the user who changes the contents of definitions files in the Library will + not necessarily be able to recompile the application to use the modified + definitions.) + + b) Use a suitable shared library mechanism for linking with the Library. A + suitable mechanism is one that (1) uses at run time a copy of the library + already present on the user's computer system, rather than copying library + functions into the executable, and (2) will operate properly with a + modified version of the library, if the user installs one, as long as the + modified version is interface-compatible with the version that the work was + made with. + + c) Accompany the work with a written offer, valid for at least three years, + to give the same user the materials specified in Subsection 6a, above, for + a charge no more than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy from a + designated place, offer equivalent access to copy the above specified + materials from the same place. + + e) Verify that the user has already received a copy of these materials or + that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable from +it. However, as a special exception, the materials to be distributed need not +include anything that is normally distributed (in either source or binary form) +with the major components (compiler, kernel, and so on) of the operating system +on which the executable runs, unless that component itself accompanies the +executable. + +It may happen that this requirement contradicts the license restrictions of other +proprietary libraries that do not normally accompany the operating system. Such a +contradiction means you cannot use both them and the Library together in an +executable that you distribute. + +7. You may place library facilities that are a work based on the Library +side-by-side in a single library together with other library facilities not +covered by this License, and distribute such a combined library, provided that +the separate distribution of the work based on the Library and of the other +library facilities is otherwise permitted, and provided that you do these two +things: + + a) Accompany the combined library with a copy of the same work based on the + Library, uncombined with any other library facilities. This must be + distributed under the terms of the Sections above. + + b) Give prominent notice with the combined library of the fact that part of + it is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to copy, +modify, sublicense, link with, or distribute the Library is void, and will +automatically terminate your rights under this License. However, parties who have +received copies, or rights, from you under this License will not have their +licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the Library +or its derivative works. These actions are prohibited by law if you do not accept +this License. Therefore, by modifying or distributing the Library (or any work +based on the Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying the Library +or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor to +copy, distribute, link with or modify the Library subject to these terms and +conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), conditions are imposed on +you (whether by court order, agreement or otherwise) that contradict the +conditions of this License, they do not excuse you from the conditions of this +License. If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as a +consequence you may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by all those +who receive copies directly or indirectly through you, then the only way you +could satisfy both it and this License would be to refrain entirely from +distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, and the +section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system which is implemented by public license practices. Many people +have made generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that system; it is +up to the author/donor to decide if he or she is willing to distribute software +through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the +Lesser General Public License from time to time. Such new versions will be +similar in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies a +version number of this License which applies to it and "any later version", you +have the option of following the terms and conditions either of that version or +of any later version published by the Free Software Foundation. If the Library +does not specify a license version number, you may choose any version ever +published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author to +ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing and +reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE +LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED +IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" +WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL +ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE +LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, +SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY +TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF +THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + +END OF TERMS AND CONDITIONS + + +How to Apply These Terms to Your New Libraries +---------------------------------------------- + +If you develop a new library, and you want it to be of the greatest possible use +to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under these +terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey the +exclusion of warranty; and each file should have at least the "copyright" line +and a pointer to where the full notice is found. + + one line to give the library's name and an idea of what it does. + + Copyright (C) year name of author + + This library is free software; you can redistribute it and/or + + modify it under the terms of the GNU Lesser General Public + + License as published by the Free Software Foundation; either + + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + + but WITHOUT ANY WARRANTY; without even the implied warranty of + + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + + License along with this library; if not, write to the Free Software + + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a +sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in + + the library `Frob' (a library for tweaking knobs) written + + by James Random Hacker. + + signature of Ty Coon, 1 April 1990 + + Ty Coon, President of Vice + +That's all there is to it! + + +Copyright (c) , +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +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. + +------------------------------------------------------------------------------- +| libzstd-dev 1.5.4 (used under BSD 3-clause "New" or "Revised" License) https://github.com/Cyan4973/zstd +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) Meta Platforms, Inc. and affiliates. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Copyright (c) , +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +| libzstd-dev 1.5.5 (used under BSD 3-clause "New" or "Revised" License) https://github.com/Cyan4973/zstd +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Copyright (c) , +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +| roboticstoolbox-python 1.0.2 (used under MIT License) https://github.com/petercorke/robotics-toolbox-python +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License +=============== + +Copyright (c) + +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. + +------------------------------------------------------------------------------- +| zstd 1.5.5 (used under BSD 3-clause "New" or "Revised" License) http://www.zstd.net +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) Martin Liska, SUSE, Meta Platforms, Inc. and affiliates. +Copyright (C) 2012-2016 Yann Collet. +Copyright (c) Yann Collet, Meta Platforms, Inc. and affiliates. +Copyright 2020 Jan Tojnar. +Copyright (c) 1995-2006, 2010-2011 Jean-loup Gailly. +Copyright (c) 2018-present lzutao +Copyright (C) 2004-2017 Mark Adler. +Copyright (c) 2003-2008 Yuta Mori. All Rights Reserved. +Copyright (c) Meta Platforms, Inc. and affiliates. You can contact the author at : and affiliates and affiliates. All Rights Reserved. +Copyright (C) 1989, 1991, 2000-2016 Free Software Foundation, Inc. +Copyright (c) 2016 Tino Reichardt. All Rights Reserved. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Copyright (c) , +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of the nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- + +Generated on Fri, 28 Jul 2023 15:38:21 +0200 using Black Duck 2023.4.2. \ No newline at end of file diff --git a/czishrink/libczicompressc/libczicompressc.nuspec b/czishrink/libczicompressc/libczicompressc.nuspec new file mode 100644 index 0000000..59332d2 --- /dev/null +++ b/czishrink/libczicompressc/libczicompressc.nuspec @@ -0,0 +1,16 @@ + + + + libczicompressc + 0.5.1-alpha.0 + Native runtimes for CZI ZStd compression + Carl Zeiss Microscopy + Carl Zeiss Microscopy + Native runtimes for CZI ZStd compression + false + MIT + https://github.com/zeissmicroscopy/czicompress/ + © Carl Zeiss Microscopy GmbH and others. All rights reserved. + + + \ No newline at end of file diff --git a/czishrink/libczicompressc/runtimes/linux-x64/native/libczicompressc.so b/czishrink/libczicompressc/runtimes/linux-x64/native/libczicompressc.so new file mode 100644 index 0000000..e69de29 diff --git a/czishrink/libczicompressc/runtimes/win-x64/native/libczicompressc.dll b/czishrink/libczicompressc/runtimes/win-x64/native/libczicompressc.dll new file mode 100644 index 0000000..e69de29 diff --git a/czishrink/netczicompress.Desktop/Program.cs b/czishrink/netczicompress.Desktop/Program.cs new file mode 100644 index 0000000..0af9f69 --- /dev/null +++ b/czishrink/netczicompress.Desktop/Program.cs @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Desktop; + +using System; +using System.Diagnostics; +using Avalonia; +using Avalonia.ReactiveUI; +using Projektanker.Icons.Avalonia; +using Projektanker.Icons.Avalonia.FontAwesome; + +internal class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) + { + // NOTE: when in use, a GUID will be prepended to the filename. + using TextWriterTraceListener listener = CreateTraceListener(); + Trace.Listeners.Add(listener); + Trace.AutoFlush = true; + + try + { + BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + } + finally + { + Trace.Flush(); + Trace.Listeners.Remove(listener); + } + } + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + { + IconProvider.Current + .Register(); + + return AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace(level: Avalonia.Logging.LogEventLevel.Error) + .UseReactiveUI(); + } + + private static TextWriterTraceListener CreateTraceListener() + { + var progName = typeof(Program).Assembly.GetName().Name ?? "CziShrink"; + var traceLogFolder = System.IO.Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + progName); + if (!Directory.Exists(traceLogFolder)) + { + try + { + Directory.CreateDirectory(traceLogFolder); + } + catch (Exception ex) + { + Trace.TraceError(ex.ToString()); + } + } + + var traceLogFile = System.IO.Path.Combine( + traceLogFolder, + $"{progName}.log"); + var listener = new TextWriterTraceListener(traceLogFile, "logFileListener"); + return listener; + } +} diff --git a/czishrink/netczicompress.Desktop/app.manifest b/czishrink/netczicompress.Desktop/app.manifest new file mode 100644 index 0000000..515802e --- /dev/null +++ b/czishrink/netczicompress.Desktop/app.manifest @@ -0,0 +1,25 @@ + + + + + + + + true + + + + + + + + + + + + + diff --git a/czishrink/netczicompress.Desktop/netczicompress.Desktop.csproj b/czishrink/netczicompress.Desktop/netczicompress.Desktop.csproj new file mode 100644 index 0000000..c863f1e --- /dev/null +++ b/czishrink/netczicompress.Desktop/netczicompress.Desktop.csproj @@ -0,0 +1,27 @@ + + + WinExe + true + app.manifest + ..\netczicompress\Assets\netczicompress.ico + CziShrink + x64 + + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + diff --git a/czishrink/netczicompress.sln b/czishrink/netczicompress.sln new file mode 100644 index 0000000..f915d18 --- /dev/null +++ b/czishrink/netczicompress.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33829.357 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "netczicompress", "netczicompress\netczicompress.csproj", "{886596AD-1451-4567-AC42-76182C9C187B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "netczicompress.Desktop", "netczicompress.Desktop\netczicompress.Desktop.csproj", "{433ABC81-4FAA-4328-9832-4416644391D5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "netczicompressTests", "netczicompressTests\netczicompressTests.csproj", "{8B1A7EB0-48FE-4DF3-B23A-46F520565052}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {886596AD-1451-4567-AC42-76182C9C187B}.Debug|x64.ActiveCfg = Debug|x64 + {886596AD-1451-4567-AC42-76182C9C187B}.Debug|x64.Build.0 = Debug|x64 + {886596AD-1451-4567-AC42-76182C9C187B}.Release|x64.ActiveCfg = Release|x64 + {886596AD-1451-4567-AC42-76182C9C187B}.Release|x64.Build.0 = Release|x64 + {433ABC81-4FAA-4328-9832-4416644391D5}.Debug|x64.ActiveCfg = Debug|x64 + {433ABC81-4FAA-4328-9832-4416644391D5}.Debug|x64.Build.0 = Debug|x64 + {433ABC81-4FAA-4328-9832-4416644391D5}.Release|x64.ActiveCfg = Release|x64 + {433ABC81-4FAA-4328-9832-4416644391D5}.Release|x64.Build.0 = Release|x64 + {8B1A7EB0-48FE-4DF3-B23A-46F520565052}.Debug|x64.ActiveCfg = Debug|x64 + {8B1A7EB0-48FE-4DF3-B23A-46F520565052}.Debug|x64.Build.0 = Debug|x64 + {8B1A7EB0-48FE-4DF3-B23A-46F520565052}.Release|x64.ActiveCfg = Release|x64 + {8B1A7EB0-48FE-4DF3-B23A-46F520565052}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {071D0ED6-64D2-4792-BF50-FA6C27898722} + EndGlobalSection +EndGlobal diff --git a/czishrink/netczicompress/App.axaml b/czishrink/netczicompress/App.axaml new file mode 100644 index 0000000..bbd6f42 --- /dev/null +++ b/czishrink/netczicompress/App.axaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/App.axaml.cs b/czishrink/netczicompress/App.axaml.cs new file mode 100644 index 0000000..cb2d191 --- /dev/null +++ b/czishrink/netczicompress/App.axaml.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress; + +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; + +/// +/// The application. +/// +public partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (this.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = AppComposer.ComposeMainWindow(); + } + else if (this.ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) + { + singleViewPlatform.MainView = AppComposer.ComposeMainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + } +} diff --git a/czishrink/netczicompress/AppComposer.cs b/czishrink/netczicompress/AppComposer.cs new file mode 100644 index 0000000..b91ae05 --- /dev/null +++ b/czishrink/netczicompress/AppComposer.cs @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress; + +using System.IO.Abstractions; + +using Microsoft.Extensions.Logging; + +using netczicompress.Models; +using netczicompress.Models.Clipboard; +using netczicompress.ViewModels; +using netczicompress.Views; + +using ReactiveUI; + +/// +/// Composes the application with 'Pure DI'. +/// +public class AppComposer +{ + public static MainWindow ComposeMainWindow() + { + var info = new ProgramNameAndVersion(); + var mainViewModel = ComposeMainViewModel(info); + + return new MainWindow + { + DataContext = mainViewModel, + Title = info.Name, + }; + } + + public static MainViewModel ComposeMainViewModel(IProgramNameAndVersion info) + { + // Parameters that we might have to tweak + const int MaxNumberOfErrorsToShow = 1000; + const int ErrorBufferSize = 100; + var errorBufferInterval = TimeSpan.FromSeconds(1); + + var statisticsReportingInterval = TimeSpan.FromSeconds(1); + + // Actual composition code + var fs = new FileSystem(); + string libraryNameAndVersion = PInvokeFileProcessor.GetLibFullName(); + + ILoggerFactory loggerfactory = new TraceLoggerFactory(); + var logger = loggerfactory.CreateLogger(); + logger.LogInformation("Starting {CZI Shrink} using {libczicompressc}", info, libraryNameAndVersion); + + var noOp = new NoOpFileProcessor(); + IFileProcessor CreateFileProcessor(CompressionMode mode, ProcessingOptions options) => mode switch + { + CompressionMode.NoOp => noOp, + _ => PInvokeFileProcessor.Create(mode, options), + }; + IFolderCompressor compressor = new MultiThreadedFolderCompressor(CreateFileProcessor, new FileProcessingFailedHandler()); + + IFileLauncher fileLauncher = new FileLauncher(fs, Environment.GetEnvironmentVariable); + var gui = RxApp.MainThreadScheduler; + var errorList = new ErrorListViewModel( + maxNumberOfErrorsToShow: MaxNumberOfErrorsToShow, + bufferInterval: errorBufferInterval, + bufferCapacity: ErrorBufferSize, + scheduler: gui, + launcher: fileLauncher); + var currentTasks = new CurrentTasksViewModel(scheduler: gui); + var logViewModel = new LogFileViewModel(new CsvLoggingStrategy(fileLauncher), gui); + + var aggregateIndicationViewModel = new AggregateIndicationViewModel(); + var aggregateStatisticsViewModel = new AggregateStatisticsViewModel( + aggregateIndicationViewModel: aggregateIndicationViewModel, + ClipboardHelper.Instance, + logger: loggerfactory.CreateLogger()) + { + OpenLogFileCommand = logViewModel.OpenLogFileCommand, + }; + + var statisticsObserver = CompositeObserver.Create(aggregateStatisticsViewModel, aggregateIndicationViewModel); + var statisticsRunObserver = new StatisticsRunObserver(statisticsObserver, statisticsReportingInterval, gui); + + compressor = compressor.DecorateWithRunObserver(statisticsRunObserver); + compressor = compressor.DecorateWithRunObserver(logViewModel); + compressor = compressor.DecorateWithRunObserver(errorList); + compressor = compressor.DecorateWithObserver(currentTasks, gui); + + var mainViewModel = new MainViewModel( + compressor: compressor, + fileSystem: fs, + errorList: errorList, + currentTasksViewModel: currentTasks, + aggregateStatisticsViewModel: aggregateStatisticsViewModel, + aboutViewModel: new AboutViewModel(fileLauncher, info) { LibraryName = libraryNameAndVersion }, + logger: loggerfactory.CreateLogger()); + + return mainViewModel; + } +} diff --git a/czishrink/netczicompress/Assets/Logo.png b/czishrink/netczicompress/Assets/Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cb975df3ae8ac794a7d9d8040ef4d6df5c32c5c9 GIT binary patch literal 15845 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 Dc0wBS literal 0 HcmV?d00001 diff --git a/czishrink/netczicompress/Assets/netczicompress.ico b/czishrink/netczicompress/Assets/netczicompress.ico new file mode 100644 index 0000000000000000000000000000000000000000..b0c83d01247e53566a1aeedbff3b6b60b29bba4b GIT binary patch literal 127985 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 z + + \ No newline at end of file diff --git a/czishrink/netczicompress/Models/AggregateStatistics.cs b/czishrink/netczicompress/Models/AggregateStatistics.cs new file mode 100644 index 0000000..2816af5 --- /dev/null +++ b/czishrink/netczicompress/Models/AggregateStatistics.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; + +/// +/// Cumulative statistics of a compression or decompression operation. +/// +public readonly record struct AggregateStatistics +{ + public static AggregateStatistics Empty { get; } = default; + + public int FilesWithErrors { get; init; } + + public int FilesWithNoErrors { get; init; } + + public TimeSpan Duration { get; init; } + + public long InputBytes { get; init; } + + public long OutputBytes { get; init; } + + public long DeltaBytes => this.OutputBytes - this.InputBytes; + + public float OutputToInputRatio => 1.0f * this.OutputBytes / this.InputBytes; +} \ No newline at end of file diff --git a/czishrink/netczicompress/Models/AsyncEnumerableToObservableActionAdapter.cs b/czishrink/netczicompress/Models/AsyncEnumerableToObservableActionAdapter.cs new file mode 100644 index 0000000..b9bd242 --- /dev/null +++ b/czishrink/netczicompress/Models/AsyncEnumerableToObservableActionAdapter.cs @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.Collections.Generic; +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading; +using System.Threading.Tasks; + +/// +/// An adapter from an to an . +/// +/// The type of the produced items. +public class AsyncEnumerableToObservableActionAdapter : IObservableAction +{ + private readonly IAsyncEnumerable enumerable; + private readonly CancellationToken token; + private readonly Subject subject = new(); + private bool started; + + public AsyncEnumerableToObservableActionAdapter(IAsyncEnumerable enumerable, CancellationToken token) + { + this.enumerable = enumerable; + this.token = token; + this.Output = this.subject.AsObservable(); + } + + /// + /// + /// This observable may deliver items on another thread than the thread that calls . + /// + /// Use an appropriate to observe it. + /// + public IObservable Output { get; } + + /// + public void Start() + { + if (this.started) + { + throw new InvalidOperationException($"{nameof(this.Start)} can only be called once."); + } + + this.started = true; + + Core(); + + async void Core() + { + try + { + await foreach (var item in this.enumerable.WithCancellation(this.token).ConfigureAwait(false)) + { + this.subject.OnNext(item); + } + + this.subject.OnCompleted(); + } + catch (Exception ex) + { + this.subject.OnError(ex); + } + } + } +} diff --git a/czishrink/netczicompress/Models/Clipboard/ClipboardHelper.cs b/czishrink/netczicompress/Models/Clipboard/ClipboardHelper.cs new file mode 100644 index 0000000..698a0ed --- /dev/null +++ b/czishrink/netczicompress/Models/Clipboard/ClipboardHelper.cs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models.Clipboard; + +using System.Runtime.InteropServices; + +internal static class ClipboardHelper +{ + private static readonly IClipboardHelper? InstanceValue; + + static ClipboardHelper() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + InstanceValue = new ClipboardHelperWin32(); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + InstanceValue = new ClipboardHelperLinux(); + } + } + + public static IClipboardHelper? Instance + { + get + { + return InstanceValue; + } + } +} diff --git a/czishrink/netczicompress/Models/Clipboard/ClipboardHelperLinux.cs b/czishrink/netczicompress/Models/Clipboard/ClipboardHelperLinux.cs new file mode 100644 index 0000000..039e265 --- /dev/null +++ b/czishrink/netczicompress/Models/Clipboard/ClipboardHelperLinux.cs @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models.Clipboard; + +using System.Diagnostics; +using Avalonia.Media.Imaging; + +/// +/// This is an implementation of the IClipboardHelper interface for the X-Windows system. We use the +/// utility "xclip" as an external program which we launch and pass to it the bitmap as a PNG. +/// +internal class ClipboardHelperLinux : IClipboardHelper +{ + public void PutBitmapIntoClipboard(Bitmap bitmap) + { + if (bitmap == null) + { + throw new ArgumentNullException(nameof(bitmap)); + } + + using (var memoryStream = new MemoryStream()) + { + bitmap.Save(memoryStream); + memoryStream.Position = 0; + ClipboardHelperLinux.SetImageToClipboard(memoryStream, "image/png"); + } + } + + private static void SetImageToClipboard(Stream imageData, string mimeType) + { + var startInfo = new ProcessStartInfo + { + FileName = "xclip", + ArgumentList = + { + "-selection", "clipboard", + "-t", mimeType, + "-i", + }, + RedirectStandardInput = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + using (var process = new Process { StartInfo = startInfo }) + { + process.Start(); + + // the png is now written to stdout + imageData.CopyTo(process.StandardInput.BaseStream); + + // then we close stdout, and xclip will start operation + process.StandardInput.Close(); + process.WaitForExit(); + } + } +} diff --git a/czishrink/netczicompress/Models/Clipboard/ClipboardHelperWin32.cs b/czishrink/netczicompress/Models/Clipboard/ClipboardHelperWin32.cs new file mode 100644 index 0000000..b2ad876 --- /dev/null +++ b/czishrink/netczicompress/Models/Clipboard/ClipboardHelperWin32.cs @@ -0,0 +1,331 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models.Clipboard; + +using System.Runtime.InteropServices; +using Avalonia; +using Avalonia.Media.Imaging; +using Avalonia.Platform; + +/// +/// An implementation of the IClipboardHelper interface based on Win32-API. +/// Obviously, this code is only operational if running on Win32 - which is not +/// checked here (i.e. it is assumed that platform specific dispatching is taking +/// place before using this code). +/// +internal partial class ClipboardHelperWin32 : IClipboardHelper +{ + public void PutBitmapIntoClipboard(Bitmap bitmap) + { + if (bitmap == null) + { + throw new ArgumentNullException(nameof(bitmap)); + } + + if (bitmap.Format != PixelFormat.Bgra8888) + { + throw new ArgumentException("Bitmap is expected to be in PixelFormat.Bgra8888, conversion is not implemented"); + } + + nint hBitmap = 0; + nint screenDC = 0; + nint sourceDC = 0; + nint destDC = 0; + nint compatibleBitmap = 0; + bool clipboardOpened = false; + + try + { + hBitmap = ClipboardHelperWin32.CreateHBitmapFromAvalonBitmap(bitmap); + + screenDC = Win32UnmanagedMethods.GetDC(IntPtr.Zero); + if (screenDC == 0) + { + throw new Exception("Error creating screenDC"); + } + + sourceDC = Win32UnmanagedMethods.CreateCompatibleDC(screenDC); + if (sourceDC == 0) + { + throw new Exception("Error creating sourceDC"); + } + + Win32UnmanagedMethods.SelectObject(sourceDC, hBitmap); + + destDC = Win32UnmanagedMethods.CreateCompatibleDC(screenDC); + if (destDC == 0) + { + throw new Exception("Error creating destDC"); + } + + compatibleBitmap = Win32UnmanagedMethods.CreateCompatibleBitmap(screenDC, bitmap.PixelSize.Width, bitmap.PixelSize.Height); + if (compatibleBitmap == 0) + { + throw new Exception("Error creating compatibleBitmap"); + } + + Win32UnmanagedMethods.SelectObject(destDC, compatibleBitmap); + Win32UnmanagedMethods.BitBlt( + destDC, + 0, + 0, + bitmap.PixelSize.Width, + bitmap.PixelSize.Height, + sourceDC, + 0, + 0, + 0x00CC0020); // SRCCOPY + + if (!Win32UnmanagedMethods.OpenClipboard(IntPtr.Zero)) + { + throw new Exception("Error opening the clipboard"); + } + + clipboardOpened = true; + + Win32UnmanagedMethods.EmptyClipboard(); + Win32UnmanagedMethods.SetClipboardData(Win32UnmanagedMethods.ClipboardFormat.CF_BITMAP, compatibleBitmap); + } + finally + { + if (clipboardOpened) + { + Win32UnmanagedMethods.CloseClipboard(); + } + + bool success = false; + if (hBitmap != 0) + { + success = Win32UnmanagedMethods.DeleteObject(hBitmap); + } + + if (compatibleBitmap != 0) + { + success = Win32UnmanagedMethods.DeleteObject(compatibleBitmap); + } + + if (sourceDC != 0) + { + success = Win32UnmanagedMethods.DeleteDC(sourceDC); + } + + if (screenDC != 0) + { + success = Win32UnmanagedMethods.ReleaseDC(IntPtr.Zero, screenDC); + } + + if (destDC != 0) + { + success = Win32UnmanagedMethods.DeleteDC(destDC); + } + + _ = success; + } + } + + private static unsafe nint CreateHBitmapFromAvalonBitmap(Bitmap bitmap) + { + if (bitmap.Format != PixelFormat.Bgra8888) + { + throw new ArgumentException("Bitmap is expected to be in PixelFormat.Bgra8888, conversion is not implemented"); + } + + byte[] buffer = new byte[bitmap.PixelSize.Width * bitmap.PixelSize.Height * 4]; + fixed (byte* p = &buffer[0]) + { + // copy the content of the Avalonia-bitmap into the buffer + bitmap.CopyPixels(new PixelRect(bitmap.PixelSize), new IntPtr(p), buffer.Length, bitmap.PixelSize.Width * 4); + + // now, we allocate an "HBITMAP"-object and copy the data into it + Win32UnmanagedMethods.BITMAPINFO bmi = default; + bmi.bmiHeader.Init(); + bmi.bmiHeader.biWidth = bitmap.PixelSize.Width; + bmi.bmiHeader.biHeight = -bitmap.PixelSize.Height; // top-down + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = Win32UnmanagedMethods.BitmapCompressionMode.BI_RGB; + IntPtr ptr = IntPtr.Zero; + nint hBitmap = Win32UnmanagedMethods.CreateDIBSection( + IntPtr.Zero, + ref bmi, + 0, /*DIB_RGB_COLORS*/ + out ptr, + IntPtr.Zero, + 0); + if (hBitmap == 0) + { + throw new Exception("CreateDIBSection failed."); + } + + int r = Win32UnmanagedMethods.SetDIBits( + IntPtr.Zero, + hBitmap, + 0, + (uint)bitmap.PixelSize.Height, + new IntPtr(p), + ref bmi, + 0); /*DIB_RGB_COLORS*/ + + if (r == 0) + { + Win32UnmanagedMethods.DeleteObject(hBitmap); + throw new Exception("SetDIBits failed."); + } + + return hBitmap; + } + } +} + +/// +/// This part contains the PInvoke definitions we need in order to "put the bitmap into the clipboard" +/// with the Win32-API. +/// +internal partial class ClipboardHelperWin32 +{ + private class Win32UnmanagedMethods + { + public enum BitmapCompressionMode : uint + { + BI_RGB = 0, + BI_RLE8 = 1, + BI_RLE4 = 2, + BI_BITFIELDS = 3, + BI_JPEG = 4, + BI_PNG = 5, + } + + public enum ClipboardFormat + { + /// + /// Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data. Use this format for ANSI text. + /// + CF_TEXT = 1, + + /// + /// A handle to a bitmap. + /// + CF_BITMAP = 2, + + /// + /// A memory object containing a BITMAPINFO structure followed by the bitmap bits. + /// + CF_DIB = 3, + + /// + /// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data. + /// + CF_UNICODETEXT = 13, + + /// + /// A handle to type HDROP that identifies a list of files. + /// + CF_HDROP = 15, + } + + [DllImport("gdi32.dll")] + public static extern IntPtr CreateDIBSection(IntPtr hdc, [In] ref BITMAPINFO pbmi, uint pila, out IntPtr ppvBits, IntPtr hSection, uint dwOffset); + + [DllImport("gdi32.dll")] + public static extern int SetDIBits(IntPtr hdc, nint hbm, uint start, uint cLines, IntPtr lpBits, [In] ref BITMAPINFO pbmi, uint colorUse); + + [DllImport("user32.dll", ExactSpelling = true)] + public static extern IntPtr GetDC(IntPtr hWnd); + + [DllImport("gdi32.dll", EntryPoint = "DeleteDC")] + public static extern bool DeleteDC([In] IntPtr hdc); + + [DllImport("gdi32.dll", ExactSpelling = true)] + public static extern IntPtr CreateCompatibleDC(IntPtr hDC); + + [DllImport("gdi32.dll", SetLastError = true, ExactSpelling = true)] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr h); + + [DllImport("gdi32.dll", SetLastError = true, ExactSpelling = true)] + public static extern bool BitBlt( + IntPtr hdc, + int x, + int y, + int cx, + int cy, + IntPtr hdcSrc, + int x1, + int y1, + uint rop); + + [DllImport("gdi32.dll", ExactSpelling = true)] + public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int cx, int cy); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool OpenClipboard(IntPtr hWndOwner); + + [DllImport("user32.dll")] + public static extern bool EmptyClipboard(); + + [DllImport("user32.dll")] + public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC); + + [DllImport("user32.dll")] + public static extern IntPtr SetClipboardData(ClipboardFormat uFormat, IntPtr hMem); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool CloseClipboard(); + + [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeleteObject([In] IntPtr hObject); + + [StructLayout(LayoutKind.Sequential)] + public struct BITMAPINFOHEADER + { +#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter + public uint biSize; + public int biWidth; + public int biHeight; + public ushort biPlanes; + public ushort biBitCount; + public BitmapCompressionMode biCompression; + public uint biSizeImage; + public int biXPelsPerMeter; + public int biYPelsPerMeter; + public uint biClrUsed; + public uint biClrImportant; +#pragma warning restore SA1307 // Accessible fields should begin with upper-case letter + + public void Init() + { + this.biSize = (uint)Marshal.SizeOf(this); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct RGBQUAD + { +#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter + public byte rgbBlue; + public byte rgbGreen; + public byte rgbRed; + public byte rgbReserved; +#pragma warning restore SA1307 // Accessible fields should begin with upper-case letter + } + + [StructLayoutAttribute(LayoutKind.Sequential)] + public struct BITMAPINFO + { +#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter + /// + /// A BITMAPINFOHEADER structure that contains information about the dimensions of color format. + /// + public BITMAPINFOHEADER bmiHeader; + + /// + /// An array of RGBQUAD. The elements of the array that make up the color table. + /// + [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = UnmanagedType.Struct)] + public RGBQUAD[] bmiColors; +#pragma warning restore SA1307 // Accessible fields should begin with upper-case letter + } + } +} diff --git a/czishrink/netczicompress/Models/Clipboard/IClipboardHelper.cs b/czishrink/netczicompress/Models/Clipboard/IClipboardHelper.cs new file mode 100644 index 0000000..3377885 --- /dev/null +++ b/czishrink/netczicompress/Models/Clipboard/IClipboardHelper.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models.Clipboard; + +using Avalonia.Media.Imaging; + +/// +/// Interface for utilities for interacting with the clipboard. +/// Unfortunately, the platform agnostic support in Avalonia is quite limited at this time, +/// so the idea here is to provide just enough functionality with platform specific implementations. +/// +public interface IClipboardHelper +{ + /// + /// Puts the specified bitmap into the system clipboard. It is a simplistic implementation, + /// the data is copied right away, meaning that the bitmap should not be large in size, a couple of + /// kilobytes is fine. Also, the pixel format of the bitmap must be PixelFormat.Bgra8888, there is + /// no conversion in place currently. + /// + /// The bitmap to put on the clipboard. + void PutBitmapIntoClipboard(Bitmap bitmap); +} diff --git a/czishrink/netczicompress/Models/CompositeObserver.cs b/czishrink/netczicompress/Models/CompositeObserver.cs new file mode 100644 index 0000000..1078ab5 --- /dev/null +++ b/czishrink/netczicompress/Models/CompositeObserver.cs @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; + +/// +/// Static class to create composite observers. +/// +public static class CompositeObserver +{ + public static IObserver Create(params IObserver[] observers) + { + return new CompositeObserverImpl(observers); + } + + /// + /// A composite . + /// + /// The observed type. + public sealed class CompositeObserverImpl : IObserver + { + private readonly IObserver[] observers; + + public CompositeObserverImpl(params IObserver[] observers) + { + this.observers = observers; + } + + public void OnCompleted() + { + this.ForEachObserver(o => o.OnCompleted()); + } + + public void OnError(Exception error) + { + this.ForEachObserver(o => o.OnError(error)); + } + + public void OnNext(T value) + { + this.ForEachObserver(o => o.OnNext(value)); + } + + private void ForEachObserver(Action> action) + { + foreach (var item in this.observers) + { + action(item); + } + } + } +} \ No newline at end of file diff --git a/czishrink/netczicompress/Models/CompressionLevel.cs b/czishrink/netczicompress/Models/CompressionLevel.cs new file mode 100644 index 0000000..4bf99cb --- /dev/null +++ b/czishrink/netczicompress/Models/CompressionLevel.cs @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System.ComponentModel.DataAnnotations; + +/// +/// Defines Zstd compression levels. +/// +public record CompressionLevel +{ + private readonly int internalValue = DefaultValue; + + public static readonly CompressionLevel Default; + + static CompressionLevel() + { + Default = new CompressionLevel() { Value = DefaultValue }; + } + + /// + /// Gets the minimum allowed compression level. + /// + /// In this application the value is 0, though Zstd would allow values down to -131072. + public static int Minimum { get; } = 0; + + /// + /// Gets maximum allowed compression level. + /// + /// The value of this property is 22. + public static int Maximum { get; } = 22; + + /// + /// Gets default compression level if not specified. + /// + /// The value of this property is 1. + public static int DefaultValue { get; } = 1; + + [Range(0, 22)] + public int Value + { + get => this.internalValue; + init => + this.internalValue = value > Maximum || value < Minimum + ? throw new ArgumentOutOfRangeException( + paramName: nameof(value), + message: $"{nameof(value)} must be between {Minimum} and {Maximum}, not {value}") + : value; + } +} \ No newline at end of file diff --git a/czishrink/netczicompress/Models/CompressionMode.cs b/czishrink/netczicompress/Models/CompressionMode.cs new file mode 100644 index 0000000..83fb124 --- /dev/null +++ b/czishrink/netczicompress/Models/CompressionMode.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +/// +/// The compression modes supported by the application. +/// +public enum CompressionMode +{ + CompressUncompressed, + CompressAll, + CompressUncompressedAndZstd, + Decompress, + NoOp, +} diff --git a/czishrink/netczicompress/Models/CompressorMessage.cs b/czishrink/netczicompress/Models/CompressorMessage.cs new file mode 100644 index 0000000..ebb9a5b --- /dev/null +++ b/czishrink/netczicompress/Models/CompressorMessage.cs @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.IO.Abstractions; + +/// +/// A message containing information about the compression of a file. +/// This is a Union Type. +/// +public record CompressorMessage +{ + private CompressorMessage() + { + } + + /// Message emitted when we have finished processing a file. + /// The file currently being processed. + /// The input size of the file. + /// The output size of the file. + /// The time elapsed processing the file. + /// An optional error message. + public record FileFinished(IFileInfo InputFile, long SizeInput, long SizeOutput, TimeSpan? TimeElapsed, + string? ErrorMessage) + : CompressorMessage + { + public decimal SizeRatio { get; } = SizeInput != 0 ? (decimal)SizeOutput / SizeInput : 0; + + public long SizeDelta { get; } = SizeOutput - SizeInput; + } + + /// + /// Message emitted when we start processing a file. + /// + /// The input file. + /// An observable that reports the progress in percent. + public record FileStarting(IFileInfo InputFile, IObservable ProgressPercent) + : CompressorMessage; +} diff --git a/czishrink/netczicompress/Models/CreateProcessor.cs b/czishrink/netczicompress/Models/CreateProcessor.cs new file mode 100644 index 0000000..b07f1dc --- /dev/null +++ b/czishrink/netczicompress/Models/CreateProcessor.cs @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +public delegate IFileProcessor CreateProcessor(CompressionMode mode, ProcessingOptions options); \ No newline at end of file diff --git a/czishrink/netczicompress/Models/CsvLogFileWriter.cs b/czishrink/netczicompress/Models/CsvLogFileWriter.cs new file mode 100644 index 0000000..8ce02e0 --- /dev/null +++ b/czishrink/netczicompress/Models/CsvLogFileWriter.cs @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.Diagnostics; +using System.IO; + +/// +/// Writes a sequence of messages to a CSV file. +/// +public class CsvLogFileWriter : IObserver +{ + private bool isCompleted; + + public CsvLogFileWriter(TextWriter writer) + { + this.Writer = writer; + this.WriteHeader(); + } + + public TextWriter Writer { get; } + + public void OnCompleted() + { + lock (this.Writer) + { + this.isCompleted = true; + this.Writer.Flush(); + this.Writer.Close(); + } + } + + public void OnError(Exception error) + { + this.OnCompleted(); + } + + public void OnNext(CompressorMessage.FileFinished value) + { + this.Write(ToLine(value)); + } + + private static string ToLine(CompressorMessage.FileFinished value) + { + return string.Join( + ',', + Quote(value.InputFile.FullName), + value.SizeInput, + value.SizeOutput, + value.ErrorMessage == null ? value.SizeRatio : string.Empty, + value.ErrorMessage == null ? value.SizeDelta : string.Empty, + value.TimeElapsed?.ToString("c") ?? string.Empty, + value.ErrorMessage == null ? "SUCCESS" : "ERROR", + Quote(value.ErrorMessage)) + "\r\n"; + } + + private static string Quote(string? value) + { + if (value == null) + { + return string.Empty; + } + + return '"' + value.Replace("\"", "\"\"") + '"'; + } + + private void WriteHeader() + { + this.Write("InputFile,SizeInput,SizeOutput,SizeRatio,SizeDelta,TimeToProcess,Status,ErrorMessage\r\n"); + } + + private void Write(string line) + { + lock (this.Writer) + { + if (this.isCompleted) + { + Debug.WriteLine($"{nameof(CsvLogFileWriter)}: received a message after completion..."); + return; + } + + this.Writer.Write(line); + } + } +} diff --git a/czishrink/netczicompress/Models/CsvLoggingStrategy.cs b/czishrink/netczicompress/Models/CsvLoggingStrategy.cs new file mode 100644 index 0000000..feae883 --- /dev/null +++ b/czishrink/netczicompress/Models/CsvLoggingStrategy.cs @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.IO; +using System.IO.Abstractions; +using System.Reactive.Concurrency; + +/// +/// A logging strategy that writes data to a CSV log file. +/// +public class CsvLoggingStrategy : ILoggingStrategy +{ + private readonly IFileLauncher fileLauncher; + + public CsvLoggingStrategy(IFileLauncher fileLauncher) + { + this.fileLauncher = fileLauncher; + } + + /// + /// Gets the scheduler to use for logging. + /// + public IScheduler LoggerScheduler { get; init; } = DefaultScheduler.Instance; + + public IFileInfo CreateLogFile(FolderCompressorParameters run) + { + var outDir = run.OutputDir; + var fs = outDir.FileSystem; + IFileInfo result; + do + { + var outFileName = fs.Path.Combine( + outDir.FullName, + $"CziShrink_{this.LoggerScheduler.Now.ToLocalTime():yyyyMMdd'T'HHmmss}_{run.Mode}.csv"); + + result = fs.FileInfo.New(outFileName); + } + while (result.Exists); + + return result; + } + + public IObserver CreateLogger(TextWriter writer) + { + return new CsvLogFileWriter(writer); + } + + public Task OpenLogFile(IFileInfo logFile) + { + return this.fileLauncher.Launch(logFile.FullName); + } +} diff --git a/czishrink/netczicompress/Models/ExecutionOptions.cs b/czishrink/netczicompress/Models/ExecutionOptions.cs new file mode 100644 index 0000000..4c7033c --- /dev/null +++ b/czishrink/netczicompress/Models/ExecutionOptions.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +/// +/// Execution options to apply to operations. +/// +/// The maximum number of threads to use. +public record ExecutionOptions(ThreadCount ThreadCount); \ No newline at end of file diff --git a/czishrink/netczicompress/Models/FileLauncher.cs b/czishrink/netczicompress/Models/FileLauncher.cs new file mode 100644 index 0000000..b9e497c --- /dev/null +++ b/czishrink/netczicompress/Models/FileLauncher.cs @@ -0,0 +1,144 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System.Diagnostics; +using System.IO.Abstractions; + +/// +/// Launches a file with its associated app, +/// or displays a file in its folder with the operating system's file manager. +/// +public class FileLauncher : IFileLauncher +{ + // Cf. https://github.com/dotnet/runtime/blob/52806bc157decf345005249c9ea7969c8b9e7e1b/src/libraries/System.Private.CoreLib/src/System/OperatingSystem.cs#L14C9-L41C6 + private const string Windows = "WINDOWS"; + private const string Osx = "OSX"; + private const string Linux = "LINUX"; + private const string FreeBsd = "FREEBSD"; + private const string NetBsd = "NETBSD"; + private const string Solaris = "SOLARIS"; + + private const string Unsupported = "UNSUPPORTED"; + private static readonly string[] SupportedPlatforms = + { + Windows, + Osx, + Linux, + FreeBsd, + NetBsd, + Solaris, + }; + + private readonly IFileSystem fs; + private readonly Func getEnvironmentVariable; + + public FileLauncher(IFileSystem fs, Func getEnvironmentVariable) + { + this.fs = fs; + this.getEnvironmentVariable = getEnvironmentVariable; + } + + protected string CurrentPlatform { get; init; } = GetPlatform(); + + public Task Launch(string path) + { + return this.RunAsync(GetLaunchStartInfo(path)); + } + + // Adapted from https://stackoverflow.com/a/73409251 + public async Task Reveal(string path) + { + var startInfo = this.GetRevealStartInfo(path); + var exitCode = await this.RunAsync(startInfo); + + if (exitCode != 0 && startInfo.FileName == "dbus-send") + { + // dbus-send was unsuccessful + await this.RunAsync(this.GetOpenParentFolderStartInfo(path)); + } + } + + protected virtual async Task RunAsync(ProcessStartInfo processStartInfo) + { + using var process = new Process { StartInfo = processStartInfo }; + process.Start(); + await process.WaitForExitAsync(); + return process.ExitCode; + } + + protected virtual string PathToFileUri(string path) + { + return new Uri(path).AbsoluteUri; + } + + private static string GetPlatform() + { + return SupportedPlatforms.FirstOrDefault(x => OperatingSystem.IsOSPlatform(x)) ?? Unsupported; + } + + private static ProcessStartInfo GetRevealStartInfoOsx(string path) + { + return new ProcessStartInfo + { + FileName = "explorer", + Arguments = $"-R \"{path}\"", + }; + } + + private static ProcessStartInfo GetLaunchStartInfo(string? path) + { + return new ProcessStartInfo + { + FileName = path, + UseShellExecute = true, + }; + } + + private ProcessStartInfo GetRevealStartInfoWindows(string path) + { + var fileName = "explorer"; + var sysRoot = this.getEnvironmentVariable("SYSTEMROOT"); + if (sysRoot != null) + { + fileName = this.fs.Path.Combine(sysRoot, fileName); + } + + return new ProcessStartInfo + { + FileName = fileName, + Arguments = $"/select,\"{path}\"", + }; + } + + private ProcessStartInfo GetRevealStartInfoDBus(string path) + { + string fileUri = this.PathToFileUri(path); + return new ProcessStartInfo() + { + FileName = "dbus-send", + Arguments = $"--print-reply --dest=org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1.ShowItems array:string:\"{fileUri}\" string:\"\"", + UseShellExecute = true, + }; + } + + private ProcessStartInfo GetOpenParentFolderStartInfo(string path) + { + return GetLaunchStartInfo(this.fs.Path.GetDirectoryName(path)); + } + + private ProcessStartInfo GetRevealStartInfo(string path) + { + Func core = this.CurrentPlatform switch + { + Windows => this.GetRevealStartInfoWindows, + Osx => GetRevealStartInfoOsx, + Unsupported => this.GetOpenParentFolderStartInfo, + _ => this.GetRevealStartInfoDBus, + }; + + return core(path); + } +} diff --git a/czishrink/netczicompress/Models/FileProcessingFailedHandler.cs b/czishrink/netczicompress/Models/FileProcessingFailedHandler.cs new file mode 100644 index 0000000..737caea --- /dev/null +++ b/czishrink/netczicompress/Models/FileProcessingFailedHandler.cs @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System.IO.Abstractions; + +/// +/// A handler which deals with the case when a file processing fails. +/// +public class FileProcessingFailedHandler +{ + public const string SuccessfulCopyMessage = "The file was copied as-is to the output folder."; + public const string FailedCopyMessage = "The file could not be copied to the output folder."; + + /// + /// Handles the case when a file processing has failed. + /// + /// The input file. + /// The output file. + /// The temporary output file. + /// The delegate to report progress. + /// The inner exception, that has occurred. + /// A value indicating whether the failed file should by copied. + /// The cancellation token. + /// A message which described how the failed file processing was handled. + /// Thrown when any argument is null. + public string FileProcessingFailed( + IFileInfo inFile, + IFileInfo outFile, + IFileInfo tempOutFile, + ReportProgress reportProgress, + string innerException, + bool copyFailedFile, + CancellationToken cancellationToken) + { + if (copyFailedFile) + { + return CopyFile(inFile, outFile, tempOutFile, reportProgress, cancellationToken) + ? $"{innerException} {SuccessfulCopyMessage}" + : $"{innerException} {FailedCopyMessage}"; + } + + return innerException; + } + + private static bool TryDelete(IFileInfo fileInfo) + { + try + { + fileInfo.Delete(); + } + catch (Exception) + { + return false; + } + + return true; + } + + private static bool CopyFile( + IFileInfo inFile, + IFileInfo outFile, + IFileInfo tempOutFile, + ReportProgress reportProgress, + CancellationToken cancellationToken) + { + try + { + CopyWithProgress(inFile.FullName, tempOutFile.FullName, reportProgress, cancellationToken); + tempOutFile.Refresh(); + tempOutFile.MoveTo(outFile.FullName); + return true; + } + catch (Exception) + { + // Remove partially written file + _ = TryDelete(outFile); + _ = TryDelete(tempOutFile); + return false; + } + } + + private static void CopyWithProgress( + string inPath, + string outPath, + ReportProgress reportProgress, + CancellationToken cancellationToken) + { + long totalBytesWritten = 0; + int bufferLength = 1024 * 1024; + byte[] buffer = new byte[bufferLength]; + using var inStream = new FileStream(inPath, FileMode.Open); + using var tempOutStream = new FileStream(outPath, FileMode.CreateNew); + + int bytesRead = inStream.Read(buffer, 0, bufferLength); + + do + { + cancellationToken.ThrowIfCancellationRequested(); + + tempOutStream.Write(buffer, 0, bytesRead); + totalBytesWritten += bytesRead; + + int progress = inStream.Length != 0 ? (int)(((totalBytesWritten * 1.0) / inStream.Length) * 100) : 100; + reportProgress.Invoke(progress); + + bytesRead = inStream.Read(buffer, 0, bufferLength); + } + while (bytesRead > 0); + } +} diff --git a/czishrink/netczicompress/Models/FolderCompressorDecorator.cs b/czishrink/netczicompress/Models/FolderCompressorDecorator.cs new file mode 100644 index 0000000..0d2f026 --- /dev/null +++ b/czishrink/netczicompress/Models/FolderCompressorDecorator.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.Threading; + +/// +/// Decorates an with configurable output observation. +/// +public class FolderCompressorDecorator : IFolderCompressor +{ + /// + /// Gets the core instance to wrap. + /// + public required IFolderCompressor Core { get; init; } + + /// + /// Gets the action that is used to start observing the output of the folder compressor. + /// + /// The observable that is passed to will terminate if the + /// returned is started. + /// In that case, their is no need to explicitly dispose subscriptions. + /// + public required Action> ObserveRun { get; init; } + + public IFolderCompressorRun PrepareNewRun(FolderCompressorParameters parameters, CancellationToken token) + { + var result = this.Core.PrepareNewRun(parameters, token); + this.ObserveRun(parameters, result.Output); + return result; + } +} \ No newline at end of file diff --git a/czishrink/netczicompress/Models/FolderCompressorExtensions.cs b/czishrink/netczicompress/Models/FolderCompressorExtensions.cs new file mode 100644 index 0000000..33c9010 --- /dev/null +++ b/czishrink/netczicompress/Models/FolderCompressorExtensions.cs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.Reactive.Concurrency; +using System.Reactive.Linq; + +/// +/// Extensions for . +/// +public static class FolderCompressorExtensions +{ + public static IFolderCompressor Decorate(this IFolderCompressor self, Action> observeRun) + where TMessage : CompressorMessage + { + return new FolderCompressorDecorator + { + Core = self, + ObserveRun = (parameters, output) => observeRun(parameters, output.OfType()), + }; + } + + public static IFolderCompressor DecorateWithRunObserver(this IFolderCompressor self, IFolderCompressorRunObserver runObserver) + where TMessage : CompressorMessage + { + return self.Decorate(runObserver.ObserveRun); + } + + public static IFolderCompressor DecorateWithObserver(this IFolderCompressor self, IObserver observer, IScheduler scheduler) + where TMessage : CompressorMessage + { + return self.Decorate((parameters, output) => output.ObserveOn(scheduler).Subscribe(observer)); + } +} diff --git a/czishrink/netczicompress/Models/FolderCompressorParameters.cs b/czishrink/netczicompress/Models/FolderCompressorParameters.cs new file mode 100644 index 0000000..008fe52 --- /dev/null +++ b/czishrink/netczicompress/Models/FolderCompressorParameters.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System.IO.Abstractions; + +/// +/// A parameter-object that contains the parameters for . +/// +/// The input directory. +/// The output directory. +/// True if the input directory should be scanned recursively, else false. +/// The compression mode. +/// Execution options to apply to operations. +/// Additional processing options. +public record FolderCompressorParameters( + IDirectoryInfo InputDir, + IDirectoryInfo OutputDir, + bool Recursive, + CompressionMode Mode, + ExecutionOptions ExecutionOptions, + ProcessingOptions ProcessingOptions); \ No newline at end of file diff --git a/czishrink/netczicompress/Models/IFileLauncher.cs b/czishrink/netczicompress/Models/IFileLauncher.cs new file mode 100644 index 0000000..adc5703 --- /dev/null +++ b/czishrink/netczicompress/Models/IFileLauncher.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +/// +/// A service that launches a file with its associated app. +/// +public interface IFileLauncher +{ + /// + /// Launches the specified file or folder with its associated app. + /// + /// The file or folder to launch. + /// A task that represents the operation. + Task Launch(string path); + + /// + /// Shows the specified file or folder in the operating system's file manager. + /// + /// The file or folder to show. + /// A task that represents the operation. + Task Reveal(string path); +} \ No newline at end of file diff --git a/czishrink/netczicompress/Models/IFileProcessor.cs b/czishrink/netczicompress/Models/IFileProcessor.cs new file mode 100644 index 0000000..1b74ebc --- /dev/null +++ b/czishrink/netczicompress/Models/IFileProcessor.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.Threading; + +/// +/// An object that can process a single file. +/// +public interface IFileProcessor : IDisposable +{ + /// + /// Gets a value indicating whether the file processor needs the output directory to exist when + /// is called. + /// + bool NeedsExistingOutputDirectory { get; } + + /// + /// Processes a single input file to a single output file. + /// + /// The absolute path of the input file. + /// The absolute path of the output file. + /// The progress delegate. + /// The cancellation token. + void ProcessFile(string inputPath, string outputPath, ReportProgress progressReport, CancellationToken token); +} diff --git a/czishrink/netczicompress/Models/IFolderCompressor.cs b/czishrink/netczicompress/Models/IFolderCompressor.cs new file mode 100644 index 0000000..e14de5e --- /dev/null +++ b/czishrink/netczicompress/Models/IFolderCompressor.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System.Threading; + +/// +/// An object that can compress files in an input folder to an output folder. +/// +public interface IFolderCompressor +{ + /// + /// Prepare a new compressor run with the specified parameters. + /// + /// Parameters of the compression operation that is to be performed. + /// The cancellation token. + /// A new compressor run. + /// + /// The actual compression is started when the method is called on the returned object. + /// + IFolderCompressorRun PrepareNewRun(FolderCompressorParameters parameters, CancellationToken token); +} diff --git a/czishrink/netczicompress/Models/IFolderCompressorRunObserver.cs b/czishrink/netczicompress/Models/IFolderCompressorRunObserver.cs new file mode 100644 index 0000000..77cee98 --- /dev/null +++ b/czishrink/netczicompress/Models/IFolderCompressorRunObserver.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; + +/// +/// An object that can observe folder compressor runs. +/// +/// The type of messages to observe. +public interface IFolderCompressorRunObserver + where TMessage : CompressorMessage +{ + void ObserveRun(FolderCompressorParameters runParameters, IObservable runMessages); +} diff --git a/czishrink/netczicompress/Models/ILoggingStrategy.cs b/czishrink/netczicompress/Models/ILoggingStrategy.cs new file mode 100644 index 0000000..24a0dc0 --- /dev/null +++ b/czishrink/netczicompress/Models/ILoggingStrategy.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.IO; +using System.IO.Abstractions; +using System.Reactive.Concurrency; + +/// +/// A text-file-based logging strategy. +/// +public interface ILoggingStrategy +{ + public IScheduler LoggerScheduler { get; } + + public IFileInfo CreateLogFile(FolderCompressorParameters run); + + public IObserver CreateLogger(TextWriter writer); + + public Task OpenLogFile(IFileInfo file); +} diff --git a/czishrink/netczicompress/Models/IObservableAction.cs b/czishrink/netczicompress/Models/IObservableAction.cs new file mode 100644 index 0000000..18be592 --- /dev/null +++ b/czishrink/netczicompress/Models/IObservableAction.cs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; + +/// +/// An action that produces a sequence of values. +/// +/// The type of the produced items. +public interface IObservableAction +{ + /// + /// Gets the output sequence. + /// + /// + /// This observable will terminate when the action completes. + /// + IObservable Output { get; } + + /// + /// Starts the action. The must complete when the action has finished. + /// + /// + /// This method can only be called once. + /// + /// Thrown when this method is called more than once. + void Start(); +} \ No newline at end of file diff --git a/czishrink/netczicompress/Models/IProgramNameAndVersion.cs b/czishrink/netczicompress/Models/IProgramNameAndVersion.cs new file mode 100644 index 0000000..be93afe --- /dev/null +++ b/czishrink/netczicompress/Models/IProgramNameAndVersion.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +/// +/// An object that provides program name and version info. +/// +public interface IProgramNameAndVersion +{ + string Name { get; } + + string Version { get; } +} \ No newline at end of file diff --git a/czishrink/netczicompress/Models/MultiThreadedFolderCompressor.cs b/czishrink/netczicompress/Models/MultiThreadedFolderCompressor.cs new file mode 100644 index 0000000..37ede24 --- /dev/null +++ b/czishrink/netczicompress/Models/MultiThreadedFolderCompressor.cs @@ -0,0 +1,267 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.IO.Abstractions; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +/// +/// A multi-threaded that uses s to do the actual work. +/// +public class MultiThreadedFolderCompressor : IFolderCompressor +{ + private readonly CreateProcessor createProcessor; + private readonly FileProcessingFailedHandler fileProcessingFailedHandler; + private readonly int maxTriesToCreateTemporaryFile; + + public MultiThreadedFolderCompressor( + CreateProcessor createProcessor, + FileProcessingFailedHandler fileProcessingFailedHandler, + int maxTriesToCreateTemporaryFiles = 100) + { + this.createProcessor = createProcessor; + this.fileProcessingFailedHandler = fileProcessingFailedHandler ?? throw new ArgumentNullException(nameof(fileProcessingFailedHandler)); + this.maxTriesToCreateTemporaryFile = maxTriesToCreateTemporaryFiles; + } + + // Public only for testing. MockFileSystem does not support CaseInsensitive + public MatchCasing MatchExtensionCasing { get; init; } = MatchCasing.CaseInsensitive; + + // Public only for testing. MockFileSystem does not support AttributesToSkip + public FileAttributes AttributesToSkip { get; init; } = FileAttributes.Hidden; + + /// + public IFolderCompressorRun PrepareNewRun(FolderCompressorParameters parameters, CancellationToken token) + { + return new AsyncEnumerableToObservableActionAdapter(CoreAsync(token), token); + + async IAsyncEnumerable CoreAsync([EnumeratorCancellation] CancellationToken t = default) + { + var startTime = DateTime.UtcNow; + + var (inputDir, outputDir, recursive, mode, executionOptions, processingOptions) = parameters; + + var maxNumberOfThreads = executionOptions.ThreadCount.Value; + + var processors = new List(maxNumberOfThreads); + try + { + var activeTasks = new List>(maxNumberOfThreads); + await foreach (var file in this.EnumerateCzisAsync(inputDir, recursive, t).WithCancellation(t).ConfigureAwait(false)) + { + if (file.CreationTimeUtc >= startTime) + { + // skip files that we may have created ourselves + continue; + } + + if (t.IsCancellationRequested) + { + // Don't ThrowIfCancellationRequested here, + // we first need to wait for activeTasks to complete. + break; + } + + var thisFile = file; + var progressObservable = new BehaviorSubject(0); + + yield return new CompressorMessage.FileStarting( + thisFile, + progressObservable.DistinctUntilChanged().AsObservable()); + + IFileInfo outFile = GetOutputFileInfo(inputDir, outputDir, file); + + if (activeTasks.Count < maxNumberOfThreads) + { + var processor = this.createProcessor(mode, processingOptions); + processors.Add(processor); + activeTasks.Add(Run(processor)); + } + else + { + int index = await WaitForTaskToComplete(activeTasks).ConfigureAwait(false); + yield return await activeTasks[index].ConfigureAwait(false); + activeTasks[index].Dispose(); + activeTasks[index] = Run(processors[index]); + } + + Task Run(IFileProcessor p) => Task.Run(() => + { + try + { + return this.ProcessFile( + p, + thisFile, + outFile, + processingOptions.CopyFailedFiles, + progressObservable.OnNext, + t); + } + finally + { + // Make sure that we deliver 100 percent progress. + // This is used by observers to detect completion. + progressObservable.OnNext(100); + } + }); + } + + // No more new tasks will be started. + // Now wait for the active tasks to complete. + while (activeTasks.Count != 0) + { + int index = await WaitForTaskToComplete(activeTasks).ConfigureAwait(false); + yield return await activeTasks[index].ConfigureAwait(false); + activeTasks[index].Dispose(); + activeTasks.RemoveAt(index); + } + + t.ThrowIfCancellationRequested(); + } + finally + { + foreach (var processor in processors) + { + processor.Dispose(); + } + } + } + } + + protected virtual IAsyncEnumerable EnumerateCzisAsync( + IDirectoryInfo folder, + bool recursive, + CancellationToken token) + { + var opts = new EnumerationOptions + { + IgnoreInaccessible = true, + MatchCasing = this.MatchExtensionCasing, + RecurseSubdirectories = recursive, + BufferSize = 1 << 16, + AttributesToSkip = this.AttributesToSkip, + }; + + return folder.EnumerateFilesAsync("*.czi", opts, token); + } + + private static void EnsureParentDirectoryExists(IFileInfo outFile) + { + var outputDir = outFile.Directory; + if (outputDir?.Exists == false) + { + outputDir.Create(); + } + } + + private static async Task WaitForTaskToComplete(IReadOnlyList tasks) + { + while (true) + { + for (int i = 0; i < tasks.Count; i++) + { + if (tasks[i].IsCompleted) + { + return i; + } + } + + await Task.WhenAny(tasks).ConfigureAwait(false); + } + } + + private static IFileInfo GetOutputFileInfo(IDirectoryInfo inputDir, IDirectoryInfo outputDir, IFileInfo file) + { + var inputFileSystem = inputDir.FileSystem; + var outputFileSystem = outputDir.FileSystem; + var relativePath = inputFileSystem.Path.GetRelativePath(inputDir.FullName, file.FullName); + var outputPath = outputFileSystem.Path.Combine(outputDir.FullName, relativePath); + var outFile = outputFileSystem.FileInfo.New(outputPath); + return outFile; + } + + private CompressorMessage.FileFinished ProcessFile( + IFileProcessor processor, + IFileInfo inFile, + IFileInfo outFile, + bool copyFailedFile, + ReportProgress reportProgress, + CancellationToken token) + { + TemporaryFile? temporaryFile = null; + bool processedByProcessor = false; + try + { + if (token.IsCancellationRequested) + { + return MakeTheReturnValue(new OperationCanceledException().Message); + } + + if (outFile.Exists) + { + return MakeTheReturnValue( + errorMessage: $"Output file {outFile} already exists."); + } + + if (processor.NeedsExistingOutputDirectory) + { + EnsureParentDirectoryExists(outFile); + } + + temporaryFile = new TemporaryFile(outFile, this.maxTriesToCreateTemporaryFile); + if (temporaryFile.TemporaryFileCreationFailed) + { + var tempOutFile = temporaryFile.Info; + return MakeTheReturnValue( + errorMessage: $"Could not delete existing temporary file {tempOutFile}."); + } + + var temporaryOutFile = temporaryFile.Info; + var startTimestamp = Stopwatch.GetTimestamp(); + processor.ProcessFile(inFile.FullName, temporaryOutFile.FullName, reportProgress, token); + processedByProcessor = true; + var elapsedTime = Stopwatch.GetElapsedTime(startTimestamp); + temporaryFile.MoveToOutFileIfExists(); + return MakeTheReturnValue(timeElapsed: elapsedTime, errorMessage: null); + } + catch (Exception ex) when (ex is IOException or OperationCanceledException) + { + // Remove partially written file + temporaryFile?.DeleteAllOutFiles(); + + if (temporaryFile == null || processedByProcessor || ex is OperationCanceledException) + { + return MakeTheReturnValue(errorMessage: ex.Message); + } + + // We only cover those exceptions, that have been reported when processing the file, i.e. processedByProcessor is false. + var tempOutFile = temporaryFile.Info; + return MakeTheReturnValue( + this.fileProcessingFailedHandler.FileProcessingFailed( + inFile, + outFile, + tempOutFile, + reportProgress, + ex.Message, + copyFailedFile, + token)); + } + + CompressorMessage.FileFinished MakeTheReturnValue(string? errorMessage, TimeSpan? timeElapsed = null) + { + long inSize = inFile.Exists ? inFile.Length : 0; + long outSize = outFile.Exists ? outFile.Length : 0; + return new CompressorMessage.FileFinished(inFile, inSize, outSize, timeElapsed, errorMessage); + } + } +} diff --git a/czishrink/netczicompress/Models/NoOpFileProcessor.cs b/czishrink/netczicompress/Models/NoOpFileProcessor.cs new file mode 100644 index 0000000..3dac416 --- /dev/null +++ b/czishrink/netczicompress/Models/NoOpFileProcessor.cs @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System.Threading; + +/// +/// An that does not do anything. +/// +public sealed class NoOpFileProcessor : IFileProcessor +{ + public bool NeedsExistingOutputDirectory => false; + + public void ProcessFile(string inputPath, string outputPath, ReportProgress progressReport, CancellationToken token) + { + progressReport(100); + } + + public void Dispose() + { + } +} diff --git a/czishrink/netczicompress/Models/ObservableMixin.CompleteOnError.cs b/czishrink/netczicompress/Models/ObservableMixin.CompleteOnError.cs new file mode 100644 index 0000000..f37a52a --- /dev/null +++ b/czishrink/netczicompress/Models/ObservableMixin.CompleteOnError.cs @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +/// +/// Extensions for observable sequences. +/// +public static partial class ObservableMixin +{ + /// + /// Creates an observable that will call + /// when the current sequence is terminated with an error. + /// + /// + /// This should be equivalent to obs.Catch(Observable.Empty<T>), + /// but I had problems (NullReferenceExceptions) with that code in my tests and so chose to + /// write this extension. + /// + /// The type of the items in the sequence. + /// The source sequence. + /// The wrapped source sequence. + public static IObservable CompleteOnError(this IObservable obs) + { + return new CompleteOnErrorDecorator(obs); + } + + public class CompleteOnErrorDecorator : IObservable + { + private readonly IObservable core; + + public CompleteOnErrorDecorator(IObservable core) + { + this.core = core; + } + + public IDisposable Subscribe(IObserver observer) + { + return this.core.Subscribe( + onNext: observer.OnNext, + onCompleted: observer.OnCompleted, + onError: ex => observer.OnCompleted()); + } + } +} diff --git a/czishrink/netczicompress/Models/PInvokeFileProcessor.cs b/czishrink/netczicompress/Models/PInvokeFileProcessor.cs new file mode 100644 index 0000000..8a71f1c --- /dev/null +++ b/czishrink/netczicompress/Models/PInvokeFileProcessor.cs @@ -0,0 +1,222 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.Buffers; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +/// +/// An that uses the native libczicompressc C API to do the actual work. +/// +public sealed partial class PInvokeFileProcessor : IFileProcessor +{ + private IntPtr nativeFileProcessor; + + /// + /// Initializes a new instance of the class. + /// + /// The compression mode to use, not all modes may be supported. + /// The processing mode to use. + /// Thrown when is . + /// Thrown when the native file processor cannot be created. + public PInvokeFileProcessor(CompressionMode mode, ProcessingOptions options) + { + if (mode == CompressionMode.NoOp) + { + throw new ArgumentOutOfRangeException(nameof(mode)); + } + + NativeMethods.Command cmd = mode switch + { + CompressionMode.Decompress => NativeMethods.Command.Decompress, + _ => NativeMethods.Command.Compress, + }; + + NativeMethods.CompressionStrategy strategy = mode switch + { + CompressionMode.CompressAll => NativeMethods.CompressionStrategy.All, + CompressionMode.CompressUncompressedAndZstd => + NativeMethods.CompressionStrategy.UncompressedAndZStdCompressed, + _ => NativeMethods.CompressionStrategy.OnlyUncompressed, + }; + + this.nativeFileProcessor = NativeMethods.CreateFileProcessor(cmd, strategy, options.CompressionLevel.Value); + + if (this.nativeFileProcessor == IntPtr.Zero) + { + throw new ApplicationException("Failed to create native file processor."); + } + } + + public bool NeedsExistingOutputDirectory => true; + + private bool IsDisposed + { + get => this.nativeFileProcessor == IntPtr.Zero; + } + + public static IFileProcessor Create(CompressionMode mode, ProcessingOptions options) + { + return new PInvokeFileProcessor(mode, options); + } + + public static string GetLibFullName() => NativeMethods.GetLibFullName(); + + public void ProcessFile(string inputPath, string outputPath, ReportProgress progressReport, CancellationToken token) + { + this.CheckDisposed(); + + var errorMessageBuffer = ArrayPool.Shared.Rent(1024); + var errorMessageLength = errorMessageBuffer.Length; + + bool ReportProgressCheckCancelImpl(int progress) + { + progressReport(progress); + + return !token.IsCancellationRequested; + } + + var result = NativeMethods.ProcessFile( + this.nativeFileProcessor, + inputPath, + outputPath, + errorMessageBuffer, + ref errorMessageLength, + ReportProgressCheckCancelImpl); + + GC.KeepAlive(this.nativeFileProcessor); + GC.KeepAlive(errorMessageBuffer); + GC.KeepAlive(errorMessageLength); + GC.KeepAlive(progressReport); + + if (result != 0) + { + var message = Encoding.UTF8.GetString(errorMessageBuffer, 0, errorMessageLength); + throw new IOException(message); + } + + // Make sure that we don't report cancellation as success, even if the native lib returns 0 when canceled. + token.ThrowIfCancellationRequested(); + } + + public void Dispose() + { + if (!this.IsDisposed) + { + NativeMethods.DestroyFileProcessor(this.nativeFileProcessor); + } + + this.nativeFileProcessor = IntPtr.Zero; + } + + private void CheckDisposed() + { + if (this.IsDisposed) + { + throw new ObjectDisposedException(this.ToString()); + } + } + + internal static partial class NativeMethods + { + private const string LibName = "libczicompressc"; + private const StringMarshalling LibStringEncoding = StringMarshalling.Utf8; + + static NativeMethods() + { + GetLibVersion(out int major, out int minor, out int patch); + + var actual = (major, minor); + (int Major, int Minor) expected = (0, 5); + + bool isCompatible = major == 0 + ? actual == expected + : major == expected.Major && minor >= expected.Minor; + + if (!isCompatible) + { + throw new InvalidOperationException($"Expecting {LibName} {nameof(GetLibVersion)} to be compatible with {expected} but found {actual}."); + } + +#if !DEBUG + var fullName = GetLibFullName(); + if (fullName.Contains("DEBUG")) + { + throw new InvalidOperationException($"You must not use {fullName} in a Release build of {nameof(netczicompress)}. Use a Release build of the library."); + } +#endif + } + + /// + /// Delegate that is invoked by processing function to provide updates. + /// + /// The progress in percent. + /// True if operation should continue; false if canceled. + public delegate bool ReportProgressCheckCancel(int progressPercent); + + public enum Command + { + Compress = 1, + Decompress = 2, + } + + public enum CompressionStrategy + { + All = 1, + OnlyUncompressed = 2, + UncompressedAndZStdCompressed = 3, + } + + public static string GetLibFullName(int suggestedBufferLength = 64) + { + checked + { + var buffer = ArrayPool.Shared.Rent(suggestedBufferLength); + ulong bufferLength = (ulong)buffer.Length; + if (NativeMethods.GetLibVersionString(buffer, ref bufferLength)) + { + var version = Encoding.UTF8.GetString(buffer, 0, buffer.Count(b => b != 0)); + return $"{LibName} {version}"; + } + else if (bufferLength > (ulong)suggestedBufferLength) + { + // This means that the buffer was too small + return GetLibFullName((int)bufferLength); + } + else + { + throw new Exception($"Failed to determine version of {LibName}"); + } + } + } + + [LibraryImport(LibName, StringMarshalling = LibStringEncoding)] + [return: MarshalAs(UnmanagedType.U1)] + public static partial bool GetLibVersionString(byte[] buffer, ref ulong bufferLength); + + [LibraryImport(LibName, StringMarshalling = LibStringEncoding)] + public static partial void GetLibVersion(out int major, out int minor, out int patch); + + [LibraryImport(LibName, StringMarshalling = LibStringEncoding)] + public static partial IntPtr CreateFileProcessor(Command command, CompressionStrategy strategy, int compressionLevel); + + [LibraryImport(LibName, StringMarshalling = LibStringEncoding)] + public static partial void DestroyFileProcessor(IntPtr file_processor); + + [LibraryImport(LibName, StringMarshalling = LibStringEncoding)] + public static partial int ProcessFile( + IntPtr file_processor, + string input_path, + string output_path, + byte[] error_message, + ref int error_message_length, + ReportProgressCheckCancel reportProgress); + } +} diff --git a/czishrink/netczicompress/Models/ProcessingOptions.cs b/czishrink/netczicompress/Models/ProcessingOptions.cs new file mode 100644 index 0000000..3601073 --- /dev/null +++ b/czishrink/netczicompress/Models/ProcessingOptions.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +/// +/// Processing options to apply to an operation. +/// +/// Level of compression to use, if applicable. +/// A value indicating whether failed files should be copied to the destination folder. +public record ProcessingOptions(CompressionLevel CompressionLevel, bool CopyFailedFiles = false); diff --git a/czishrink/netczicompress/Models/ProgramNameAndVersion.cs b/czishrink/netczicompress/Models/ProgramNameAndVersion.cs new file mode 100644 index 0000000..3ec8f8e --- /dev/null +++ b/czishrink/netczicompress/Models/ProgramNameAndVersion.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.Linq; +using System.Reflection; + +using netczicompress.ViewModels; + +/// +/// Program name and version info. +/// +public record class ProgramNameAndVersion : IProgramNameAndVersion +{ + public ProgramNameAndVersion() + { + var assembly = typeof(AboutViewModel).Assembly; + this.Version = assembly.GetCustomAttributes().Select(att => att.InformationalVersion).FirstOrDefault() ?? + assembly.GetName().Version?.ToString() ?? + throw new ApplicationException("Failed to determine assembly version"); + this.Name = "CZI Shrink"; + } + + public string Name { get; } + + public string Version { get; } + + public override string ToString() + { + return $"{this.Name} {this.Version}"; + } +} diff --git a/czishrink/netczicompress/Models/ReportProgress.cs b/czishrink/netczicompress/Models/ReportProgress.cs new file mode 100644 index 0000000..dcff645 --- /dev/null +++ b/czishrink/netczicompress/Models/ReportProgress.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +/// +/// Delegate that is invoked by processing function to provide updates. +/// +/// The percentage of completion in the range 0 .. 100. +public delegate void ReportProgress(int percentComplete); diff --git a/czishrink/netczicompress/Models/StatisticsRunObserver.cs b/czishrink/netczicompress/Models/StatisticsRunObserver.cs new file mode 100644 index 0000000..76441ce --- /dev/null +++ b/czishrink/netczicompress/Models/StatisticsRunObserver.cs @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.Reactive.Concurrency; +using System.Reactive.Linq; + +/// +/// An that forwards to an observer of these. +/// +public class StatisticsRunObserver : IFolderCompressorRunObserver +{ + private readonly IObserver statisticsObserver; + private readonly TimeSpan samplingInterval; + private readonly IScheduler scheduler; + + public StatisticsRunObserver( + IObserver statisticsObserver, + TimeSpan samplingInterval, + IScheduler scheduler) + { + this.statisticsObserver = statisticsObserver; + this.samplingInterval = samplingInterval; + this.scheduler = scheduler; + } + + public void ObserveRun(FolderCompressorParameters runParameters, IObservable runMessages) + { + var start = this.scheduler.Now; + + var emptyStats = AggregateStatistics.Empty; + this.statisticsObserver.OnNext(emptyStats); + var cumulativeStatistics = runMessages + .CompleteOnError() + .Scan( + emptyStats, + (stats, msg) => stats.AddData(msg, this.scheduler.Now - start)); + + // Subscriptions will expire upon completion, no need for explicit disposal. + _ = cumulativeStatistics + .Sample(this.samplingInterval) + .ObserveOn(this.scheduler) + .Subscribe(this.statisticsObserver); + } +} \ No newline at end of file diff --git a/czishrink/netczicompress/Models/TemporaryFile.cs b/czishrink/netczicompress/Models/TemporaryFile.cs new file mode 100644 index 0000000..d4b8eea --- /dev/null +++ b/czishrink/netczicompress/Models/TemporaryFile.cs @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System.IO.Abstractions; + +/// +/// Creates and handles temporary files according to the following logic: +/// +/// The target filename "outfile_name" is given. +/// The basic temporary filename is "outfile_name" + "~". +/// If the basic temporary filename can be used (file does not yet exist or can be deleted) +/// then it will be used. +/// If it cannot be used, we (now recursively) try with another file name "outfile_name" + "~" + i, +/// with i being an index starting at 1. +/// There is a maximum number of tries, so the index is bounded by "maximum number of tries - 1". +/// +/// +public class TemporaryFile +{ + /// + /// Initializes a new instance of the class. + /// + /// The is set and (not the file on the hard disk) + /// immediately created, if possible. + /// By this is set accordingly. + /// + /// The basic output file name. + /// The maximum number or tries. + public TemporaryFile( + IFileInfo outfile, + int maxNumberOfTriesToCreate = 100) + { + this.OutFile = outfile; + this.MaxNumberOfTriesToCreate = maxNumberOfTriesToCreate; + this.Info = this.TryCreate(); + } + + /// + /// Gets a value indicating whether the temporary file creation failed. + /// + public bool TemporaryFileCreationFailed { get; private set; } + + /// + /// Gets the created temporary file. + /// + public IFileInfo Info { get; } + + /// + /// Gets the created final output file. + /// + protected IFileInfo OutFile { get; } + + /// + /// Gets the maximum number of tries for the temporary file creation. + /// + private int MaxNumberOfTriesToCreate { get; } + + /// + /// If the temporary file exists, moves the temporary file to the final output file. + /// Mind that the attribute of the temporary file will + /// be adapted not the flag. + /// + public void MoveToOutFileIfExists() + { + this.Info.Refresh(); // mind that the file might have not existed when the variable was created. + if (!this.Info.Exists) + { + return; + } + + this.Info.MoveTo(this.OutFile.FullName); + this.Info.Refresh(); // mind that the move changes the full name, not the `Exits` flag. + this.OutFile.Refresh(); + } + + /// + /// Deletes the basic output file and the temporary file. + /// + public void DeleteAllOutFiles() + { + _ = this.TryDelete(this.OutFile); + _ = this.TryDelete(this.Info); + } + + /// + /// Tries to create the temporary file. + /// + /// Create the temporary filename. + /// If it can be used, return it. + /// If not, append an increasing index until the file can be deleted or used. + /// If the maximum number of tries is reached, set "failed" and return the latest try. + /// + /// + /// The temporary file info. + private IFileInfo TryCreate() + { + var outFileName = this.OutFile.FullName; + var tempOutFileName = this.GetBasicTempFileName(outFileName); + IFileInfo temporaryFile = this.OutFile.FileSystem.FileInfo.New(tempOutFileName); + var postfixIndex = 1; + while (temporaryFile.Exists && !this.TryDelete(temporaryFile)) + { + if (postfixIndex + 1 > this.MaxNumberOfTriesToCreate) + { + this.TemporaryFileCreationFailed = true; + return temporaryFile; + } + + temporaryFile = this.CreateWithIndex(this.OutFile, tempOutFileName, postfixIndex); + ++postfixIndex; + } + + this.TemporaryFileCreationFailed = false; + return temporaryFile; + } + + private bool TryDelete(IFileInfo file) + { + try + { + file.Refresh(); + if (file.Exists) + { + file.Delete(); + file.Refresh(); + } + } + catch (Exception) + { + return false; + } + + return true; + } + + private string GetBasicTempFileName(string outFileName) + { + return outFileName + "~"; + } + + private IFileInfo CreateWithIndex(IFileInfo outFile, string tempOutFileName, int index) + { + return outFile.FileSystem.FileInfo.New($"{tempOutFileName}{index}"); + } +} diff --git a/czishrink/netczicompress/Models/ThreadCount.cs b/czishrink/netczicompress/Models/ThreadCount.cs new file mode 100644 index 0000000..1ac5d98 --- /dev/null +++ b/czishrink/netczicompress/Models/ThreadCount.cs @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +public partial record ThreadCount +{ + private readonly int internalValue = Maximum; + + public static readonly ThreadCount Default; + + static ThreadCount() + { + DefaultValue = Maximum; + Default = new ThreadCount { Value = DefaultValue }; + } + + public static int Maximum { get; } = GetMaxProcessorCount(); + + public static int Minimum { get; } = 1; + + /// + /// Gets default thread count if not specified. + /// + public static int DefaultValue { get; } = Maximum; + + public int Value + { + get => this.internalValue; + init => + this.internalValue = value > Maximum || value < Minimum + ? throw new ArgumentOutOfRangeException( + paramName: nameof(value), + $"{nameof(value)} must be between {Minimum} and {Maximum}, not {value}") + : value; + } + + // Note we leave one processor free for the UI thread. + private static int GetMaxProcessorCount() + { + return Environment.ProcessorCount > 4 + ? Environment.ProcessorCount - 1 + : Environment.ProcessorCount; + } +} \ No newline at end of file diff --git a/czishrink/netczicompress/Models/TraceLoggerFactory.cs b/czishrink/netczicompress/Models/TraceLoggerFactory.cs new file mode 100644 index 0000000..9f44d5a --- /dev/null +++ b/czishrink/netczicompress/Models/TraceLoggerFactory.cs @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.Diagnostics; + +using Microsoft.Extensions.Logging; + +/// +/// A logger provider and factory that uses to write log messages. +/// +/// +/// Logs levels Information, Warning, Error, and Critical. +/// +/// is not supported. +/// +public sealed class TraceLoggerFactory : ILoggerFactory, ILoggerProvider +{ + private readonly bool useLogLevelsAsTraceLevels; + + public TraceLoggerFactory(bool useLogLevelsAsTraceLevels = false) + { + this.useLogLevelsAsTraceLevels = useLogLevelsAsTraceLevels; + } + + public ILogger CreateLogger(string categoryName) + { + return new TraceLogger(categoryName, this.useLogLevelsAsTraceLevels); + } + + void ILoggerFactory.AddProvider(ILoggerProvider provider) + { + throw new NotSupportedException(); + } + + void IDisposable.Dispose() + { + Trace.Flush(); + } + + private sealed class TraceLogger : ILogger + { + private readonly string categoryName; + private readonly bool useLogLevelsAsTraceLevels; + + public TraceLogger(string categoryName, bool useLogLevelsAsTraceLevels) + { + this.categoryName = categoryName; + this.useLogLevelsAsTraceLevels = useLogLevelsAsTraceLevels; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (this.IsEnabled(logLevel)) + { + var msg = $"{DateTimeOffset.Now:O}|{this.categoryName}|{ToString(logLevel)}|{eventId}|{formatter(state, exception)}"; + if (this.useLogLevelsAsTraceLevels) + { + switch (logLevel) + { + case LogLevel.Error: + case LogLevel.Critical: + Trace.TraceError(msg); + break; + case LogLevel.Information: + Trace.TraceInformation(msg); + break; + case LogLevel.Warning: + Trace.TraceWarning(msg); + break; + default: + break; + } + } + else + { + Trace.WriteLine(msg); + } + } + } + + public bool IsEnabled(LogLevel logLevel) + { + return logLevel switch + { + LogLevel.Trace => false, + LogLevel.Debug => false, + LogLevel.Information => true, + LogLevel.Warning => true, + LogLevel.Error => true, + LogLevel.Critical => true, + LogLevel.None => false, + _ => false, + }; + } + + public IDisposable? BeginScope(TState state) + where TState : notnull + { + return null; + } + + private static string ToString(LogLevel logLevel) + { + return logLevel switch + { + LogLevel.Information => "INFO", + LogLevel.Warning => "WARN", + LogLevel.Error => "ERROR", + LogLevel.Critical => "FATAL", + _ => string.Empty, + }; + } + } +} diff --git a/czishrink/netczicompress/Models/VariousExtensions.cs b/czishrink/netczicompress/Models/VariousExtensions.cs new file mode 100644 index 0000000..31a38ed --- /dev/null +++ b/czishrink/netczicompress/Models/VariousExtensions.cs @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Models; + +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Abstractions; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +public static class VariousExtensions +{ + public static async IAsyncEnumerable EnumerateFilesAsync( + this IDirectoryInfo folder, + string searchPattern, + EnumerationOptions opts, + [EnumeratorCancellation] CancellationToken token = default) + { + var files = await Task.Run(() => folder.EnumerateFiles(searchPattern, opts)).ConfigureAwait(false); + using var e = files.GetEnumerator(); + while (await Task.Run(() => e.MoveNext()).ConfigureAwait(false)) + { + if (token.IsCancellationRequested) + { + yield break; + } + + yield return e.Current; + } + } + + public static AggregateStatistics AddData(this AggregateStatistics aggregateStatistics, CompressorMessage.FileFinished msg, TimeSpan elapsed) + { + if (msg.ErrorMessage == null) + { + return aggregateStatistics with + { + FilesWithNoErrors = aggregateStatistics.FilesWithNoErrors + 1, + InputBytes = aggregateStatistics.InputBytes + msg.SizeInput, + OutputBytes = aggregateStatistics.OutputBytes + msg.SizeOutput, + Duration = elapsed, + }; + } + else + { + return aggregateStatistics with + { + FilesWithErrors = aggregateStatistics.FilesWithErrors + 1, + Duration = elapsed, + }; + } + } +} diff --git a/czishrink/netczicompress/Styles/Border.axaml b/czishrink/netczicompress/Styles/Border.axaml new file mode 100644 index 0000000..20ebe7d --- /dev/null +++ b/czishrink/netczicompress/Styles/Border.axaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Styles/Button_Unused.axaml b/czishrink/netczicompress/Styles/Button_Unused.axaml new file mode 100644 index 0000000..0e07304 --- /dev/null +++ b/czishrink/netczicompress/Styles/Button_Unused.axaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Styles/ComboBox.axaml b/czishrink/netczicompress/Styles/ComboBox.axaml new file mode 100644 index 0000000..8d64252 --- /dev/null +++ b/czishrink/netczicompress/Styles/ComboBox.axaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Styles/DataGrid.axaml b/czishrink/netczicompress/Styles/DataGrid.axaml new file mode 100644 index 0000000..ef3dc0f --- /dev/null +++ b/czishrink/netczicompress/Styles/DataGrid.axaml @@ -0,0 +1,50 @@ + + + + + + + + + 18 + 12 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Styles/GridSplitter.axaml b/czishrink/netczicompress/Styles/GridSplitter.axaml new file mode 100644 index 0000000..629ed83 --- /dev/null +++ b/czishrink/netczicompress/Styles/GridSplitter.axaml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/czishrink/netczicompress/Styles/ProgressBar.axaml b/czishrink/netczicompress/Styles/ProgressBar.axaml new file mode 100644 index 0000000..3cb514b --- /dev/null +++ b/czishrink/netczicompress/Styles/ProgressBar.axaml @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/czishrink/netczicompress/Styles/TextBox.axaml b/czishrink/netczicompress/Styles/TextBox.axaml new file mode 100644 index 0000000..d3647ea --- /dev/null +++ b/czishrink/netczicompress/Styles/TextBox.axaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Styles/Themes/ControlThemeButton.axaml b/czishrink/netczicompress/Styles/Themes/ControlThemeButton.axaml new file mode 100644 index 0000000..71f18dd --- /dev/null +++ b/czishrink/netczicompress/Styles/Themes/ControlThemeButton.axaml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + + + + + + diff --git a/czishrink/netczicompress/Views/AboutView.axaml.cs b/czishrink/netczicompress/Views/AboutView.axaml.cs new file mode 100644 index 0000000..5bcd1e2 --- /dev/null +++ b/czishrink/netczicompress/Views/AboutView.axaml.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using Avalonia.Controls; + +/// +/// An about box and commands to show and close it. +/// +public partial class AboutView : UserControl +{ + public AboutView() => this.InitializeComponent(); +} diff --git a/czishrink/netczicompress/Views/AdvancedOptionsView.axaml b/czishrink/netczicompress/Views/AdvancedOptionsView.axaml new file mode 100644 index 0000000..ce39dba --- /dev/null +++ b/czishrink/netczicompress/Views/AdvancedOptionsView.axaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/czishrink/netczicompress/Views/AdvancedOptionsView.axaml.cs b/czishrink/netczicompress/Views/AdvancedOptionsView.axaml.cs new file mode 100644 index 0000000..b72e589 --- /dev/null +++ b/czishrink/netczicompress/Views/AdvancedOptionsView.axaml.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using Avalonia.Controls; + +public partial class AdvancedOptionsView : UserControl +{ + public AdvancedOptionsView() => this.InitializeComponent(); +} \ No newline at end of file diff --git a/czishrink/netczicompress/Views/AggregateIndicationView.axaml b/czishrink/netczicompress/Views/AggregateIndicationView.axaml new file mode 100644 index 0000000..1bf3abd --- /dev/null +++ b/czishrink/netczicompress/Views/AggregateIndicationView.axaml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Views/AggregateIndicationView.axaml.cs b/czishrink/netczicompress/Views/AggregateIndicationView.axaml.cs new file mode 100644 index 0000000..87ba22e --- /dev/null +++ b/czishrink/netczicompress/Views/AggregateIndicationView.axaml.cs @@ -0,0 +1,11 @@ +namespace netczicompress.Views; + +using Avalonia.Controls; + +/// +/// A control that visualizes with an icon and a label. +/// +public partial class AggregateIndicationView : UserControl +{ + public AggregateIndicationView() => this.InitializeComponent(); +} diff --git a/czishrink/netczicompress/Views/AggregateStatisticsView.axaml b/czishrink/netczicompress/Views/AggregateStatisticsView.axaml new file mode 100644 index 0000000..f94323c --- /dev/null +++ b/czishrink/netczicompress/Views/AggregateStatisticsView.axaml @@ -0,0 +1,206 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Views/AggregateStatisticsView.axaml.cs b/czishrink/netczicompress/Views/AggregateStatisticsView.axaml.cs new file mode 100644 index 0000000..342f6f6 --- /dev/null +++ b/czishrink/netczicompress/Views/AggregateStatisticsView.axaml.cs @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using Avalonia.Controls; +using Avalonia.Controls.Notifications; + +using netczicompress.ViewModels; + +/// +/// A control that visualizes . +/// +public partial class AggregateStatisticsView : UserControl +{ + public AggregateStatisticsView() => this.InitializeComponent(); + + protected override void OnDataContextChanged(EventArgs e) + { + base.OnDataContextChanged(e); + + // Subscribe to the new ViewModels event + if (this.DataContext is IAggregateStatisticsViewModel newViewModel) + { + newViewModel.BadgeCopiedToClipboardRaised += this.OnNotificationBadgeCopiedToClipboardRaised; + } + } + + private void OnNotificationBadgeCopiedToClipboardRaised() + { + var notification = + new Notification( + title: "Badge copied to clipboard", + message: null, + type: NotificationType.Success, + expiration: TimeSpan.FromSeconds(1.5)); + this.RaiseEvent( + new NotificationEventArgs(notification) + { + RoutedEvent = MainWindow.NotificationRequestedEvent, + Source = this, + }); + } +} diff --git a/czishrink/netczicompress/Views/CompressionResultBadge.axaml b/czishrink/netczicompress/Views/CompressionResultBadge.axaml new file mode 100644 index 0000000..95b0b0e --- /dev/null +++ b/czishrink/netczicompress/Views/CompressionResultBadge.axaml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/czishrink/netczicompress/Views/CompressionResultBadge.axaml.cs b/czishrink/netczicompress/Views/CompressionResultBadge.axaml.cs new file mode 100644 index 0000000..b04fe78 --- /dev/null +++ b/czishrink/netczicompress/Views/CompressionResultBadge.axaml.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using Avalonia.Controls; + +public partial class CompressionResultBadge : Panel +{ + public CompressionResultBadge() => this.InitializeComponent(); +} diff --git a/czishrink/netczicompress/Views/CurrentTasksView.axaml b/czishrink/netczicompress/Views/CurrentTasksView.axaml new file mode 100644 index 0000000..858f058 --- /dev/null +++ b/czishrink/netczicompress/Views/CurrentTasksView.axaml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Views/CurrentTasksView.axaml.cs b/czishrink/netczicompress/Views/CurrentTasksView.axaml.cs new file mode 100644 index 0000000..1d4c6c1 --- /dev/null +++ b/czishrink/netczicompress/Views/CurrentTasksView.axaml.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using Avalonia.Controls; + +/// +/// A control that visualizes currently running tasks. +/// +public partial class CurrentTasksView : UserControl +{ + public CurrentTasksView() => this.InitializeComponent(); +} diff --git a/czishrink/netczicompress/Views/ErrorListView.axaml b/czishrink/netczicompress/Views/ErrorListView.axaml new file mode 100644 index 0000000..007659a --- /dev/null +++ b/czishrink/netczicompress/Views/ErrorListView.axaml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Views/ErrorListView.axaml.cs b/czishrink/netczicompress/Views/ErrorListView.axaml.cs new file mode 100644 index 0000000..56b936c --- /dev/null +++ b/czishrink/netczicompress/Views/ErrorListView.axaml.cs @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using Avalonia.Controls; +using Avalonia.Controls.Notifications; +using Avalonia.Input.Platform; +using Avalonia.Interactivity; + +using netczicompress.ViewModels; + +/// +/// A control that visualizes a list of errors. +/// +public partial class ErrorListView : UserControl +{ + public ErrorListView() => this.InitializeComponent(); + + private void ErrorListDataGrid_OnSelectionChanged(object? sender, SelectionChangedEventArgs e) + { + if (sender is DataGrid dataGrid && e.AddedItems.Count > 0) + { + dataGrid.ScrollIntoView(e.AddedItems[0], null); + } + + // SelectedItems is not bindable. So for simplicity's sake, we set IsEnabled in this event handler. + bool isExactlyOneFileSelected = this.dataGrid.SelectedItems.Count == 1; + this.showSelectedFileMenuItem.IsEnabled = isExactlyOneFileSelected; + this.openSelectedFileMenuItem.IsEnabled = isExactlyOneFileSelected; + } + + private async void CopySelectedFilePaths(object? sender, RoutedEventArgs e) + { + var clipboard = TopLevel.GetTopLevel(this)?.Clipboard; + if (clipboard != null) + { + await this.CopySelectedFilePaths(clipboard); + this.NotifySelectedFilePathsCopied(); + } + } + + private async Task CopySelectedFilePaths(IClipboard clipboard) + { + var paths = this.dataGrid.SelectedItems.OfType().Select(x => x.File); + var text = string.Join(Environment.NewLine, paths); + await clipboard.SetTextAsync(text); + } + + private void NotifySelectedFilePathsCopied() + { + var notification = + new Notification( + title: "Path copied to clipboard", + message: null, + type: NotificationType.Success, + expiration: TimeSpan.FromSeconds(1.5)); + this.RaiseEvent( + new NotificationEventArgs(notification) + { + RoutedEvent = MainWindow.NotificationRequestedEvent, + Source = this, + }); + } +} diff --git a/czishrink/netczicompress/Views/FolderInputOutputView.axaml b/czishrink/netczicompress/Views/FolderInputOutputView.axaml new file mode 100644 index 0000000..0a05d7b --- /dev/null +++ b/czishrink/netczicompress/Views/FolderInputOutputView.axaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + diff --git a/czishrink/netczicompress/Views/FolderInputOutputView.axaml.cs b/czishrink/netczicompress/Views/FolderInputOutputView.axaml.cs new file mode 100644 index 0000000..a160655 --- /dev/null +++ b/czishrink/netczicompress/Views/FolderInputOutputView.axaml.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using Avalonia.Controls; + +/// +/// Folder input/output view. +/// +public partial class FolderInputOutputView : UserControl +{ + public FolderInputOutputView() => this.InitializeComponent(); +} \ No newline at end of file diff --git a/czishrink/netczicompress/Views/FolderPicker.axaml b/czishrink/netczicompress/Views/FolderPicker.axaml new file mode 100644 index 0000000..e7e4a3b --- /dev/null +++ b/czishrink/netczicompress/Views/FolderPicker.axaml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Views/FolderPicker.axaml.cs b/czishrink/netczicompress/Views/FolderPicker.axaml.cs new file mode 100644 index 0000000..0c21d6f --- /dev/null +++ b/czishrink/netczicompress/Views/FolderPicker.axaml.cs @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using System.Diagnostics; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Interactivity; +using Avalonia.Platform.Storage; + +/// +/// A control that allows the user to pick a folder. +/// +public partial class FolderPicker : UserControl +{ + public static readonly StyledProperty FolderProperty = + AvaloniaProperty.Register(nameof(Folder), defaultBindingMode: BindingMode.TwoWay); + + public static readonly StyledProperty TitleProperty = + AvaloniaProperty.Register(nameof(Title)); + + public FolderPicker() => this.InitializeComponent(); + + public string Folder + { + get => this.GetValue(FolderProperty); + set => this.SetValue(FolderProperty, value); + } + + public string Title + { + get => this.GetValue(TitleProperty); + set => this.SetValue(TitleProperty, value); + } + + private async void OnButtonClicked(object source, RoutedEventArgs args) + { + try + { + await this.OpenFolderPicker(); + } + catch (Exception ex) + { + Trace.TraceError(ex.ToString()); + } + } + + private async Task OpenFolderPicker() + { + // Get top level from the current control. Alternatively, you can use Window reference instead. + var topLevel = TopLevel.GetTopLevel(this); + + if (topLevel == null) + { + return; + } + + IStorageFolder? startLocation; + try + { + startLocation = await topLevel.StorageProvider.TryGetFolderFromPathAsync(this.Folder); + } + catch + { + startLocation = null; + } + + // Start async operation to open the dialog. + var folders = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = this.Title, + AllowMultiple = false, + SuggestedStartLocation = startLocation, + }); + + if (folders.Count >= 1) + { + this.Folder = folders[0].TryGetLocalPath() ?? folders[0].Path.ToString(); + } + } +} diff --git a/czishrink/netczicompress/Views/MainView.axaml b/czishrink/netczicompress/Views/MainView.axaml new file mode 100644 index 0000000..5c6e3b0 --- /dev/null +++ b/czishrink/netczicompress/Views/MainView.axaml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Views/MainView.axaml.cs b/czishrink/netczicompress/Views/MainView.axaml.cs new file mode 100644 index 0000000..2738c9d --- /dev/null +++ b/czishrink/netczicompress/Views/MainView.axaml.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using Avalonia.Controls; + +/// +/// The main view of the application. +/// +public partial class MainView : UserControl +{ + public MainView() => this.InitializeComponent(); +} diff --git a/czishrink/netczicompress/Views/MainWindow.axaml b/czishrink/netczicompress/Views/MainWindow.axaml new file mode 100644 index 0000000..28f5de8 --- /dev/null +++ b/czishrink/netczicompress/Views/MainWindow.axaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Views/MainWindow.axaml.cs b/czishrink/netczicompress/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..6c6e74c --- /dev/null +++ b/czishrink/netczicompress/Views/MainWindow.axaml.cs @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using System.Reactive; +using System.Reactive.Linq; +using System.Windows.Input; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Notifications; +using Avalonia.Interactivity; + +using MsBox.Avalonia; +using MsBox.Avalonia.Dto; +using MsBox.Avalonia.Models; + +using AsyncCommand = ReactiveUI.ReactiveCommand; + +/// +/// The main window of the application. +/// +public partial class MainWindow : Window +{ + public static readonly StyledProperty StopRunningTasksCommandProperty = + AvaloniaProperty.Register(nameof(StopRunningTasksCommand)); + + /// + /// A routed event that requests a notification to be shown. + /// + public static readonly RoutedEvent NotificationRequestedEvent = + RoutedEvent.Register( + nameof(NotificationRequested), + RoutingStrategies.Bubble, + typeof(MainWindow)); + + private INotificationManager? manager; + + public MainWindow() + { + this.InitializeComponent(); + + this.Closing += this.OnClosing; + this.NotificationRequested += this.OnNotificationRequested; + } + + /// + /// Occurs when a notification is requested on this control or one of its children. + /// + public event EventHandler NotificationRequested + { + add => this.AddHandler(NotificationRequestedEvent, value); + remove => this.RemoveHandler(NotificationRequestedEvent, value); + } + + public AsyncCommand? StopRunningTasksCommand + { + get => this.GetValue(StopRunningTasksCommandProperty); + set => this.SetValue(StopRunningTasksCommandProperty, value); + } + + private INotificationManager NotificationManager + { + get + { + if (this.manager == null) + { + var newInstance = new WindowNotificationManager(this) + { + Position = NotificationPosition.BottomRight, + }; + + // If we do not call ApplyTemplate + // the first notification will not be shown. + newInstance.ApplyTemplate(); + + this.manager = newInstance; + } + + return this.manager; + } + } + + private async void OnClosing(object? sender, WindowClosingEventArgs args) + { + if (!this.HasRunningTasks()) + { + // OK to close immediately + return; + } + + // We cannot close right now => cancel this call to Close() + args.Cancel = true; + + if (await this.UserConfirmsStopRunningTasksAndClose()) + { + this.IsEnabled = false; + await this.StopRunningTasksAsync(); + this.Close(); + } + } + + private async Task StopRunningTasksAsync() + { + if (this.HasRunningTasks()) + { + await this.StopRunningTasksCommand!.Execute(Unit.Default); + } + } + + private bool HasRunningTasks() + { + ICommand? synchronousCommand = this.StopRunningTasksCommand; + return synchronousCommand?.CanExecute(null) ?? false; + } + + private async Task UserConfirmsStopRunningTasksAndClose() + { + var contentMessage = $"Do you want to stop the running task and close {this.Title}?"; + + var box = MessageBoxManager.GetMessageBoxCustom( + new MessageBoxCustomParams + { + Icon = MsBox.Avalonia.Enums.Icon.Stop, + ContentTitle = "Folder Operation in Progress", + ContentHeader = "Stop the current operation?", + ContentMessage = contentMessage, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + ButtonDefinitions = new[] + { + new ButtonDefinition { Name = "Yes", IsDefault = false }, + new ButtonDefinition { Name = "No", IsDefault = true, IsCancel = true }, + }, + WindowIcon = this.Icon, + }); + + var result = await box.ShowWindowDialogAsync(this); + return result == "Yes"; + } + + private void OnNotificationRequested(object? sender, NotificationEventArgs e) + { + this.NotificationManager.Show(e.Notification); + } +} diff --git a/czishrink/netczicompress/Views/NotificationEventArgs.cs b/czishrink/netczicompress/Views/NotificationEventArgs.cs new file mode 100644 index 0000000..61d19d0 --- /dev/null +++ b/czishrink/netczicompress/Views/NotificationEventArgs.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using Avalonia.Controls.Notifications; +using Avalonia.Interactivity; + +/// +/// Routed event arguments with an payload. +/// +/// +/// +public class NotificationEventArgs : RoutedEventArgs +{ + public NotificationEventArgs(INotification notification) + { + this.Notification = notification; + } + + public INotification Notification { get; } +} \ No newline at end of file diff --git a/czishrink/netczicompress/Views/SettingsView.axaml b/czishrink/netczicompress/Views/SettingsView.axaml new file mode 100644 index 0000000..a556e9a --- /dev/null +++ b/czishrink/netczicompress/Views/SettingsView.axaml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Views/SettingsView.axaml.cs b/czishrink/netczicompress/Views/SettingsView.axaml.cs new file mode 100644 index 0000000..7ba5ea2 --- /dev/null +++ b/czishrink/netczicompress/Views/SettingsView.axaml.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using Avalonia.Controls; + +/// +/// View for operation settings. +/// +public partial class SettingsView : UserControl +{ + public SettingsView() => this.InitializeComponent(); +} \ No newline at end of file diff --git a/czishrink/netczicompress/Views/StartStopBarView.axaml b/czishrink/netczicompress/Views/StartStopBarView.axaml new file mode 100644 index 0000000..3ca926f --- /dev/null +++ b/czishrink/netczicompress/Views/StartStopBarView.axaml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/czishrink/netczicompress/Views/StartStopBarView.axaml.cs b/czishrink/netczicompress/Views/StartStopBarView.axaml.cs new file mode 100644 index 0000000..202bb0a --- /dev/null +++ b/czishrink/netczicompress/Views/StartStopBarView.axaml.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompress.Views; + +using Avalonia.Controls; + +/// +/// Execution Buttons View. +/// +public partial class StartStopBarView : UserControl +{ + public StartStopBarView() => this.InitializeComponent(); +} \ No newline at end of file diff --git a/czishrink/netczicompress/netczicompress.csproj b/czishrink/netczicompress/netczicompress.csproj new file mode 100644 index 0000000..930dde8 --- /dev/null +++ b/czishrink/netczicompress/netczicompress.csproj @@ -0,0 +1,32 @@ + + + true + True + true + copyused + true + true + true + true + x64 + + + + + + + + + + + + + + + + + + + + + diff --git a/czishrink/netczicompressTests/AppComposerTests.cs b/czishrink/netczicompressTests/AppComposerTests.cs new file mode 100644 index 0000000..0b57ec4 --- /dev/null +++ b/czishrink/netczicompressTests/AppComposerTests.cs @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests; + +using System.Diagnostics; +using System.Reactive.Disposables; +using System.Text.RegularExpressions; + +using netczicompress; +using netczicompress.ViewModels; + +/// +/// Tests for . +/// +public partial class AppComposerTests +{ + [Fact] + public void ComposeMainViewModel_WhenCalledDoesNotThrow_AndLogsMessage() + { + var listenerMock = new Mock(); + var listener = listenerMock.Object; + using var cleanup = Disposable.Create(() => Trace.Listeners.Remove(listener)); + Trace.Listeners.Add(listener); + + // ACT + var actual = AppComposer.ComposeMainViewModel(new ProgramNameAndVersion()); + + // ASSERT + actual.Should().NotBeNull(); + var re = ExpectedLogMessagePattern(); + listenerMock.Verify(x => x.WriteLine(It.Is(msg => re.IsMatch(msg))), Times.Once); + + // check composition + actual.About.Should().BeOfType(); + actual.ErrorList.Should().BeOfType(); + actual.CurrentTasks.Should().BeOfType(); + + // check initial state + actual.InputDirectory.Should().BeNull(); + actual.OutputDirectory.Should().BeNull(); + actual.Recursive.Should().BeTrue(); + actual.SelectedMode.Value.Should().Be(CompressionMode.CompressUncompressed); + actual.OverallStatus.Should().BeEmpty(); + } + + [GeneratedRegex(@"^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+?[+-]\d\d:\d\d\|netczicompress.App\|INFO\|0\|Starting CZI Shrink 1\.0\.0-alpha\.[1-9]\d* using libczicompressc \d*?\.\d+?\.\d+?.*")] + private static partial Regex ExpectedLogMessagePattern(); +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/Customizations/CompressionLevelSpecimenBuilder.cs b/czishrink/netczicompressTests/Customizations/CompressionLevelSpecimenBuilder.cs new file mode 100644 index 0000000..440c3b7 --- /dev/null +++ b/czishrink/netczicompressTests/Customizations/CompressionLevelSpecimenBuilder.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Customizations; + +using AutoFixture.Kernel; + +/// +/// CompressionLevel specimen builder that will create only valid CompressionLevels. +/// +/// +/// This builder depends on Min/Max values of CompressionLevel being public to avoid magic values. +/// +public class CompressionLevelSpecimenBuilder : ISpecimenBuilder +{ + public object Create(object request, ISpecimenContext context) + { + if (request is Type type && type == typeof(CompressionLevel)) + { + var value = new Random().Next(CompressionLevel.Minimum, CompressionLevel.Maximum + 1); + return new CompressionLevel { Value = value }; + } + + return new NoSpecimen(); + } +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/Customizations/CorrectValueObjectAutoDataAttribute.cs b/czishrink/netczicompressTests/Customizations/CorrectValueObjectAutoDataAttribute.cs new file mode 100644 index 0000000..8a943c6 --- /dev/null +++ b/czishrink/netczicompressTests/Customizations/CorrectValueObjectAutoDataAttribute.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Customizations; + +public class CorrectValueObjectAutoDataAttribute : AutoDataAttribute +{ + public CorrectValueObjectAutoDataAttribute() + : base(() => + { + var fixture = new Fixture(); + fixture.Customizations.Add(new CompressionLevelSpecimenBuilder()); + fixture.Customizations.Add(new ThreadCountSpecimenBuilder()); + return fixture; + }) + { + } +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/Customizations/ThreadCountSpecimenBuilder.cs b/czishrink/netczicompressTests/Customizations/ThreadCountSpecimenBuilder.cs new file mode 100644 index 0000000..334958c --- /dev/null +++ b/czishrink/netczicompressTests/Customizations/ThreadCountSpecimenBuilder.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Customizations; + +using AutoFixture.Kernel; + +/// +/// specimen builder that will create only valid s. +/// +/// +/// This builder depends on Min/Max values of being public to avoid magic values. +/// +public class ThreadCountSpecimenBuilder : ISpecimenBuilder +{ + public object Create(object request, ISpecimenContext context) + { + if (request is Type type && type == typeof(ThreadCount)) + { + var value = new Random().Next(ThreadCount.Minimum, ThreadCount.Maximum + 1); + return new ThreadCount { Value = value }; + } + + return new NoSpecimen(); + } +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/Customizations/ValidInlineAutoData.cs b/czishrink/netczicompressTests/Customizations/ValidInlineAutoData.cs new file mode 100644 index 0000000..8e9ae2d --- /dev/null +++ b/czishrink/netczicompressTests/Customizations/ValidInlineAutoData.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Customizations; + +using Xunit.Sdk; + +public class ValidInlineAutoData : CompositeDataAttribute +{ + public ValidInlineAutoData(params object[] values) + : base(new DataAttribute[] + { + new InlineDataAttribute(values), + new CorrectValueObjectAutoDataAttribute(), + }) + { + } +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/ExcludePropertiesByAttributeSelectionRule.cs b/czishrink/netczicompressTests/ExcludePropertiesByAttributeSelectionRule.cs new file mode 100644 index 0000000..7733fd0 --- /dev/null +++ b/czishrink/netczicompressTests/ExcludePropertiesByAttributeSelectionRule.cs @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests; + +using System.Collections.Generic; +using System.Reflection; +using global::FluentAssertions.Equivalency; + +/// +/// A selection rule that excludes all properties with a particular attribute. +/// +public class ExcludePropertiesByAttributeSelectionRule : IMemberSelectionRule +{ + private readonly Type attributeType; + + public ExcludePropertiesByAttributeSelectionRule(Type attributeType) + { + this.attributeType = attributeType; + } + + public bool IncludesMembers => false; + + public IEnumerable SelectMembers( + INode currentNode, + IEnumerable selectedMembers, + MemberSelectionContext context) + { + return from m in selectedMembers + where !this.IsPropertyWithExcludedAttribute(m) + select m; + } + + private bool IsPropertyWithExcludedAttribute(IMember member) + { + var prop = member.ReflectedType.GetProperty(member.Name); + var result = prop?.GetCustomAttributes(this.attributeType).Any() == true; + return result; + } +} diff --git a/czishrink/netczicompressTests/FluentAssertionsConfig.cs b/czishrink/netczicompressTests/FluentAssertionsConfig.cs new file mode 100644 index 0000000..6df4203 --- /dev/null +++ b/czishrink/netczicompressTests/FluentAssertionsConfig.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests; + +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; + +internal static class FluentAssertionsConfig +{ + [ModuleInitializer] + public static void SetDefaults() + { + AssertionOptions.AssertEquivalencyUsing( + options => options + .WithStrictOrdering() + .Using( + new ExcludePropertiesByAttributeSelectionRule( + typeof(OSPlatformAttribute)))); + } +} diff --git a/czishrink/netczicompressTests/Models/AggregateStatisticsTests.cs b/czishrink/netczicompressTests/Models/AggregateStatisticsTests.cs new file mode 100644 index 0000000..e451048 --- /dev/null +++ b/czishrink/netczicompressTests/Models/AggregateStatisticsTests.cs @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +/// +/// Tests for . +/// +public class AggregateStatisticsTests +{ + [Fact] + public void Empty_EqualsDefault() + { + AggregateStatistics.Empty.Should().Be(default(AggregateStatistics)); + } + + [Fact] + public void Empty_HasExpectedPropertyValues() + { + AggregateStatistics sut = AggregateStatistics.Empty; + sut.Should().BeEquivalentTo( + new AggregateStatistics + { + FilesWithErrors = 0, + FilesWithNoErrors = 0, + Duration = TimeSpan.Zero, + InputBytes = 0, + OutputBytes = 0, + }, + cfg => cfg.ComparingByMembers()); + + sut.DeltaBytes.Should().Be(0); + float.IsNaN(sut.OutputToInputRatio).Should().BeTrue(); + } + + [Theory] + [AutoData] + public void NonEmpty_HasExpectedCalculatedPropertyValues(AggregateStatistics sut) + { + sut.DeltaBytes.Should().Be(sut.OutputBytes - sut.InputBytes); + sut.OutputToInputRatio.Should().Be(1.0f * sut.OutputBytes / sut.InputBytes); + } + + [Fact] + public void ConcreteExample_HasExpectedCalculatedPropertyValues() + { + var sut = new AggregateStatistics + { + OutputBytes = 1500, + InputBytes = 2000, + }; + sut.DeltaBytes.Should().Be(-500); + sut.OutputToInputRatio.Should().Be(0.75f); + } +} diff --git a/czishrink/netczicompressTests/Models/AsyncEnumerableToObservableActionAdapterTests.cs b/czishrink/netczicompressTests/Models/AsyncEnumerableToObservableActionAdapterTests.cs new file mode 100644 index 0000000..4a03d6e --- /dev/null +++ b/czishrink/netczicompressTests/Models/AsyncEnumerableToObservableActionAdapterTests.cs @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using System.Reactive.Threading.Tasks; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +using netczicompressTests.Models.Mocks; + +/// +/// Tests for . +/// +public class AsyncEnumerableToObservableActionAdapterTests +{ + [Theory] + [AutoData] + public async Task Start_WhenCalled_ReturnsItemsInSequenceAndCompletes(int[] data) + { + // ARRANGE + var sut = CreateSut(data); + + var recorder = new Recorder(); + sut.Output.Subscribe(recorder); + + // ACT + var task = sut.Output.ToTask(); + sut.Start(); + await task; + + // ASSERT + recorder.Data.Should().BeEquivalentTo(data); + } + + [Theory] + [AutoData] + public async Task Start_WhenCalledTwice_ThrowsInvalidOperationException(int[] data) + { + // ARRANGE + var sut = CreateSut(data); + var task = sut.Output.ToTask(); + sut.Start(); + + // ACT + Action act = () => sut.Start(); + + // ASSERT + act.Should().Throw().WithMessage("Start can only be called once."); + + // CLEANUP + await task; + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Start_WhenCanceled_StopsAndReportsErrorInOutput(bool enumerableThrowsWhenCanceled) + { + // ARRANGE + using var cts = new CancellationTokenSource(); + var sut = CreateSut(Enumerable.Range(1, 10), enumerableThrowsWhenCanceled, cts.Token); + + Recorder recorder = new(); + sut.Output.Subscribe(recorder); + sut.Output.Subscribe( + onNext: x => + { + if (x == 5) + { + cts.Cancel(); + } + }, + onError: ex => { }); + + // ACT + var task = sut.Output.ToTask(); + sut.Start(); + + // ASSERT + if (enumerableThrowsWhenCanceled) + { + Func checkError = () => task; + await checkError.Should().ThrowAsync(); + recorder.Error.Should().BeOfType(); + } + else + { + await task; + recorder.Error.Should().BeNull(); + } + + recorder.Data.Should().BeEquivalentTo( + Enumerable.Range(1, 5)); + } + + private static AsyncEnumerableToObservableActionAdapter CreateSut( + IEnumerable data, + bool throwWhenCanceled = false, + CancellationToken? cancellationToken = null) + { + var token = cancellationToken ?? CancellationToken.None; + var sut = new AsyncEnumerableToObservableActionAdapter( + AsAsync(data, throwWhenCanceled, token), + token); + return sut; + } + + private static async IAsyncEnumerable AsAsync( + IEnumerable data, + bool throwWhenCanceled, + [EnumeratorCancellation] CancellationToken token) + { + await Task.Yield(); + foreach (var item in data) + { + if (throwWhenCanceled) + { + token.ThrowIfCancellationRequested(); + } + else if (token.IsCancellationRequested) + { + yield break; + } + + yield return item; + await Task.Yield(); + } + } +} diff --git a/czishrink/netczicompressTests/Models/CompositeObserverTests.cs b/czishrink/netczicompressTests/Models/CompositeObserverTests.cs new file mode 100644 index 0000000..e3e7ab7 --- /dev/null +++ b/czishrink/netczicompressTests/Models/CompositeObserverTests.cs @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +/// +/// Tests for . +/// +public class CompositeObserverTests +{ + [Fact] + public void OnCompleted_IsForwardedToAllObservers() + { + var observerMocks = CreateObserverMocks(); + + var sut = CompositeObserver.Create( + observerMocks.Select(x => x.Object).ToArray()); + + sut.OnCompleted(); + + foreach (var mock in observerMocks) + { + mock.Verify(x => x.OnCompleted(), Times.Once); + mock.VerifyNoOtherCalls(); + } + } + + [Fact] + public void OnNext_IsForwardedToAllObservers() + { + var observerMocks = CreateObserverMocks(); + + var sut = CompositeObserver.Create( + observerMocks.Select(x => x.Object).ToArray()); + + sut.OnNext(17); + sut.OnNext(17); + + foreach (var mock in observerMocks) + { + mock.Verify(x => x.OnNext(17), Times.Exactly(2)); + mock.VerifyNoOtherCalls(); + } + } + + [Fact] + public void OnError_IsForwardedToAllObservers() + { + var observerMocks = CreateObserverMocks(); + + var sut = CompositeObserver.Create( + observerMocks.Select(x => x.Object).ToArray()); + + var ex = new Exception("bla"); + sut.OnError(ex); + + foreach (var mock in observerMocks) + { + mock.Verify(x => x.OnError(ex), Times.Once); + mock.VerifyNoOtherCalls(); + } + } + + private static Mock>[] CreateObserverMocks() + { + var observerMocks = Enumerable.Range(1, 5).Select(i => new Mock>()).ToArray(); + return observerMocks; + } +} diff --git a/czishrink/netczicompressTests/Models/CompressionLevelTests.cs b/czishrink/netczicompressTests/Models/CompressionLevelTests.cs new file mode 100644 index 0000000..4ff193e --- /dev/null +++ b/czishrink/netczicompressTests/Models/CompressionLevelTests.cs @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +/// +/// Tests for . +/// +public class CompressionLevelTests +{ + [Fact] + public void DefaultConstructor_HasDefaultValue() + { + var sut = new CompressionLevel(); + sut.Value.Should().Be(CompressionLevel.DefaultValue); + } + + [Theory] + [InlineData(-1)] + [InlineData(23)] + [InlineData(-100)] + [InlineData(230)] + public void OutOfRange_ShouldThrowOutOfRangeException(int value) + { + var act = () => + { + var sut = new CompressionLevel() { Value = value }; + }; + + act.Should().Throw(); + } + + [Theory] + [InlineData(22)] + [InlineData(9)] + [InlineData(0)] + public void InRangeValue_ShouldBeEqual(int value) + { + var sut = new CompressionLevel() { Value = value }; + sut.Value.Should().Be(value); + } +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/Models/CompressorMessageTests.cs b/czishrink/netczicompressTests/Models/CompressorMessageTests.cs new file mode 100644 index 0000000..6d4204a --- /dev/null +++ b/czishrink/netczicompressTests/Models/CompressorMessageTests.cs @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using System.IO.Abstractions; + +/// +/// Tests for and its subclasses. +/// +public class CompressorMessageTests +{ + [Theory] + [InlineAutoData(0)] + public void SetZeroSizeInput_HasZeroSizeRatioAndDelta(long input, long output) + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var sut = new CompressorMessage.FileFinished( + fixture.Create(), + input, + output, + fixture.Create(), + null); + + sut.SizeDelta.Should().Be(output); + sut.SizeRatio.Should().Be(0); + } + + [Theory] + [AutoData] + public void CreateCompressionLevel_CalculatedPropertiesAreValid(long input, long output) + { + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var sut = new CompressorMessage.FileFinished( + fixture.Create(), + input, + output, + fixture.Create(), + null); + + sut.SizeInput.Should().Be(input); + sut.SizeOutput.Should().Be(output); + sut.SizeDelta.Should().Be(output - input); + sut.SizeRatio.Should().Be(input == 0 ? 0 : (decimal)output / input); + } +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/Models/CsvLogFileWriterTests.cs b/czishrink/netczicompressTests/Models/CsvLogFileWriterTests.cs new file mode 100644 index 0000000..f125fb4 --- /dev/null +++ b/czishrink/netczicompressTests/Models/CsvLogFileWriterTests.cs @@ -0,0 +1,103 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using System.IO.Abstractions; + +/// +/// Tests for . +/// +public class CsvLogFileWriterTests +{ + [Theory] + [InlineData(true)] + [InlineData(false)] + public void FullRun_ProducesExpectedText(bool error) + { + // ARRANGE + var data = new (string Path, long SizeIn, long SizeOut, TimeSpan? TimeElapsed, string? Error)[] + { + ("foo/bla/bla.czi", 100, 16, TimeSpan.Zero, null), + ("foo/bla/blub.czi", 1000, 1600, TimeSpan.Zero, null), + ("bar/bla/baz,; .CZI", 1000, 1600, null, "oops\nHere's another line."), + }; + + using var writer = new StringWriter(); + + var sut = new CsvLogFileWriter(writer); + + var messages = from item in data + select new CompressorMessage.FileFinished( + Mock.Of(x => x.FullName == item.Path), + item.SizeIn, + item.SizeOut, + item.TimeElapsed, + item.Error); + + // ACT + foreach (var item in messages) + { + sut.OnNext(item); + } + + if (error) + { + sut.OnError(new Exception()); + } + else + { + sut.OnCompleted(); + } + + // ASSERT + var csvFileContents = writer.ToString(); + var csvFileRows = csvFileContents.Split("\r\n"); + + csvFileRows.Should().BeEquivalentTo( + new[] + { + "InputFile,SizeInput,SizeOutput,SizeRatio,SizeDelta,TimeToProcess,Status,ErrorMessage", + "\"foo/bla/bla.czi\",100,16,0.16,-84,00:00:00,SUCCESS,", + "\"foo/bla/blub.czi\",1000,1600,1.6,600,00:00:00,SUCCESS,", + "\"bar/bla/baz,; .CZI\",1000,1600,,,,ERROR,\"oops\nHere's another line.\"", + string.Empty, + }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void OnNext_WhenCalledAfterCompletion_ProducesNoResult(bool error) + { + // ARRANGE + using var writer = new StringWriter(); + + var sut = new CsvLogFileWriter(writer); + + if (error) + { + sut.OnError(new Exception()); + } + else + { + sut.OnCompleted(); + } + + // ACT + var message = new Fixture().Customize(new AutoMoqCustomization { ConfigureMembers = true }).Create(); + sut.OnNext(message); + + // ASSERT + var csvFileContents = writer.ToString(); + var csvFileRows = csvFileContents.Split("\r\n"); + + csvFileRows.Should().BeEquivalentTo( + new[] + { + "InputFile,SizeInput,SizeOutput,SizeRatio,SizeDelta,TimeToProcess,Status,ErrorMessage", + string.Empty, + }); + } +} diff --git a/czishrink/netczicompressTests/Models/CsvLoggingStrategyTests.cs b/czishrink/netczicompressTests/Models/CsvLoggingStrategyTests.cs new file mode 100644 index 0000000..94fea65 --- /dev/null +++ b/czishrink/netczicompressTests/Models/CsvLoggingStrategyTests.cs @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using System.Diagnostics; +using System.IO.Abstractions; +using System.IO.Abstractions.TestingHelpers; + +/// +/// Tests for . +/// +public class CsvLoggingStrategyTests +{ + [Fact] + public void LoggerScheduler_WhenNotSet_ReturnsDefaultScheduler() + { + var sut = new CsvLoggingStrategy(Mock.Of()); + + sut.LoggerScheduler.Should().BeSameAs(DefaultScheduler.Instance); + } + + [Theory] + [AutoData] + public void CreateLogFile_WhenCalled_ProducesExpectedFileName(bool recursive, CompressionMode mode) + { + // ARRANGE + var schedulerMock = new Mock(MockBehavior.Strict); + + var sut = new CsvLoggingStrategy(Mock.Of()) + { + LoggerScheduler = schedulerMock.Object, + }; + + var fs = new MockFileSystem(new MockFileSystemOptions { CreateDefaultTempDir = true }); + + var outputDir = fs.Path.Combine(fs.Path.GetTempPath(), "foo"); + var runParameters = new FolderCompressorParameters( + new Mock(MockBehavior.Strict).Object, + fs.DirectoryInfo.New(outputDir), + recursive, + mode, + new ExecutionOptions(ThreadCount.Default), + new ProcessingOptions(CompressionLevel.Default)); + + schedulerMock.SetupGet(x => x.Now).Returns( + DateTimeOffset.Parse($"3067-11-14T23:59:59.1234")); + + // ACT + var actual = sut.CreateLogFile(runParameters); + + // ASSERT + var expected = fs.Path.Combine(outputDir, $"CziShrink_30671114T235959_{mode}.csv"); + actual.FullName.Should().Be(expected); + actual.Exists.Should().BeFalse(); + } + + [Theory] + [AutoData] + public async Task CreateLogFile_WhenCalled_ProducesUniqueFileName(bool recursive, CompressionMode mode) + { + // ARRANGE + DateTimeOffset now = DateTimeOffset.Parse($"3067-11-14T23:59:59.1234"); + var schedulerMock = new Mock(MockBehavior.Strict); + schedulerMock.SetupGet(x => x.Now).Returns(() => + { + lock (schedulerMock) + { + return now; + } + }); + + var sut = new CsvLoggingStrategy(Mock.Of()) + { + LoggerScheduler = schedulerMock.Object, + }; + + var fs = new MockFileSystem(new MockFileSystemOptions { CreateDefaultTempDir = true }); + + var outputDir = fs.Path.Combine(fs.Path.GetTempPath(), "foo"); + var runParameters = new FolderCompressorParameters( + new Mock(MockBehavior.Strict).Object, + fs.DirectoryInfo.New(outputDir), + recursive, + mode, + new ExecutionOptions(ThreadCount.Default), + new ProcessingOptions(CompressionLevel.Default)); + CreateFile(sut.CreateLogFile(runParameters)); + + // ACT + Task actTask = Task.Run(() => sut.CreateLogFile(runParameters)); + SpinWait.SpinUntil(() => actTask.Status == TaskStatus.Running); + await Task.Delay(10); + actTask.Status.Should().Be(TaskStatus.Running); + + lock (schedulerMock) + { + now += TimeSpan.FromSeconds(1.5); + } + + // ASSERT + var expected = fs.Path.Combine(outputDir, $"CziShrink_30671115T000000_{mode}.csv"); + var actual = await actTask; + actual.FullName.Should().Be(expected); + actual.Exists.Should().BeFalse(); + } + + [Fact] + public void CreateLogger_WhenCalled_CreatesCsvLogWriter() + { + // ARRANGE + var sut = new CsvLoggingStrategy(Mock.Of()); + + using var writer = Mock.Of(); + + // ACT + var actual = sut.CreateLogger(writer); + + // ASSERT + var result = actual.Should().BeOfType().Subject; + result.Writer.Should().BeSameAs(writer); + } + + [Fact] + public void OpenLogFile_WhenCalled_UsesFileLauncher() + { + // ARRANGE + var fileLauncherMock = new Mock(); + var sut = new CsvLoggingStrategy(fileLauncherMock.Object); + var fileInfo = Mock.Of(f => f.FullName == "/foo/bar/baz.CSV"); + + // ACT + sut.OpenLogFile(fileInfo); + + // ASSERT + fileLauncherMock.Verify(l => l.Launch("/foo/bar/baz.CSV"), Times.Once); + fileLauncherMock.VerifyNoOtherCalls(); + } + + private static void CreateFile(IFileInfo firstFile) + { + firstFile.Directory!.Create(); + using (var firstFileHandle = firstFile.AppendText()) + { + firstFileHandle.WriteLine("foo"); + } + + firstFile.Refresh(); + Debug.Assert(firstFile.Exists, "Error in ARRANGE"); + } +} diff --git a/czishrink/netczicompressTests/Models/FileLauncherTests.cs b/czishrink/netczicompressTests/Models/FileLauncherTests.cs new file mode 100644 index 0000000..76126d9 --- /dev/null +++ b/czishrink/netczicompressTests/Models/FileLauncherTests.cs @@ -0,0 +1,279 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using System.Diagnostics; +using System.IO.Abstractions; +using System.Linq.Expressions; +using System.Runtime.InteropServices; + +/// +/// Tests for . +/// +public class FileLauncherTests +{ + [Fact] + public async Task Launch_WhenCalled_RunsExpectedProcess() + { + // ARRANGE + var sut = new TestableFileLauncher(); + var path = "/foo/bar/baz"; + + // ACT + await sut.Launch(path); + + // ASSERT + var runAsyncInvocationArgs = sut.GetRunAsyncInvocationsArgs(); + runAsyncInvocationArgs.Should().BeEquivalentTo( + new[] + { + new ProcessStartInfo + { + FileName = "/foo/bar/baz", + UseShellExecute = true, + }, + }); + } + + [Theory] + [InlineData(null, "explorer")] + [InlineData(@"C:\Fenster", @"C:\Fenster\explorer")] + public async Task Reveal_WhenWindows_RunsExpectedProcess( + string? systemRootEnvironmentVariableValue, + string expectedFileName) + { + // ARRANGE + string? overridePlatform = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? null + : "WINDOWS"; + + var getEnv = Mock.Of>( + x => x("SYSTEMROOT") == systemRootEnvironmentVariableValue); + + var sut = new TestableFileLauncher( + overridePlatform: overridePlatform, + fs: Mock.Of(x => x.Path == MockPathCombineWindows()), + getEnvironmentVariable: getEnv); + + var path = @"C:\foo bar\baz"; + + // ACT + await sut.Reveal(path); + + // ASSERT + var runAsyncInvocationArgs = sut.GetRunAsyncInvocationsArgs(); + runAsyncInvocationArgs.Should().BeEquivalentTo( + new[] + { + new ProcessStartInfo + { + FileName = expectedFileName, + Arguments = @"/select,""C:\foo bar\baz""", + }, + }); + } + + [Fact] + public async Task Reveal_WhenOsx_RunsExpectedProcess() + { + // ARRANGE + string? overridePlatform = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) + ? null + : "OSX"; + + var sut = new TestableFileLauncher( + overridePlatform: overridePlatform); + + var path = @"/foo bar/baz"; + + // ACT + await sut.Reveal(path); + + // ASSERT + var runAsyncInvocationArgs = sut.GetRunAsyncInvocationsArgs(); + runAsyncInvocationArgs.Should().BeEquivalentTo( + new[] + { + new ProcessStartInfo + { + FileName = "explorer", + Arguments = @"-R ""/foo bar/baz""", + }, + }); + } + + [Theory] + [InlineData("LINUX")] + [InlineData("SOLARIS")] + [InlineData("FREEBSD")] + [InlineData("NETBSD")] + public async Task Reveal_WhenLinux_RunsExpectedProcess(string platform) + { + // ARRANGE + string? overridePlatform = OperatingSystem.IsOSPlatform(platform) + ? null + : platform; + + var path = @"/foo/bar baz/xyz.czi"; + Func? overridePathToFileUri = OperatingSystem.IsOSPlatform(platform) + ? null + : Mock.Of>(f => f(path) == "file:///foo/bar%20baz/xyz.czi"); + + var sut = new TestableFileLauncher( + overridePathToFileUri: overridePathToFileUri, + overridePlatform: overridePlatform); + + // ACT + await sut.Reveal(path); + + // ASSERT + var runAsyncInvocationArgs = sut.GetRunAsyncInvocationsArgs(); + runAsyncInvocationArgs.Should().BeEquivalentTo( + new[] + { + new ProcessStartInfo + { + FileName = "dbus-send", + Arguments = @"--print-reply --dest=org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1.ShowItems array:string:""file:///foo/bar%20baz/xyz.czi"" string:""""", + UseShellExecute = true, + }, + }); + } + + [Theory] + [InlineData("LINUX")] + [InlineData("SOLARIS")] + [InlineData("FREEBSD")] + [InlineData("NETBSD")] + public async Task Reveal_WhenDbusSendReturnsNonZero_RunsExpectedProcess(string platform) + { + // ARRANGE + string? overridePlatform = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && platform == "LINUX" + ? null + : platform; + + var path = "/foo/bar baz/xyz.czi"; + Func? pathToFileUri = OperatingSystem.IsOSPlatform(platform) + ? null + : Mock.Of>(f => f(path) == "file:///foo/bar%20baz/xyz.czi"); + + var sut = new TestableFileLauncher( + fs: Mock.Of(x => x.Path.GetDirectoryName(path) == "/path/of/parent/dir"), + overridePathToFileUri: pathToFileUri, + overridePlatform: overridePlatform); + + var nonZeroExitCode = new Fixture().CreateMany().First(x => x != 0); + sut.SetupExitCode(p => p.FileName == "dbus-send", nonZeroExitCode); + + // ACT + await sut.Reveal(path); + + // ASSERT + var runAsyncInvocationArgs = sut.GetRunAsyncInvocationsArgs(); + runAsyncInvocationArgs.Should().BeEquivalentTo( + new[] + { + new ProcessStartInfo + { + FileName = "dbus-send", + Arguments = @"--print-reply --dest=org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1.ShowItems array:string:""file:///foo/bar%20baz/xyz.czi"" string:""""", + UseShellExecute = true, + }, + new ProcessStartInfo + { + FileName = "/path/of/parent/dir", + UseShellExecute = true, + }, + }); + } + + [Fact] + public async Task Reveal_WhenOtherOs_LaunchesParentFolder() + { + // ARRANGE + var path = "/foo/bar baz/xyz.czi"; + + var sut = new TestableFileLauncher( + fs: Mock.Of(x => x.Path.GetDirectoryName(path) == "/path/of/parent/dir"), + overridePlatform: "UNSUPPORTED"); + + // ACT + await sut.Reveal(path); + + // ASSERT + var runAsyncInvocationArgs = sut.GetRunAsyncInvocationsArgs(); + runAsyncInvocationArgs.Should().BeEquivalentTo( + new[] + { + new ProcessStartInfo + { + FileName = "/path/of/parent/dir", + UseShellExecute = true, + }, + }); + } + + private static IPath MockPathCombineWindows() + { + var pathMock = new Mock(); + pathMock + .Setup( + x => x.Combine(It.IsAny(), It.IsAny())) + .Returns( + (string a, string b) => $"{a}\\{b}"); + var pathMockObject = pathMock.Object; + return pathMockObject; + } + + private class TestableFileLauncher : FileLauncher + { + private readonly Func? pathToFileUri; + + public TestableFileLauncher( + IFileSystem? fs = null, + string? overridePlatform = null, + Func? overridePathToFileUri = null, + Func? getEnvironmentVariable = null) + : base( + fs ?? Mock.Of(), + getEnvironmentVariable ?? Mock.Of>()) + { + this.pathToFileUri = overridePathToFileUri; + this.CurrentPlatform = overridePlatform ?? this.CurrentPlatform; + this.RunAsyncMock = new Mock>>(); + this.RunAsyncMock + .Setup(x => x.Invoke(It.IsAny())) + .Returns(Task.FromResult(0)); + } + + public Mock>> RunAsyncMock { get; } + + public ProcessStartInfo[] GetRunAsyncInvocationsArgs() + { + var result = from invocation in this.RunAsyncMock.Invocations + select invocation.Arguments.Cast().Single(); + return result.ToArray(); + } + + public void SetupExitCode(Expression> processStartInfoSelector, int exitCode) + { + this.RunAsyncMock + .Setup( + x => x.Invoke(It.Is(processStartInfoSelector))) + .Returns(Task.FromResult(exitCode)); + } + + protected override Task RunAsync(ProcessStartInfo processStartInfo) + { + return this.RunAsyncMock.Object.Invoke(processStartInfo); + } + + protected override string PathToFileUri(string path) + { + return this.pathToFileUri?.Invoke(path) + ?? base.PathToFileUri(path); + } + } +} diff --git a/czishrink/netczicompressTests/Models/FileProcessingFailedHandlerTests.cs b/czishrink/netczicompressTests/Models/FileProcessingFailedHandlerTests.cs new file mode 100644 index 0000000..5a4ebb1 --- /dev/null +++ b/czishrink/netczicompressTests/Models/FileProcessingFailedHandlerTests.cs @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using System.IO.Abstractions; +using System.Reactive.Disposables; + +/// +/// Unit tests for the class. +/// +public class FileProcessingFailedHandlerTests +{ + [Theory] + [InlineData(true)] + [InlineData(false)] + public void OnFileProcessingFailed_WithDifferentOptions_ReturnsCorrectValue(bool copyFailedFiles) + { + // Arrange + string fileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var inputFileName = $"{fileName}.czi"; + using var deleteInput = DeleteLater(inputFileName); + var inputFile = CreateFileInfo(inputFileName); + var inputFileInfo = new FileInfo(inputFileName); + using var inputStream = inputFileInfo.Create(); + inputStream.WriteByte(5); + inputStream.Close(); + + var outputFileName = $"{fileName}_out.czi"; + using var deleteOutput = DeleteLater(outputFileName); + + var tempOutputFileName = $"{fileName}_temp.czi"; + using var deleteTempOutput = DeleteLater(tempOutputFileName); + + var target = new FileProcessingFailedHandler(); + string innerException = "Something bad happened."; + int reportedProgress = -1; + + // Act + var result = target.FileProcessingFailed( + inputFile, + CreateFileInfo(outputFileName), + CreateFileInfo(tempOutputFileName), + progress => reportedProgress = progress, + innerException, + copyFailedFiles, + CancellationToken.None); + + // Assert + result.Should().Be(copyFailedFiles ? $"{innerException} {FileProcessingFailedHandler.SuccessfulCopyMessage}" : innerException); + reportedProgress.Should().Be(copyFailedFiles ? 100 : -1); + } + + [Fact] + public void OnFileProcessingFailed_WithCopyOptionAndFailedCopyOperation_ReturnsCorrectValue() + { + // Arrange + var target = new FileProcessingFailedHandler(); + var inFile = new Mock(); + inFile.Setup(x => x.CopyTo(It.IsAny(), It.IsAny())).Throws(); + var outFile = new Mock(); + var tempOutFile = new Mock(); + string innerException = "Something bad happened."; + + // Act + var result = target.FileProcessingFailed( + inFile.Object, + outFile.Object, + tempOutFile.Object, + _ => { }, + innerException, + true, + CancellationToken.None); + + // Assert + result.Should().Be($"{innerException} {FileProcessingFailedHandler.FailedCopyMessage}"); + } + + private static IFileInfo CreateFileInfo(string fileName) + { + var fileInfo = new Mock(); + fileInfo.Setup(x => x.FullName).Returns(fileName); + return fileInfo.Object; + } + + private static IDisposable DeleteLater(string path) => Disposable.Create(() => File.Delete(path)); +} diff --git a/czishrink/netczicompressTests/Models/FolderCompressorDecoratorTests.cs b/czishrink/netczicompressTests/Models/FolderCompressorDecoratorTests.cs new file mode 100644 index 0000000..98a6c3e --- /dev/null +++ b/czishrink/netczicompressTests/Models/FolderCompressorDecoratorTests.cs @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using netczicompressTests.Customizations; + +using IFolderCompressorRun = netczicompress.Models.IObservableAction; + +/// +/// Tests for . +/// +public class FolderCompressorDecoratorTests +{ + [Fact] + public void PrepareNewRun_WhenCalled_CallsObserveRunWithExpectedArgs() + { + // ARRANGE + var strictMocks = new MockRepository(MockBehavior.Strict); + var coreMock = strictMocks.Create(); + var observeRun = new Mock>>(); + var coreOutput = strictMocks.OneOf>(); + var observableAction = strictMocks.OneOf( + x => x.Output == coreOutput); + + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + fixture.Customizations.Add(new CompressionLevelSpecimenBuilder()); + fixture.Customizations.Add(new ThreadCountSpecimenBuilder()); + var p = fixture.Create(); + var t = fixture.Create(); + coreMock + .Setup(x => x.PrepareNewRun(p, t)) + .Returns(observableAction) + .Verifiable(Times.Once); + + var sut = new FolderCompressorDecorator + { + Core = coreMock.Object, + ObserveRun = observeRun.Object, + }; + + // ACT + var actual = sut.PrepareNewRun(p, t); + + // ASSERT + actual.Should().BeSameAs(observableAction); + coreMock.Verify(); + observeRun.Verify(d => d.Invoke(p, coreOutput), Times.Once); + observeRun.VerifyNoOtherCalls(); + } +} diff --git a/czishrink/netczicompressTests/Models/FolderCompressorExtensionsTests.cs b/czishrink/netczicompressTests/Models/FolderCompressorExtensionsTests.cs new file mode 100644 index 0000000..b3eacf3 --- /dev/null +++ b/czishrink/netczicompressTests/Models/FolderCompressorExtensionsTests.cs @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using netczicompressTests.Customizations; +using netczicompressTests.Models.Mocks; +using static netczicompress.Models.CompressorMessage; +using IFolderCompressorRun = netczicompress.Models.IObservableAction; + +/// +/// Tests for . +/// +public class FolderCompressorExtensionsTests +{ + [Fact] + public void DecorateWithRunObserver_WhenCalled_ReportsMessagesFromRunToObserver() + { + // ARRANGE + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + fixture.Customizations.Add(new ThreadCountSpecimenBuilder()); + var p = fixture.Create(); + var t = fixture.Create(); + + IEnumerable runData = CreateMixedMessages(fixture); + + var runMock = new Mock().WithData(runData); + + var coreMock = new Mock(); + coreMock + .Setup(x => x.PrepareNewRun(p, t)) + .Returns(runMock.Object) + .Verifiable(Times.Once); + + var recorder = new Recorder(); + var runObserverMock = new Mock>(); + runObserverMock + .Setup( + x => x.ObserveRun(p, It.IsAny>())) + .Callback>( + (pp, obs) => obs.Subscribe(recorder)) + .Verifiable(Times.Once); + + var sut = coreMock.Object.DecorateWithRunObserver(runObserverMock.Object); + + // ACT + sut.PrepareNewRun(p, t).Start(); + + // ASSERT + coreMock.Verify(); + runObserverMock.Verify(); + recorder.Data.Should().BeEquivalentTo( + runData.OfType(), + cfg => cfg.WithoutStrictOrdering()); + } + + [Fact] + public void DecorateWithObserver_WhenCalled_ReportsMessagesFromRunToObserver() + { + // ARRANGE + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + fixture.Customizations.Add(new ThreadCountSpecimenBuilder()); + var p = fixture.Create(); + var t = fixture.Create(); + + var runData = CreateMixedMessages(fixture); + + var runMock = new Mock().WithData(runData); + + var coreMock = new Mock(); + coreMock + .Setup(x => x.PrepareNewRun(p, t)) + .Returns(runMock.Object) + .Verifiable(Times.Once); + + var recorder = new Recorder(); + + var sut = coreMock.Object.DecorateWithObserver(recorder, Scheduler.Immediate); + + // ACT + sut.PrepareNewRun(p, t).Start(); + + // ASSERT + coreMock.Verify(); + recorder.Data.Should().BeEquivalentTo( + runData.OfType(), + cfg => cfg.WithoutStrictOrdering()); + } + + private static IEnumerable CreateMixedMessages(IFixture fixture) + { + return fixture.CreateMany(5).Cast() + .Concat(fixture.CreateMany(3)) + .Concat(fixture.CreateMany(5)) + .Concat(fixture.CreateMany(8)).ToArray(); + } +} diff --git a/czishrink/netczicompressTests/Models/Mocks/FileProcessorMockExtensions.cs b/czishrink/netczicompressTests/Models/Mocks/FileProcessorMockExtensions.cs new file mode 100644 index 0000000..0edb8fa --- /dev/null +++ b/czishrink/netczicompressTests/Models/Mocks/FileProcessorMockExtensions.cs @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models.Mocks; + +/// +/// Extension method for mocks of and collections thereof. +/// +internal static class FileProcessorMockExtensions +{ + public static Mock WithProcessFile( + this Mock mock, + Action processFile) + { + mock + .Setup(x => x.ProcessFile(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Callback(processFile); + return mock; + } + + public static T CheckCount(this T listOfCreatedProcessors, int maxCount) + where T : IReadOnlyCollection> + { + listOfCreatedProcessors.Count.Should().BeLessThanOrEqualTo(maxCount); + return listOfCreatedProcessors; + } + + public static IEnumerable<(string InputFile, string OutputFile)> GetProcessedFileArgs(this Mock p) + { + var allArgses = from invocation in p.Invocations + where invocation.Method.Name == nameof(IFileProcessor.ProcessFile) + select invocation.Arguments; + + var filenameArgses = from args in allArgses + select ((string)args[0], (string)args[1]); + + return filenameArgses; + } + + public static T CheckDisposed(this T processors) + where T : IEnumerable> + { + // check all disposables have been disposed + foreach (var d in processors) + { + d.Verify(x => x.Dispose(), Times.Once); + } + + return processors; + } +} diff --git a/czishrink/netczicompressTests/Models/Mocks/FolderCompressorMockExtensions.cs b/czishrink/netczicompressTests/Models/Mocks/FolderCompressorMockExtensions.cs new file mode 100644 index 0000000..d393efe --- /dev/null +++ b/czishrink/netczicompressTests/Models/Mocks/FolderCompressorMockExtensions.cs @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models.Mocks; + +using AutoFixture; + +using IFolderCompressorRun = netczicompress.Models.IObservableAction; + +public static class FolderCompressorMockExtensions +{ + public static Mock WithRun(this Mock self, IFolderCompressorRun run) + { + self + .Setup(x => x.PrepareNewRun(It.IsAny(), It.IsAny())) + .Returns(run); + return self; + } + + public static Mock WithData(this Mock self, IEnumerable messages) + { + var runMock = new Mock().WithData(messages); + return self.WithRun(runMock.Object); + } + + public static Mock WithAutoData(this Mock self, IFixture fixture) + { + IEnumerable messages = + fixture.CreateMany(); + messages = messages.Concat( + fixture.CreateMany()); + + var runMock = new Mock().WithData(messages); + return self.WithRun(runMock.Object); + } + + public static Mock WithException(this Mock self, Exception ex) + { + var runMock = new Mock().WithException(ex); + return self.WithRun(runMock.Object); + } + + public static Mock WithWaitForCancellation(this Mock self) + { + self + .Setup( + x => x.PrepareNewRun(It.IsAny(), It.IsAny())) + .Returns( + (_, token) => new Mock().WithWaitForCancellation(token).Object); + return self; + } +} diff --git a/czishrink/netczicompressTests/Models/Mocks/FolderCompressorRunMockExtensions.cs b/czishrink/netczicompressTests/Models/Mocks/FolderCompressorRunMockExtensions.cs new file mode 100644 index 0000000..0c73082 --- /dev/null +++ b/czishrink/netczicompressTests/Models/Mocks/FolderCompressorRunMockExtensions.cs @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models.Mocks; + +using System.Reactive.Subjects; + +using IFolderCompressorRun = netczicompress.Models.IObservableAction; + +public static class FolderCompressorRunMockExtensions +{ + public static Mock> WithData(this Mock> runMock, IEnumerable messages) + { + var subject = new Subject(); + runMock.SetupGet(x => x.Output).Returns(subject.AsObservable()); + runMock + .Setup(x => x.Start()) + .Callback(() => subject.OnNextAllAndComplete(messages)); + return runMock; + } + + public static Mock WithException(this Mock runMock, Exception ex) + { + var subject = new Subject(); + runMock.SetupGet(x => x.Output).Returns(subject.AsObservable()); + runMock + .Setup(x => x.Start()) + .Callback(() => subject.OnError(ex)); + return runMock; + } + + public static Mock WithWaitForCancellation( + this Mock runMock, + CancellationToken token) + { + var subject = new Subject(); + runMock.SetupGet(x => x.Output).Returns(subject.AsObservable()); + runMock + .Setup(x => x.Start()) + .Callback(() => + { + token.Register(() => + { + try + { + token.ThrowIfCancellationRequested(); + } + catch (OperationCanceledException ex) + { + subject.OnError(ex); + } + }); + }); + return runMock; + } + + public static IObservableAction ToObservableAction(this IEnumerable messages) + { + return new Mock>().WithData(messages).Object; + } +} diff --git a/czishrink/netczicompressTests/Models/Mocks/ObservableExtensions.cs b/czishrink/netczicompressTests/Models/Mocks/ObservableExtensions.cs new file mode 100644 index 0000000..67bc023 --- /dev/null +++ b/czishrink/netczicompressTests/Models/Mocks/ObservableExtensions.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models.Mocks; + +public static class ObservableExtensions +{ + public static Recorder StartRecording(this IObservable source) + { + var result = new Recorder(); + _ = source.Subscribe(result); + return result; + } + + public static void OnNextAll(this IObserver observer, IEnumerable values) + { + foreach (var item in values) + { + observer.OnNext(item); + } + } + + public static void OnNextAllAndComplete(this IObserver observer, IEnumerable values) + { + observer.OnNextAll(values); + observer.OnCompleted(); + } +} diff --git a/czishrink/netczicompressTests/Models/Mocks/ProgressRecorder.cs b/czishrink/netczicompressTests/Models/Mocks/ProgressRecorder.cs new file mode 100644 index 0000000..0bccf58 --- /dev/null +++ b/czishrink/netczicompressTests/Models/Mocks/ProgressRecorder.cs @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models.Mocks; + +using System.Collections.Concurrent; + +/// +/// Records all progress messages from an run. +/// +internal class ProgressRecorder : IObserver +{ + public ConcurrentDictionary> ProgressValuesByFile { get; } = new(); + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(CompressorMessage.FileStarting value) + { + this.RecordProgress(value); + } + + private void RecordProgressValue(string file, int p) + { + var progressValuesForFile = this.ProgressValuesByFile.GetOrAdd(file, new List()); + progressValuesForFile.Add(p); + } + + private void RecordProgress(CompressorMessage.FileStarting fileStarting) + { + fileStarting.ProgressPercent.Subscribe( + p => this.RecordProgressValue(fileStarting.InputFile.FullName, p)); + } +} diff --git a/czishrink/netczicompressTests/Models/Mocks/Recorder{T}.cs b/czishrink/netczicompressTests/Models/Mocks/Recorder{T}.cs new file mode 100644 index 0000000..d6f5dbb --- /dev/null +++ b/czishrink/netczicompressTests/Models/Mocks/Recorder{T}.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models.Mocks; + +public class Recorder : IObserver +{ + public List Data { get; } = new(); + + public bool Completed { get; private set; } + + public Exception? Error { get; private set; } + + public void OnCompleted() + { + this.Completed = true; + } + + public void OnError(Exception error) + { + this.Error = error; + } + + public void OnNext(T value) + { + if (this.Completed || this.Error != null) + { + throw new InvalidOperationException(); + } + + this.Data.Add(value); + } +} diff --git a/czishrink/netczicompressTests/Models/Mocks/SchedulerMock.cs b/czishrink/netczicompressTests/Models/Mocks/SchedulerMock.cs new file mode 100644 index 0000000..18fef00 --- /dev/null +++ b/czishrink/netczicompressTests/Models/Mocks/SchedulerMock.cs @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models.Mocks; + +using System.Reactive.Concurrency; +using System.Reactive.Disposables; + +/// +/// A scheduler whose queue must be run explicitly. +/// +public class SchedulerMock : IScheduler +{ + public DateTimeOffset Now => DateTimeOffset.Now; + + private Queue Queue { get; } = new(); + + public void RunQueuedActions() + { + while (this.Queue.TryDequeue(out var action)) + { + action(); + } + } + + public IDisposable Schedule(TState state, Func action) + { + this.Queue.Enqueue(() => _ = action(this, state)); + return Disposable.Empty; + } + + public IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + throw new NotImplementedException(); + } + + public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) + { + throw new NotImplementedException(); + } +} diff --git a/czishrink/netczicompressTests/Models/MultiThreadedFolderCompressorTests.cs b/czishrink/netczicompressTests/Models/MultiThreadedFolderCompressorTests.cs new file mode 100644 index 0000000..aaca2b1 --- /dev/null +++ b/czishrink/netczicompressTests/Models/MultiThreadedFolderCompressorTests.cs @@ -0,0 +1,622 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO.Abstractions; +using System.IO.Abstractions.TestingHelpers; +using System.Reactive.Threading.Tasks; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +using netczicompressTests.Customizations; + +using netczicompressTests.Models.Mocks; + +/// +/// Tests for . +/// +public class MultiThreadedFolderCompressorTests +{ + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task WhenProcessorNeedsOutputDirs_RunCreatesOutputDirs(bool needsOutputDir) + { + // ARRANGE + var processor = Mock.Of(x => x.NeedsExistingOutputDirectory == needsOutputDir); + var ff = new FoldersAndFiles( + @"C:\inputDir", + @"X:\outputDir", + @"C:\inputDir\subDir\image-1.czi", + @"C:\inputDir\subDir\image-2.czi", + @"X:\outputDir\blabla.text"); + + var sut = CreateSut((_, _) => processor); + + var run = sut.PrepareNewRun( + ff.GetFolderCompressorParameters(), + CancellationToken.None); + + // ACT + Task t = run.Output.ToTask(); + run.Start(); + await t; + + // ASSERT + var fs = ff.FileSystem; + var outputSubDir = fs.DirectoryInfo.New( + fs.Path.Combine(ff.OutputDir.FullName, "subDir")); + outputSubDir.Exists.Should().Be(needsOutputDir); + } + + [Theory] + [CorrectValueObjectAutoData] + public async Task WhenRunCompletes_ThenProcessFileHasBeenCalledWithCorrectFileArgs(CompressionMode mode, ExecutionOptions executionOptions, ProcessingOptions processingOptions) + { + var ff = new FoldersAndFiles( + @"C:\inputDir", + @"D:\outputDir", + @"C:\inputDir\subDir\image-1.czi", + @"C:\inputDir\subDir\image-2.czi", + @"C:\inputDir\subDir\image-3.czi", + @"C:\inputDir\subDir2\image-4.czi", + @"C:\inputDir\subDir3\image-5.czi", + @"C:\inputDir\subDir4\ € 123,;!.czi", + @"C:\inputDir\subDir4\image-6.tiff", + @"C:\inputDir\subDir\image-7.czi", + @"D:\outputDir\blabla.czi"); + + await WhenRunCompletes_ThenProcessFileHasBeenCalledWithCorrectFileArgsImpl(mode, executionOptions, processingOptions, ff); + } + + [Theory] + [CorrectValueObjectAutoData] + public async Task WhenRunCompletes_ThenProgressHasBeenReportedForAllFiles( + CompressionMode mode, + ExecutionOptions executionOptions, + ProcessingOptions processingOptions) + { + // ARRANGE + int ProgressForFile(string inputPath) => 1 + (Math.Abs(inputPath.GetHashCode()) % 99); + + var progressRecorder = new ProgressRecorder(); + var processorFactory = CreateProcessorFactory( + mode, + processingOptions, + onMockCreated: m => m.WithProcessFile((inputPath, _, reportProgress, _) => + reportProgress(ProgressForFile(inputPath)))); + + var ff = new FoldersAndFiles( + @"C:\inputDir", + @"C:\outputDir", + @"C:\inputDir\subDir\image.czi", + @"C:\inputDir\subDir\image-2.czi", + @"C:\inputDir\subDir\image-3.czi", + @"C:\inputDir\subDir2\image.czi", + @"C:\inputDir\subDir3\image-5.czi", + @"C:\inputDir\subDir4\image-6.czi", + @"C:\inputDir\subDir\image-7.czi", + @"C:\outputDir\blabla.text"); + var czis = ff.InitialFiles.Where(f => f.EndsWith(".czi")).ToArray(); + + var sut = CreateSut(processorFactory); + + var run = sut.PrepareNewRun( + ff.GetFolderCompressorParameters(mode, executionOptions, processingOptions), + CancellationToken.None); + + run + .Output + .OfType() + .Subscribe(progressRecorder); + + // ACT + Task t = run.Output.ToTask(); + run.Start(); + await t; + + // ASSERT + progressRecorder.ProgressValuesByFile.Count.Should().Be(czis.Length); + foreach (var file in czis) + { + progressRecorder.ProgressValuesByFile[file] + .Should() + .BeEquivalentTo( + new[] { 0, ProgressForFile(file), 100 }); + } + } + + [Theory] + [CorrectValueObjectAutoData] + public async Task WhenRunCompletes_ThenHasNotProcessedAnyFilesCreatedDuringTheRun( + CompressionMode mode, ExecutionOptions executionOptions, ProcessingOptions processingOptions) + { + // Now outputDir is a subdirectory of inputDir. Files created there must not be processed. + var ff = new FoldersAndFiles( + @"C:\inputDir", + @"C:\inputDir\subDir4\outputDir", + @"C:\inputDir\subDir\image-1.czi", + @"C:\inputDir\subDir\image-2.czi", + @"C:\inputDir\subDir\image-3.czi", + @"C:\inputDir\subDir2\image-4.czi", + @"C:\inputDir\subDir3\image-5.czi", + @"C:\inputDir\subDir4\image-6.czi", + @"C:\inputDir\subDir\image-7.czi", + @"D:\outputDir\blabla.text"); + + void ConfigureProcessor(Mock mock) + { + mock.WithProcessFile((inp, outp, _, _) => ff.FileSystem.File.Copy(inp, outp)); + mock.SetupGet(p => p.NeedsExistingOutputDirectory).Returns(true); + } + + await WhenRunCompletes_ThenProcessFileHasBeenCalledWithCorrectFileArgsImpl(mode, executionOptions, processingOptions, ff, ConfigureProcessor, true); + } + + [Theory] + [ValidInlineAutoData(true)] + [ValidInlineAutoData(false)] + public async Task WhenCanceled_ThenDisposesAllProcessorsAndThrows( + bool processFileThrows, + CompressionMode mode, + ExecutionOptions executionOptions, + ProcessingOptions processingOptions) + { + var ff = new FoldersAndFiles( + @"C:\inputDir", + @"C:\outputDir", + @"C:\inputDir\subDir\image.czi", + @"C:\inputDir\subDir\image-2.czi", + @"C:\inputDir\subDir\image-3.czi", + @"C:\inputDir\subDir2\image.czi", + @"C:\inputDir\subDir3\image-5.czi", + @"C:\inputDir\subDir4\image-6.czi", + @"C:\inputDir\subDir\image-7.czi", + @"C:\outputDir\blabla.text"); + + // Auto-data does not work here without adaption. + // In the gitlab runners the maximum thread number is 2, on local machines there might be 28 threads or more + // available. + // We need to bound the number of threads by the number of czi-files, mind that one of the initial files is no czi. + var numOfCziFiles = ff.InitialFiles.Length - 1; + executionOptions = new ExecutionOptions(new ThreadCount() + { Value = Math.Min(executionOptions.ThreadCount.Value, numOfCziFiles) }); + + // ARRANGE + using var cts = new CancellationTokenSource(); + ConcurrentBag> processors = new(); + bool disposedDuringProcessing = false; + var processorFactory = CreateProcessorFactory(mode, processingOptions, onMockCreated: p => + { + processors.Add(p); + p.WithProcessFile((_, _, _, cancellationToken) => + { + cancellationToken.WaitHandle.WaitOne(); + Thread.Sleep(20); + if (p.Invocations.Any(inv => inv.Method.Name == nameof(IDisposable.Dispose))) + { + disposedDuringProcessing = true; + } + + if (processFileThrows) + { + cancellationToken.ThrowIfCancellationRequested(); + } + }); + + if (processors.Count == executionOptions.ThreadCount.Value) + { + cts.Cancel(); + } + }); + + var sut = CreateSut(processorFactory); + + var run = sut.PrepareNewRun( + ff.GetFolderCompressorParameters(mode, executionOptions, processingOptions), + cts.Token); + + // ACT + Task t = run.Output.ToTask(); + run.Start(); + Func act = () => t; + await act.Should().ThrowAsync(); + + // ASSERT + disposedDuringProcessing.Should().BeFalse(); + processors + .CheckCount(maxCount: executionOptions.ThreadCount.Value) + .CheckDisposed(); + } + + [Theory] + [CorrectValueObjectAutoData] + public async Task WhenNoTemporaryOutputFileCouldBeCreated_ThenProducesExpectedMessages( + CompressionMode mode, + ProcessingOptions processingOptions) + { + // ARRANGE + // this does not work on my local machine with other tested values than 1. + ExecutionOptions executionOptions = new ExecutionOptions(new ThreadCount() { Value = 1 }); + var ff = new FoldersAndFiles( + @"C:\inputDir", + @"C:\outputDir", + true, + @"C:\inputDir\image.czi", + @"C:\outputDir\image.czi~", + @"C:\outputDir\image.czi~1", + @"C:\outputDir\image.czi~2", + @"C:\outputDir\image.czi~3", + @"C:\outputDir\subDir\image.czi~4"); + + var processorFactory = CreateProcessorFactory( + mode, + processingOptions, + onMockCreated: m => { }); + + var sut = CreateSut(processorFactory); + + var run = sut.PrepareNewRun( + ff.GetFolderCompressorParameters(mode, executionOptions, processingOptions), + CancellationToken.None); + + var task = run.Output.ToTask(); + var recorder = new Recorder(); + using var subscription = run.Output + .OfType() + .Subscribe(recorder); + + // ACT + run.Start(); + await task; + + // ASSERT + CompressorMessage.FileFinished GetExpectation(IFileInfo inputInfo) + { + var filename = FoldersAndFiles.ToPlatformPath(@"C:\outputDir\image.czi~3"); + var error = $@"Could not delete existing temporary file {filename}."; + long expectedOutputSize = 0; + + return new CompressorMessage.FileFinished( + inputInfo, + inputInfo.Length, + expectedOutputSize, + null, + error); + } + + var inputFileName = FoldersAndFiles.ToPlatformPath(@"C:\inputDir\image.czi"); + var inputFile = ff!.FileSystem.FileInfo.New(inputFileName); + + var expectation = GetExpectation(inputFile); + + var actualAsStrings = from item in recorder.Data select item.ToString(); + var actualAsStringsWithoutTimeElapsed = actualAsStrings.Select(x => RemoveTimeStamp(x)); + Collection expectationAsStrings = new(); + expectationAsStrings.Add(expectation.ToString()); + + // TODO: debug this, this seems to fail sometimes as the order of files/outputs seems not to be fixed. + actualAsStringsWithoutTimeElapsed.Should().BeEquivalentTo( + expectationAsStrings, + compare => compare.WithoutStrictOrdering()); + } + + [Theory] + [CorrectValueObjectAutoData] + public async Task WhenOneOutputFileExists_ThenProducesExpectedMessages( + CompressionMode mode, + ProcessingOptions processingOptions) + { + // ARRANGE + // this does not work on my local machine with other tested values than 1. + ExecutionOptions executionOptions = new ExecutionOptions(new ThreadCount() { Value = 1 }); + var ff = new FoldersAndFiles( + @"C:\inputDir", + @"C:\outputDir", + @"C:\inputDir\subDir\image.czi", + @"C:\inputDir\subDir\image-3.czi", + @"C:\inputDir\subDir2\image.czi", + @"C:\inputDir\subDir3\image-6.czi", + @"C:\inputDir\subDir4\image-6.czi", + @"C:\outputDir\subDir\image-3.czi"); + + static IOException? GetErrorForFile(string path) => + path.EndsWith("6.czi") ? new IOException("FooBar " + path) : null; + + var processorFactory = CreateProcessorFactory( + mode, + processingOptions, + onMockCreated: m => + { + m.WithProcessFile( + (inputPath, outputPath, _, _) => + { + var ex = GetErrorForFile(inputPath); + if (ex != null) + { + throw ex; + } + + lock (ff.FileSystem) + { + ff.FileSystem.File.AppendAllText( + outputPath, + inputPath + inputPath); + } + }); + m.SetupGet(x => x.NeedsExistingOutputDirectory).Returns(true); + }); + + var sut = CreateSut(processorFactory); + + var run = sut.PrepareNewRun( + ff.GetFolderCompressorParameters(mode, executionOptions, processingOptions), + CancellationToken.None); + + var task = run.Output.ToTask(); + var recorder = new Recorder(); + using var subscription = run.Output + .OfType() + .Subscribe(recorder); + + // ACT + run.Start(); + await task; + + // ASSERT + CompressorMessage.FileFinished GetExpectation(string inputFile) + { + var fileInfo = ff!.FileSystem.FileInfo.New(inputFile); + var error = GetErrorForFile(inputFile)?.Message; + long expectedOutputSize = error == null ? fileInfo.Length * 2 : 0; + + if (inputFile.EndsWith("image-3.czi")) + { + // output file exists + var outputFile = inputFile.Replace(ff.InputDir.FullName, ff.OutputDir.FullName); + error = $"Output file {outputFile} already exists."; + expectedOutputSize = fileInfo.Length + 1; // unchanged from initial file + } + + return new CompressorMessage.FileFinished( + fileInfo, + fileInfo.Length, + expectedOutputSize, + null, + error); + } + + var expectation = from path in ff.InitialFiles + where path.EndsWith(".czi") && path.StartsWith(ff.InputDir.FullName) + select GetExpectation(path); + + var actualAsStrings = from item in recorder.Data select item.ToString(); + var actualAsStringsWithoutTimeElapsed = actualAsStrings.Select(x => RemoveTimeStamp(x)); + var expectationAsStrings = from item in expectation select item.ToString(); + + // TODO: debug this, this seems to fail sometimes as the order of files/outputs seems not to be fixed. + actualAsStringsWithoutTimeElapsed.Should().BeEquivalentTo( + expectationAsStrings, + compare => compare.WithoutStrictOrdering()); + } + + private static string RemoveTimeStamp(string target) + { + const string timeElapsedPattern = $"\\b{nameof(CompressorMessage.FileFinished.TimeElapsed)}[^\\r\\n,]*,"; + const string timeElapsedReplacement = $"{nameof(CompressorMessage.FileFinished.TimeElapsed)} = ,"; + return Regex.Replace(target, timeElapsedPattern, timeElapsedReplacement); + } + + private static async Task WhenRunCompletes_ThenProcessFileHasBeenCalledWithCorrectFileArgsImpl( + CompressionMode mode, + ExecutionOptions executionOptions, + ProcessingOptions processingOptions, + FoldersAndFiles ff, + Action>? configureProcessor = null, + bool forceLazyFileSystemEnumeration = false) + { + // ARRANGE + ConcurrentBag> processors = new(); + var processorFactory = CreateProcessorFactory( + mode, + processingOptions, + onMockCreated: p => + { + processors.Add(p); + configureProcessor?.Invoke(p); + }); + string GetExpectedOutputPath(string f) => f.Replace(ff.InputDir.FullName, ff.OutputDir.FullName); + + MultiThreadedFolderCompressor sut; + if (forceLazyFileSystemEnumeration) + { + sut = new LazyEnumerateCziSut(processorFactory) + { + MatchExtensionCasing = MatchCasing.PlatformDefault, // CaseInsensitive is currently not supported by MockFileSystem + AttributesToSkip = FileAttributes.Hidden | FileAttributes.System, // AttributesToSkip is currently not supported by MockFileSystem + }; + } + else + { + sut = CreateSut(processorFactory); + } + + var run = sut.PrepareNewRun( + ff.GetFolderCompressorParameters(mode, executionOptions, processingOptions), + CancellationToken.None); + + // ACT + Task t = run.Output.ToTask(); + run.Start(); + await t; + + // ASSERT + processors + .CheckCount(maxCount: executionOptions.ThreadCount.Value) + .CheckDisposed(); + + var actualFileNamePairs = + from p in processors + from argsPair in p.GetProcessedFileArgs() + select argsPair; + + var expectedFileNamePairs = + from f in ff.InitialFiles + where f.EndsWith(".czi") && f.StartsWith(ff.InputDir.FullName) + select (f, GetExpectedOutputPath(f) + "~"); + + actualFileNamePairs.Should().BeEquivalentTo( + expectedFileNamePairs, + compare => compare.WithoutStrictOrdering()); + } + + private static MultiThreadedFolderCompressor CreateSut( + CreateProcessor processorFactory, + int maxTriesToCreateTemporaryFiles = 4) + { + var sut = new MultiThreadedFolderCompressor( + processorFactory, + new FileProcessingFailedHandler(), + maxTriesToCreateTemporaryFiles) + { + MatchExtensionCasing = + MatchCasing.PlatformDefault, // CaseInsensitive is currently not supported by MockFileSystem + AttributesToSkip = FileAttributes.System | FileAttributes.Hidden, // AttributesToSkip is not supported by MockFileSystem + }; + return sut; + } + + private static CreateProcessor CreateProcessorFactory( + CompressionMode expectedMode, + ProcessingOptions expectedProcessingOptions, + Action>? onMockCreated = null) + { + // Strict mock behavior checks that we are receiving the correct mode argument. + var createProcessor = new Mock(MockBehavior.Strict); + createProcessor.Setup(x => x.Invoke(expectedMode, expectedProcessingOptions)).Returns( + (_, _) => + { + var p = new Mock().WithProcessFile((_, _, _, c) => c.ThrowIfCancellationRequested()); + + onMockCreated?.Invoke(p); + + return p.Object; + }); + + return createProcessor.Object; + } + + private class FoldersAndFiles + { + public FoldersAndFiles(string inputDir, string outputDir, params string[] files) + : this(inputDir, outputDir, false, files) + { + } + + public FoldersAndFiles(string inputDir, string outputDir, bool allFilesReadonly, params string[] files) + { + this.InitialFiles = files.Select(ToPlatformPath).ToArray(); + this.FileSystem = CreateFileSystem(this.InitialFiles, allFilesReadonly); + this.InputDir = this.FileSystem.DirectoryInfo.New(ToPlatformPath(inputDir)); + this.OutputDir = this.FileSystem.DirectoryInfo.New(ToPlatformPath(outputDir)); + } + + public IFileSystem FileSystem { get; } + + public string[] InitialFiles { get; } + + public IDirectoryInfo InputDir { get; } + + public IDirectoryInfo OutputDir { get; } + + public static string ToPlatformPath(string path) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return path; + } + + var posixPath = path.Replace('\\', '/'); + posixPath = posixPath.Replace(":", string.Empty); + posixPath = "/cygdrive/" + posixPath; + return posixPath; + } + + public FolderCompressorParameters GetFolderCompressorParameters( + CompressionMode mode = CompressionMode.CompressUncompressed, + ExecutionOptions? executionOptions = null, + ProcessingOptions? processingOptions = null, + bool recursive = true) + { + processingOptions ??= new ProcessingOptions(CompressionLevel.Default); + executionOptions ??= new ExecutionOptions(ThreadCount.Default); + return new FolderCompressorParameters( + this.InputDir, + this.OutputDir, + recursive, + mode, + executionOptions, + processingOptions); + } + + private static MockFileSystem CreateFileSystem(string[] files, bool allFilesReadonly = false) + { + var filesDict = files.ToDictionary(f => f, f => new MockFileData(f)); + if (allFilesReadonly) + { + foreach (var file in filesDict.Values) + { + file.Attributes = FileAttributes.ReadOnly; + } + } + + var result = new MockFileSystem(filesDict); + return result; + } + } + + // Overrides EnumerateCzisAsync to first return files and then look into subdirectories. + // This is necessary when we want to test that output files placed into a subdirectory + // of the input directory are skipped. It seems that MockFileSystem collects all files + // immediately (in GetEnumerator() not in MoveNext()). + private class LazyEnumerateCziSut : MultiThreadedFolderCompressor + { + public LazyEnumerateCziSut(CreateProcessor createProcessor) + : base(createProcessor, new FileProcessingFailedHandler()) + { + } + + protected override IAsyncEnumerable EnumerateCzisAsync(IDirectoryInfo folder, bool recursive, CancellationToken token) + { + return recursive + ? LazyEnumerateCzisAsync(folder, token) + : base.EnumerateCzisAsync(folder, false, token); + + async IAsyncEnumerable LazyEnumerateCzisAsync( + IDirectoryInfo folder, + [EnumeratorCancellation] CancellationToken token) + { + await foreach (var item in base.EnumerateCzisAsync(folder, false, token) + .WithCancellation(token)) + { + yield return item; + } + + foreach (var dir in folder.EnumerateDirectories()) + { + await foreach (var item in LazyEnumerateCzisAsync(dir, token) + .WithCancellation(token)) + { + yield return item; + } + } + } + } + } +} diff --git a/czishrink/netczicompressTests/Models/NoOpFileProcessorTests.cs b/czishrink/netczicompressTests/Models/NoOpFileProcessorTests.cs new file mode 100644 index 0000000..91584ef --- /dev/null +++ b/czishrink/netczicompressTests/Models/NoOpFileProcessorTests.cs @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +/// +/// Tests for . +/// +public class NoOpFileProcessorTests +{ + [Fact] + public void NeedsExistingOutputDirectory_ShouldBeFalse() + { + using var target = new NoOpFileProcessor(); + target.NeedsExistingOutputDirectory.Should().BeFalse(); + } + + [Fact] + public void ProcessFile_WhenCalled_ReportsProgress100() + { + // ARRANGE + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var mock = fixture.Freeze>(); + + var sut = new NoOpFileProcessor(); + + // ACT + sut.ProcessFile( + fixture.Create(), + fixture.Create(), + mock.Object, + fixture.Create()); + + // ASSERT + mock.Verify(x => x.Invoke(100), Times.Once); + mock.VerifyNoOtherCalls(); + } +} diff --git a/czishrink/netczicompressTests/Models/PInvokeFileProcessorTests.cs b/czishrink/netczicompressTests/Models/PInvokeFileProcessorTests.cs new file mode 100644 index 0000000..2fbea63 --- /dev/null +++ b/czishrink/netczicompressTests/Models/PInvokeFileProcessorTests.cs @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using System.Reactive.Disposables; + +/// +/// Tests for . +/// +public class PInvokeFileProcessorTests +{ + [Fact] + public void Ctr_WhenCalledWithNoOp_Throws() + { + Action act = () => _ = new PInvokeFileProcessor(CompressionMode.NoOp, new ProcessingOptions(new CompressionLevel())); + + act.Should().Throw().WithParameterName("mode"); + } + + [Theory] + [InlineData(CompressionMode.CompressUncompressed)] + [InlineData(CompressionMode.CompressAll)] + [InlineData(CompressionMode.CompressUncompressedAndZstd)] + public void CompressAndDecompressOneFile_FilesHaveCorrectSize( + CompressionMode mode) + { + // ARRANGE + var testFile = GetTestFilePath(); + + var compressed = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".czi"); + using var deleteCompressed = DeleteLater(compressed); + + var uncompressed = compressed + "-decompressed.czi"; + using var deleteUncompressed = Disposable.Create(() => File.Delete(uncompressed)); + + // ACT + using var compressor = new PInvokeFileProcessor(mode, new ProcessingOptions(new CompressionLevel())); + compressor.ProcessFile(testFile, compressed, _ => { }, CancellationToken.None); + + using var decompressor = new PInvokeFileProcessor(CompressionMode.Decompress, new ProcessingOptions(new CompressionLevel())); + decompressor.ProcessFile(compressed, uncompressed, _ => { }, CancellationToken.None); + + var compressedSize = GetLength(compressed); + var decompressedSize = GetLength(uncompressed); + var originalSize = GetLength(testFile); + + // ASSERT + compressedSize.Should().Be(58432L); + decompressedSize.Should().BeCloseTo(originalSize, 2048); + decompressedSize.Should().Be(99552L); + } + + [Theory] + [InlineData(CompressionMode.CompressUncompressed)] + [InlineData(CompressionMode.CompressAll)] + [InlineData(CompressionMode.CompressUncompressedAndZstd)] + [InlineData(CompressionMode.Decompress)] + public void NeedsExistingOutputDirectory_ShouldBeTrue( + CompressionMode mode) + { + using var target = new PInvokeFileProcessor(mode, new ProcessingOptions(new CompressionLevel())); + target.NeedsExistingOutputDirectory.Should().BeTrue(); + } + + [Theory] + [InlineData(CompressionMode.CompressUncompressed)] + [InlineData(CompressionMode.CompressAll)] + [InlineData(CompressionMode.CompressUncompressedAndZstd)] + [InlineData(CompressionMode.Decompress)] + public void ProcessFile_WhenCancelled_Throws( + CompressionMode mode) + { + // ARRANGE + string testFile = GetTestFilePath(); + + var compressed = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".czi"); + using var deleteCompressed = DeleteLater(compressed); + + // ACT + using var compressor = PInvokeFileProcessor.Create(mode, new ProcessingOptions(new CompressionLevel())); + var token = new CancellationToken(true); + Action act = () => compressor.ProcessFile(testFile, compressed, _ => { }, token); + + // ASSERT + act.Should().Throw() + .Which + .CancellationToken.Should().Be(token); + } + + [Theory] + [InlineData(CompressionMode.CompressUncompressed)] + [InlineData(CompressionMode.CompressAll)] + [InlineData(CompressionMode.CompressUncompressedAndZstd)] + [InlineData(CompressionMode.Decompress)] + public void ProcessFile_WhenDisposed_Throws( + CompressionMode mode) + { + // ARRANGE + using var compressor = PInvokeFileProcessor.Create(mode, new ProcessingOptions(new CompressionLevel())); + compressor.Dispose(); + + Action act = () => compressor.ProcessFile("foo", "bar", _ => { }, CancellationToken.None); + + // ASSERT + act.Should().Throw(); + } + + [Fact] + public void GetLibFullName_WhenCalled_ReturnsExpected() + { + PInvokeFileProcessor.GetLibFullName().Should().Match("libczicompressc 0.*.*"); + } + + [Theory] + [InlineData(0, 99552L, 57760L)] + [InlineData(2, 99552L, 57920L)] + [InlineData(4, 99552L, 57472L)] + public void CompressWithLevel_FilesHaveCorrectSize( + int compressionLevel, long expectedUncompressedSize, long expectedCompressedSize) + { + // ARRANGE + var testFile = GetTestFilePath(); + + var compressed = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".czi"); + using var deleteCompressed = DeleteLater(compressed); + + var uncompressed = compressed + "-decompressed.czi"; + using var deleteUncompressed = Disposable.Create(() => File.Delete(uncompressed)); + + // ACT + using var compressor = new PInvokeFileProcessor( + CompressionMode.CompressAll, + new ProcessingOptions(new CompressionLevel() + { + Value = compressionLevel, + })); + compressor.ProcessFile(testFile, compressed, _ => { }, CancellationToken.None); + + using var decompressor = new PInvokeFileProcessor(CompressionMode.Decompress, new ProcessingOptions(new CompressionLevel())); + decompressor.ProcessFile(compressed, uncompressed, _ => { }, CancellationToken.None); + + var compressedSize = GetLength(compressed); + var decompressedSize = GetLength(uncompressed); + var originalSize = GetLength(testFile); + + // ASSERT + compressedSize.Should().Be(expectedCompressedSize); + decompressedSize.Should().Be(expectedUncompressedSize); + } + + private static long GetLength(string path) => new FileInfo(path).Length; + + private static IDisposable DeleteLater(string path) => Disposable.Create(() => File.Delete(path)); + + private static string GetTestFilePath() + { + var dirname = Path.GetDirectoryName(typeof(PInvokeFileProcessorTests).Assembly.Location); + dirname.Should().NotBeNull(); + var testFile = Path.Combine(dirname ?? ".", "mandelbrot.czi"); + return testFile; + } +} diff --git a/czishrink/netczicompressTests/Models/ProgramNameAndVersionTests.cs b/czishrink/netczicompressTests/Models/ProgramNameAndVersionTests.cs new file mode 100644 index 0000000..7c395ab --- /dev/null +++ b/czishrink/netczicompressTests/Models/ProgramNameAndVersionTests.cs @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using System.Text.RegularExpressions; + +/// +/// Tests for . +/// +public class ProgramNameAndVersionTests +{ + [Fact] + public void ToString_WhenCalled_ReturnsExpected() + { + // ACT + var actual = new ProgramNameAndVersion().ToString(); + + // ASSERT + var re = new Regex(@"^CZI Shrink 1\.0\.0-alpha\.[1-9]\d\d*$"); + re.IsMatch(actual).Should().BeTrue(); + } + + [Fact] + public void Name_WhenCalled_ReturnsExpected() + { + // ACT + var actual = new ProgramNameAndVersion().Name; + + // ASSERT + actual.Should().Be("CZI Shrink"); + } + + [Fact] + public void Version_WhenCalled_ReturnsExpected() + { + // ACT + var actual = new ProgramNameAndVersion().Version; + + // ASSERT + var re = new Regex(@"^1\.0\.0-alpha\.[1-9]\d\d*$"); + re.IsMatch(actual).Should().BeTrue(); + } +} diff --git a/czishrink/netczicompressTests/Models/StatisticsRunObserverTests.cs b/czishrink/netczicompressTests/Models/StatisticsRunObserverTests.cs new file mode 100644 index 0000000..5488bfa --- /dev/null +++ b/czishrink/netczicompressTests/Models/StatisticsRunObserverTests.cs @@ -0,0 +1,109 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using System.Collections.Generic; +using System.Diagnostics; +using System.IO.Abstractions; +using System.Reactive.Subjects; + +using netczicompressTests.Customizations; +using netczicompressTests.Models.Mocks; +using static netczicompress.Models.CompressorMessage; + +/// +/// Tests for . +/// +public class StatisticsRunObserverTests +{ + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(5)] + [InlineData(15)] + public void ObserveRun_WhenRunCompletes_CorrectFirstAndFinalResult(int scanTimeMillis) + { + // ARRANGE + var f = new Fixture().Customize(new AutoMoqCustomization()); + f.Customizations.Add(new ThreadCountSpecimenBuilder()); + var parameters = f.Create(); + + var recorder = new Recorder(); + var fileMock = new Mock(MockBehavior.Strict).Object; + + var data = new FileFinished[] + { + new(fileMock, 20, 10, null, null), + new(fileMock, 20, 10, null, null), + new(fileMock, 20, 10, null, null), + new(fileMock, 20, 10, null, null), + new(fileMock, 20, 10, null, null), + new(fileMock, 500, 400, null, null), + new(fileMock, 10, 1000, null, "Failed"), + new(fileMock, 10, 1000, null, "Failed"), + }; + + var observable = new Subject(); + var watch = Stopwatch.StartNew(); + + var sut = new StatisticsRunObserver( + recorder, + TimeSpan.FromMilliseconds(scanTimeMillis), + DefaultScheduler.Instance); + + // ACT + sut.ObserveRun(parameters, observable.AsObservable()); + + foreach (var item in data) + { + Thread.Sleep(scanTimeMillis / 2); + observable.OnNext(item); + } + + observable.OnCompleted(); + + var durationUpperLimit = watch.Elapsed + + TimeSpan.FromMilliseconds(2); + + SpinWait.SpinUntil(() => recorder.Completed); + + // ASSERT + recorder.Data.Count.Should().BeInRange(2, data.Length + 1); + AssertMonotonicity(recorder.Data); + + recorder.Data[0].Should().Be(AggregateStatistics.Empty); + + var lastStats = recorder.Data[^1]; + + lastStats.FilesWithErrors.Should().Be(2); + lastStats.FilesWithNoErrors.Should().Be(6); + lastStats.OutputToInputRatio.Should().Be(450.0f / 600); + lastStats.DeltaBytes.Should().Be(450 - 600); + lastStats.InputBytes.Should().Be(600); + lastStats.OutputBytes.Should().Be(450); + lastStats.Duration.Should().BeGreaterThan(TimeSpan.Zero); + lastStats.Duration.Should().BeLessThan(durationUpperLimit); + } + + private static void AssertMonotonicity( + IReadOnlyList data) + { + for (int i = 1; i < data.Count; i++) + { + var current = data[i]; + var previous = data[i - 1]; + + current.FilesWithErrors.Should().BeGreaterThanOrEqualTo( + previous.FilesWithErrors); + current.FilesWithNoErrors.Should().BeGreaterThanOrEqualTo( + previous.FilesWithNoErrors); + current.InputBytes.Should().BeGreaterThanOrEqualTo( + previous.InputBytes); + current.OutputBytes.Should().BeGreaterThanOrEqualTo( + previous.OutputBytes); + current.Duration.Should().BeGreaterThan(previous.Duration); + } + } +} diff --git a/czishrink/netczicompressTests/Models/TemporaryFileTests.cs b/czishrink/netczicompressTests/Models/TemporaryFileTests.cs new file mode 100644 index 0000000..cd384ff --- /dev/null +++ b/czishrink/netczicompressTests/Models/TemporaryFileTests.cs @@ -0,0 +1,239 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using System.IO.Abstractions; +using System.IO.Abstractions.TestingHelpers; + +/// +/// Tests for . +/// +public class TemporaryFileTests +{ + [Fact] + public void WhenNoOutputFileExist_CreationOfTemporaryOutputDirSucceeds() + { + // ARRANGE + var fileSystem = new MockFileSystem(new Dictionary()); + var outFile = fileSystem.FileInfo.New("outFile"); + + // ACT + var sut = new TemporaryFileOverridenDelete(outFile); + + // ASSERT + var expectedOutFileName = fileSystem.Path.GetFullPath("outFile"); + var expectedTempOutFileName = expectedOutFileName + "~"; + sut.GetFullOutFileName().Should().Be(expectedOutFileName); + sut.TemporaryFileCreationFailed.Should().BeFalse(); + sut.Info.FullName.Should().Be(expectedTempOutFileName); + sut.Info.Exists.Should().BeFalse(); + } + + [Fact] + public void WhenNoOutputFileExistAndTemporaryFilesAreWritten_TemporaryFileShouldExistAndOutputFileNotYet() + { + // ARRANGE + var fileSystem = new MockFileSystem(new Dictionary()); + var outFile = fileSystem.FileInfo.New("outFile"); + + // ACT + var sut = new TemporaryFileOverridenDelete(outFile); + sut.Info.Create(); // simulates the compression processing + + // ASSERT + sut.GetOutputFile().Exists.Should().BeFalse(); + sut.MoveToOutFileIfExists(); + sut.GetOutputFile().Exists.Should().BeTrue(); + } + + [Fact] + public void WhenNoOutputFileExistAndTemporaryFilesAreWrittenAndMoved_OutFileShouldExistAndTemporaryFileNotMore() + { + // ARRANGE + var fileSystem = new MockFileSystem(new Dictionary()); + var outFile = fileSystem.FileInfo.New("outFile"); + + // ACT + var sut = new TemporaryFileOverridenDelete(outFile); + sut.Info.Create(); // simulates the compression processing + sut.MoveToOutFileIfExists(); // simulates the compression processing + + // ASSERT + var expectedTempOutFileName = fileSystem.Path.GetFullPath("outFile") + "~"; + + // the sut's temporary output file will be renamed upon the move, + // so we need to create a new file info from the original file name. + var tempOutFile = fileSystem.FileInfo.New(expectedTempOutFileName); + tempOutFile.Exists.Should().BeFalse(); + sut.DeleteAllOutFiles(); + sut.GetOutputFile().Exists.Should().BeFalse(); + } + + [Fact] + public void WhenOutputFileExistsAndCanBeDeleted_CreationOfTemporaryOutputFileSucceeds() + { + // ARRANGE + var fileSystem = CreateFileSystem(); + var outFile = fileSystem.FileInfo.New("outFile"); + + // ACT + var sut = new TemporaryFileOverridenDelete(outFile); + + // ASSERT + var expectedOutFileName = fileSystem.Path.GetFullPath("outFile"); + var expectedTempOutFileName = expectedOutFileName + "~"; + sut.GetFullOutFileName().Should().Be(expectedOutFileName); + sut.TemporaryFileCreationFailed.Should().BeFalse(); + sut.Info.FullName.Should().Be(expectedTempOutFileName); + sut.Info.Exists.Should().BeFalse(); + } + + [Fact] + public void WhenFourTemporaryOutputFileExistAndCannotBeDeleted_CreationOfTemporaryOutputFileSucceeds() + { + // ARRANGE + var fileSystem = CreateFileSystem(readOnlyLevel: 2); + var outFile = fileSystem.FileInfo.New("outFile"); + + // ACT + var sut = new TemporaryFileOverridenDelete(outFile); + + // ASSERT + var expectedOutFileName = fileSystem.Path.GetFullPath("outFile"); + var expectedTempOutFileName = expectedOutFileName + "~4"; + sut.GetFullOutFileName().Should().Be(expectedOutFileName); + sut.TemporaryFileCreationFailed.Should().BeFalse(); + sut.Info.FullName.Should().Be(expectedTempOutFileName); + sut.Info.Exists.Should().BeFalse(); + } + + [Fact] + public void WhenFourTemporaryOutputFileExistAndCannotBeDeletedAfterMove_OutputFileShouldExistAndTemporaryFileNoMote() + { + // ARRANGE + var fileSystem = CreateFileSystem(readOnlyLevel: 2); + var outFile = fileSystem.FileInfo.New("outFile"); + + // ACT + var sut = new TemporaryFileOverridenDelete(outFile); + sut.Info.Create(); // simulates the compression processing + var tempOutFileName = sut.Info.FullName; + sut.MoveToOutFileIfExists(); + + // ASSERT + sut.GetOutputFile().Exists.Should().BeTrue(); + + // the sut's temporary output file will be renamed upon the move, + // so we need to create a new file info from the original file name. + fileSystem.FileInfo.New(tempOutFileName).Exists.Should().BeFalse(); + } + + [Fact] + public void WhenFourTemporaryOutputFileExistAndTwoCannotBeDeleted_CreationOfTemporaryOutputFileSucceeds() + { + // ARRANGE + var fileSystem = CreateFileSystem(readOnlyLevel: 1); + var outFile = fileSystem.FileInfo.New("outFile"); + + // ACT + var sut = new TemporaryFileOverridenDelete(outFile); + + // ASSERT + var expectedOutFileName = fileSystem.Path.GetFullPath("outFile"); + var expectedTempOutFileName = expectedOutFileName + "~2"; + sut.GetFullOutFileName().Should().Be(expectedOutFileName); + sut.TemporaryFileCreationFailed.Should().BeFalse(); + sut.Info.FullName.Should().Be(expectedTempOutFileName); + sut.Info.Exists.Should().BeFalse(); + } + + [Fact] + public void WhenFourTemporaryOutputFileExistAndTwoCannotBeDeletedAfterMove_OutputFileShouldExistAndTemporaryFileNoMote() + { + // ARRANGE + var fileSystem = CreateFileSystem(readOnlyLevel: 1); + var outFile = fileSystem.FileInfo.New("outFile"); + + // ACT + var sut = new TemporaryFileOverridenDelete(outFile); + sut.Info.Create(); // simulates the compression processing + var tempOutFileName = sut.Info.FullName; + sut.MoveToOutFileIfExists(); + + // ASSERT + sut.GetOutputFile().Exists.Should().BeTrue(); + fileSystem.FileInfo.New(tempOutFileName).Exists.Should().BeFalse(); + } + + [Fact] + public void WhenMaximumTriesToCreateTemporaryFileAreReached_CreationOfTemporaryOutputFileFails() + { + // ARRANGE + var fileSystem = CreateFileSystem(readOnlyLevel: 2); + var outFile = fileSystem.FileInfo.New("outFile"); + + // ACT + var sut = new TemporaryFileOverridenDelete(outFile, 4); + + // ASSERT + sut.TemporaryFileCreationFailed.Should().BeTrue(); + } + + private static MockFileSystem CreateFileSystem(uint readOnlyLevel = 0) + { + // var outFileName = "outFile"; + var tempOutFile = "outFile~"; + var tempOutFile1 = "outFile~1"; + var tempOutFile2 = "outFile~2"; + var tempOutFile3 = "outFile~3"; + + // var outFileMock = new MockFileData("this is the outfile."); + var tempOutFileMock = new MockFileData("this is the temporary outfile."); + var tempOutFileMock1 = new MockFileData("this is the temporary outfile 1."); + var tempOutFileMock2 = new MockFileData("this is the temporary outfile 2."); + var tempOutFileMock3 = new MockFileData("this is the temporary outfile 3."); + + if (readOnlyLevel >= 1) + { + tempOutFileMock.Attributes = FileAttributes.ReadOnly; + tempOutFileMock1.Attributes = FileAttributes.ReadOnly; + } + + if (readOnlyLevel >= 2) + { + tempOutFileMock2.Attributes = FileAttributes.ReadOnly; + tempOutFileMock3.Attributes = FileAttributes.ReadOnly; + } + + var files = new Dictionary + { + { tempOutFile, tempOutFileMock }, + { tempOutFile1, tempOutFileMock1 }, + { tempOutFile2, tempOutFileMock2 }, + { tempOutFile3, tempOutFileMock3 }, + }; + + var result = new MockFileSystem(files); + return result; + } + + private class TemporaryFileOverridenDelete : TemporaryFile + { + public TemporaryFileOverridenDelete(IFileInfo outfile, int maxNumberOfTriesToCreate = 5) + : base(outfile, maxNumberOfTriesToCreate) + { + } + + public IFileInfo GetOutputFile() + { + return this.OutFile; + } + + public string GetFullOutFileName() + { + return this.OutFile.FullName; + } + } +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/Models/ThreadCountTests.cs b/czishrink/netczicompressTests/Models/ThreadCountTests.cs new file mode 100644 index 0000000..d9c3fbc --- /dev/null +++ b/czishrink/netczicompressTests/Models/ThreadCountTests.cs @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +/// +/// Tests for . +/// +public class ThreadCountTests +{ + [Fact] + public void DefaultConstructor_HasDefaultValue() + { + var sut = new ThreadCount(); + sut.Value.Should().Be(ThreadCount.DefaultValue); + } + + [Theory] + [InlineData(0)] + [InlineData(-100)] + [InlineData(230)] + public void OutOfRange_ShouldThrowOutOfRangeException(int value) + { + var act = () => + { + var sut = new ThreadCount() { Value = value }; + }; + + act.Should().Throw(); + } + + [Theory] + [InlineData(1)] + public void InRangeValue_ShouldBeEqual(int value) + { + var sut = new ThreadCount() { Value = value }; + sut.Value.Should().Be(value); + } +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/Models/TraceLoggerFactoryTests.cs b/czishrink/netczicompressTests/Models/TraceLoggerFactoryTests.cs new file mode 100644 index 0000000..018b7d6 --- /dev/null +++ b/czishrink/netczicompressTests/Models/TraceLoggerFactoryTests.cs @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.Models; + +using System.Diagnostics; +using System.Text; +using Microsoft.Extensions.Logging; + +/// +/// Tests for . +/// +public sealed class TraceLoggerFactoryTests : IDisposable +{ + private readonly MemoryTraceListener listener = new(); + + public TraceLoggerFactoryTests() + { + Trace.Listeners.Add(this.listener); + this.listener.Reset(); + } + + [Fact] + public void AddProvider_WhenCalled_Throws() + { + // ARRANGE + using ILoggerFactory sut = new TraceLoggerFactory(); + + // ACT + Action act = () => sut.AddProvider(Mock.Of()); + + // ASSERT + act.Should().Throw(); + } + + [Theory] + [InlineData(true, LogLevel.Information, "Information", "INFO")] + [InlineData(false, LogLevel.Information, "Information", "INFO")] + [InlineData(true, LogLevel.Warning, "Warning", "WARN")] + [InlineData(false, LogLevel.Warning, "Warning", "WARN")] + [InlineData(true, LogLevel.Error, "Error", "ERROR")] + [InlineData(false, LogLevel.Error, "Error", "ERROR")] + [InlineData(true, LogLevel.Critical, "Error", "FATAL")] + [InlineData(false, LogLevel.Critical, "Error", "FATAL")] + public void LogHighLevel_WhenCalled_TracesExpected( + bool useLogLevels, + LogLevel level, + string expectedTraceLevel, + string expectedLogLevel) + { + // ARRANGE + using ILoggerFactory sut = new TraceLoggerFactory(useLogLevels); + var logger = sut.CreateLogger(); + + // ACT + DateTimeOffset before = DateTimeOffset.Now; + logger.Log(level, "Bla {eins} {zwei}", 1, 2); + + // ASSERT + var trace = this.listener.ToString(); + if (useLogLevels) + { + trace = CheckAndSkipPrefix(trace, expectedTraceLevel); + } + + CheckLogMessage(trace, before, expectedLogLevel, "Bla 1 2"); + } + + [Theory] + [InlineAutoData(LogLevel.None)] + [InlineAutoData(LogLevel.Debug)] + [InlineAutoData(LogLevel.Trace)] + [InlineAutoData((LogLevel)int.MaxValue)] + public void LogLowLevel_WhenCalled_TracesNothing(LogLevel level, bool useLogLevels) + { + // ARRANGE + using ILoggerFactory sut = new TraceLoggerFactory(useLogLevels); + var logger = sut.CreateLogger(); + + // ACT + logger.Log(level, "Bla {eins} {zwei}", 1, 2); + + // ASSERT + var trace = this.listener.ToString(); + trace.Should().BeEmpty(); + } + + [Theory] + [InlineAutoData(LogLevel.None, false)] + [InlineAutoData(LogLevel.Debug, false)] + [InlineAutoData(LogLevel.Trace, false)] + [InlineAutoData((LogLevel)int.MaxValue, false)] + [InlineAutoData(LogLevel.Information, true)] + [InlineAutoData(LogLevel.Warning, true)] + [InlineAutoData(LogLevel.Error, true)] + [InlineAutoData(LogLevel.Critical, true)] + public void LoggerIsEnabled_WhenCalled_ReturnsExpected(LogLevel level, bool expected, bool useLogLevels) + { + // ARRANGE + using ILoggerFactory sut = new TraceLoggerFactory(useLogLevels); + var logger = sut.CreateLogger(); + + // ACT + var actual = logger.IsEnabled(level); + + // ASSERT + actual.Should().Be(expected); + } + + [Theory] + [AutoData] + public void LoggerBeginScope_WhenCalled_ReturnsNull(bool useLogLevels) + { + // ARRANGE + using ILoggerFactory sut = new TraceLoggerFactory(useLogLevels); + var logger = sut.CreateLogger(); + + // ACT + var actual = logger.BeginScope("bla"); + + // ASSERT + actual.Should().BeNull(); + this.listener.ToString().Should().BeEmpty(); + } + + public void Dispose() + { + Trace.Listeners.Remove(this.listener); + } + + private static string CheckAndSkipPrefix(string trace, string level) + { + // we expect that the trace-text starts with something like " {level}: 0 : ", + // and we want to remove this part from the string + var levelText = $"{level}: 0 : "; + int index = trace.IndexOf(levelText, StringComparison.Ordinal); + index.Should().BePositive(); + trace = trace[(index + levelText.Length)..]; + return trace; + } + + private static void CheckLogMessage(string logLine, DateTimeOffset before, string expectedLevel, string expectedMessage) + { + var traceTokens = logLine.Split('|'); + traceTokens.Length.Should().Be(5); + var loggedDate = DateTimeOffset.Parse(traceTokens[0]); + loggedDate.Should().BeBefore(DateTimeOffset.Now); + loggedDate.Should().BeAfter(before); + traceTokens[1].Should().Be(typeof(TraceLoggerFactoryTests).FullName); + traceTokens[2].Should().Be(expectedLevel); + traceTokens[3].Should().Be("0"); + traceTokens[4].Should().Be(expectedMessage + "\n"); + } + + private class MemoryTraceListener : TraceListener + { + private readonly StringBuilder result = new(); + + public void Reset() + { + Trace.Flush(); + this.result.Clear(); + } + + public override void Write(string? message) + { + this.result.Append(message); + } + + public override void WriteLine(string? message) + { + this.result.Append(message).Append('\n'); + } + + public override string ToString() + { + Trace.Flush(); + return this.result.ToString(); + } + } +} diff --git a/czishrink/netczicompressTests/Usings.cs b/czishrink/netczicompressTests/Usings.cs new file mode 100644 index 0000000..8befff0 --- /dev/null +++ b/czishrink/netczicompressTests/Usings.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma warning disable SA1200 // Using directives should be placed correctly + +global using System; +global using System.Collections.Generic; +global using System.Reactive.Concurrency; +global using System.Reactive.Linq; + +global using AutoFixture; +global using AutoFixture.AutoMoq; +global using AutoFixture.Xunit2; + +global using FluentAssertions; + +global using Moq; + +global using netczicompress.Models; + +global using Xunit; \ No newline at end of file diff --git a/czishrink/netczicompressTests/ViewModels/AboutViewModelTests.cs b/czishrink/netczicompressTests/ViewModels/AboutViewModelTests.cs new file mode 100644 index 0000000..48105f0 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/AboutViewModelTests.cs @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using netczicompress.ViewModels; + +/// +/// Tests for . +/// +public class AboutViewModelTests +{ + [Fact] + public void AfterConstruction_HasExpectedProperties() + { + // ARRANGE + IFixture fixture = CreateFixture(); + var launcher = fixture.Create(); + var versionInfo = fixture.Create>(); + var libname = fixture.Create(); + versionInfo.Setup(x => x.ToString()).Returns("FooBar 99.3.7"); + + // ACT + var sut = new AboutViewModel(launcher, versionInfo.Object) { LibraryName = libname }; + + // ASSERT + sut.IsVisible.Should().BeFalse(); + sut.LibraryName.Should().BeSameAs(libname); + sut.ProgramVersionAndCopyRight.Should().Be("FooBar 99.3.7, © 2023 Carl Zeiss Microscopy GmbH and others"); + } + + [Theory] + [AutoData] + public void IsVisible_WhenSet_RaisesEvent(bool initialValue) + { + // ARRANGE + IFixture fixture = CreateFixture(); + + var sut = fixture.Create(); + sut.IsVisible = initialValue; + using var monitor = sut.Monitor(); + + // ACT + sut.IsVisible = !initialValue; + + // ASSERT + monitor.Should().RaisePropertyChangeFor(x => x.IsVisible); + sut.IsVisible.Should().Be(!initialValue); + } + + [Fact] + public void ShowAboutCommand_WhenNotVisible_SetsVisibleToTrue() + { + // ARRANGE + IFixture fixture = CreateFixture(); + + var sut = fixture.Create(); + sut.IsVisible = false; + using var monitor = sut.Monitor(); + + // ACT + sut.ShowAboutCommand.CanExecute(null).Should().BeTrue(); + sut.ShowAboutCommand.Execute(null); + + // ASSERT + monitor.Should().RaisePropertyChangeFor(x => x.IsVisible); + sut.IsVisible.Should().BeTrue(); + } + + [Fact] + public void CloseAboutCommand_WhenVisible_SetsVisibleToFalse() + { + // ARRANGE + IFixture fixture = CreateFixture(); + + var sut = fixture.Create(); + sut.IsVisible = true; + using var monitor = sut.Monitor(); + + // ACT + sut.CloseAboutCommand.CanExecute(null).Should().BeTrue(); + sut.CloseAboutCommand.Execute(null); + + // ASSERT + monitor.Should().RaisePropertyChangeFor(x => x.IsVisible); + sut.IsVisible.Should().BeFalse(); + } + + [Fact] + public void ShowTextFileCommand_WhenCalled_LaunchesFileFromBaseDirectory() + { + // ARRANGE + IFixture fixture = CreateFixture(); + var launcherMock = fixture.Freeze>(); + + var sut = fixture.Create(); + const string file = "foobar.ext"; + + // ACT + sut.ShowTextFileCommand.CanExecute(file).Should().BeTrue(); + sut.ShowTextFileCommand.Execute(file); + + // ASSERT + var expectedFileName = System.IO.Path.Combine(AppContext.BaseDirectory, file); + launcherMock.Verify(x => x.Launch(expectedFileName), Times.Once); + } + + private static IFixture CreateFixture() + { + // ARRANGE + return new Fixture().Customize(new AutoMoqCustomization()); + } +} diff --git a/czishrink/netczicompressTests/ViewModels/AggregateIndicationViewModelTests.cs b/czishrink/netczicompressTests/ViewModels/AggregateIndicationViewModelTests.cs new file mode 100644 index 0000000..69d567f --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/AggregateIndicationViewModelTests.cs @@ -0,0 +1,106 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using netczicompress.ViewModels; + +/// +/// Tests for . +/// +public class AggregateIndicationViewModelTests +{ + [Fact] + public void Properties_AfterCreation_HaveExpectedValues() + { + // ARRANGE + IFixture fixture = CreateFixture(); + + // ACT + var sut = fixture.Create(); + + // ASSERT + sut.IndicationStatus.Should().Be(AggregateIndicationStatus.NotStarted); + } + + [Fact] + public void OnNext_WhenCalled_ChangesPropertyValues() + { + // ARRANGE + IFixture fixture = CreateFixture(); + + var sut = fixture.Create(); + var newValue = fixture.Create(); + using var monitor = sut.Monitor(); + + // ACT + sut.OnNext(newValue); + + // ASSERT + monitor.Should().RaisePropertyChangeFor(x => x.IndicationStatus); + sut.IndicationStatus.Should().Be(AggregateIndicationStatus.Started); + } + + [Theory] + [InlineData(0, AggregateIndicationStatus.Success)] + [InlineData(-1, AggregateIndicationStatus.Success)] + [InlineData(int.MinValue, AggregateIndicationStatus.Success)] + [InlineData(1, AggregateIndicationStatus.Error)] + [InlineData(100, AggregateIndicationStatus.Error)] + [InlineData(int.MaxValue, AggregateIndicationStatus.Error)] + public void OnCompleted_WhenCalled_ChangesIndicationStatus(int filesWithErrors, AggregateIndicationStatus expectation) + { + // ARRANGE + IFixture fixture = CreateFixture(); + + var sut = fixture.Create(); + var newValue = fixture.Create(); + newValue = newValue with { FilesWithErrors = filesWithErrors }; + sut.OnNext(newValue); + using var monitor = sut.Monitor(); + + // ACT + sut.OnCompleted(); + + // ASSERT + monitor.Should().RaisePropertyChangeFor(x => x.IndicationStatus); + sut.IndicationStatus.Should().Be(expectation); + } + + [Fact] + public void OnError_WhenExceptionIsOperationCancelledException_ChangesStatusToCancelled() + { + OnError_WhenExceptionIsOfType_ChangesStatusTo(AggregateIndicationStatus.Cancelled); + } + + [Fact] + public void OnError_WhenExceptionIsNotOperationCancelledException_ChangesStatusToError() + { + OnError_WhenExceptionIsOfType_ChangesStatusTo( + AggregateIndicationStatus.Error); + } + + private static void OnError_WhenExceptionIsOfType_ChangesStatusTo(AggregateIndicationStatus expectation) + where T : Exception + { + // ARRANGE + IFixture fixture = CreateFixture(); + + var sut = fixture.Create(); + using var monitor = sut.Monitor(); + + // ACT + sut.OnError(Mock.Of()); + + // ASSERT + monitor.Should().RaisePropertyChangeFor(x => x.IndicationStatus); + sut.IndicationStatus.Should().Be(expectation); + } + + private static IFixture CreateFixture() + { + // ARRANGE + return new Fixture().Customize(new AutoMoqCustomization()); + } +} diff --git a/czishrink/netczicompressTests/ViewModels/AggregateStatisticsViewModelTests.cs b/czishrink/netczicompressTests/ViewModels/AggregateStatisticsViewModelTests.cs new file mode 100644 index 0000000..07a659e --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/AggregateStatisticsViewModelTests.cs @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using System.ComponentModel; + +using netczicompress.ViewModels; + +/// +/// Tests for . +/// +public class AggregateStatisticsViewModelTests +{ + [Fact] + public void Properties_AfterCreation_HaveExpectedValues() + { + // ARRANGE + IFixture fixture = CreateFixture(); + + // ACT + var sut = fixture.Create(); + + // ASSERT + sut.Should().BeEquivalentTo(AggregateStatistics.Empty); + } + + [Fact] + public void OnNext_WhenCalled_ChangesPropertyValues() + { + // ARRANGE + IFixture fixture = CreateFixture(); + + var sut = fixture.Create(); + var newValue = fixture.Create(); + var propertyListener = new Mock(); + sut.PropertyChanged += propertyListener.Object; + + // ACT + sut.OnNext(newValue); + + // ASSERT + propertyListener.Verify( + x => x.Invoke( + sut, + It.Is(e => e.PropertyName == string.Empty)), + Times.Once); + propertyListener.VerifyNoOtherCalls(); + + sut.Should().BeEquivalentTo(newValue); + } + + [Fact] + public void OnCompleted_WhenCalled_DoesNotDoAnything() + { + WhenCalled_DoesNotDoAnything((sut, _) => sut.OnCompleted()); + } + + [Fact] + public void OnError_WhenCalled_DoesNotDoAnything() + { + WhenCalled_DoesNotDoAnything((sut, fixture) => sut.OnError(fixture.Create())); + } + + [Fact] + public void AggregateIndicationViewModel_WhenGot_IsInjectedInstance() + { + // ARRANGE + IFixture fixture = CreateFixture(); + var injected = fixture.Freeze(); + + // ACT + var sut = fixture.Create(); + + // ASSERT + sut.AggregateIndicationViewModel.Should().BeSameAs(injected); + } + + private static void WhenCalled_DoesNotDoAnything(Action, IFixture> act) + { + // ARRANGE + IFixture fixture = CreateFixture(); + var oldValue = fixture.Create(); + var sut = fixture.Create(); + sut.OnNext(oldValue); + + // ACT + using var monitor = sut.Monitor(); + act(sut, fixture); + + // ASSERT + monitor.OccurredEvents.Should().BeEmpty(); + sut.Should().BeEquivalentTo(oldValue); + } + + private static IFixture CreateFixture() + { + // ARRANGE + return new Fixture().Customize(new AutoMoqCustomization()); + } +} diff --git a/czishrink/netczicompressTests/ViewModels/CompressionTaskViewModelTests.cs b/czishrink/netczicompressTests/ViewModels/CompressionTaskViewModelTests.cs new file mode 100644 index 0000000..772f406 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/CompressionTaskViewModelTests.cs @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using System.Reactive.Subjects; + +using netczicompress.ViewModels; + +/// +/// Tests for . +/// +/// +/// Note that +/// is tested via . +/// +public class CompressionTaskViewModelTests +{ + [Fact] + public void PublicProperties_WhenGot_HaveExpectedValues() + { + // ARRANGE + var fileName = CreateFixture().Create(); + var progress = new BehaviorSubject(0); + var sut = new CompressionTaskViewModel( + fileName, + progress); + + List<(int Progress, (string FileName, int ProgressPercent, bool IsIndeterminateProgress, bool IsCompleted, string ChangedProps))> data = new(); + var changedProperties = new List(); + sut.PropertyChanged += (s, e) => changedProperties.Add(e.PropertyName); + + void Collect(int p) + { + changedProperties.Clear(); + progress.OnNext(p); + changedProperties.Sort(); + data.Add( + (p, + (sut.FileName, + sut.ProgressPercent, + sut.IsIndeterminateProgress, + sut.IsCompleted, + string.Join(",", changedProperties)))); + } + + // ACT + Collect(0); + Collect(1); + Collect(2); + Collect(2); + Collect(10); + Collect(50); + Collect(99); + Collect(100); + + // ASSERT + const string ProgressPercent = nameof(sut.ProgressPercent); + const string IsIndeterminateProgress = nameof(sut.IsIndeterminateProgress); + const string IsCompleted = nameof(sut.IsCompleted); + static string Cat(params string[] args) => string.Join(",", args); + data.Should().BeEquivalentTo( + new[] + { + (0, (fileName, 0, true, false, string.Empty)), + (1, (fileName, 1, true, false, ProgressPercent)), + (2, (fileName, 2, false, false, Cat(IsIndeterminateProgress, ProgressPercent))), + (2, (fileName, 2, false, false, string.Empty)), + (10, (fileName, 10, false, false, ProgressPercent)), + (50, (fileName, 50, false, false, ProgressPercent)), + (99, (fileName, 99, false, false, ProgressPercent)), + (100, (fileName, 100, false, true, Cat(IsCompleted, ProgressPercent))), + }); + } + + private static IFixture CreateFixture() + { + return new Fixture().Customize(new AutoMoqCustomization()); + } +} diff --git a/czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToAnimationConverterTests.cs b/czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToAnimationConverterTests.cs new file mode 100644 index 0000000..0b2afbe --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToAnimationConverterTests.cs @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels.Converters; + +using System.Globalization; + +using netczicompress.ViewModels; +using netczicompress.ViewModels.Converters; + +using Projektanker.Icons.Avalonia; + +/// +/// Tests for . +/// +public class AggregateStatusToAnimationConverterTests +{ + [Theory] + [InlineData(AggregateIndicationStatus.Started, IconAnimation.Spin)] + [InlineData(AggregateIndicationStatus.Cancelled, IconAnimation.None)] + [InlineData(AggregateIndicationStatus.NotStarted, IconAnimation.None)] + [InlineData(AggregateIndicationStatus.Error, IconAnimation.None)] + [InlineData(AggregateIndicationStatus.Success, IconAnimation.None)] + public void ConversionWorks(AggregateIndicationStatus status, IconAnimation expected) + { + var converter = new AggregateStatusToAnimationConverter(); + var result = converter.Convert(status, typeof(IconAnimation), null, CultureInfo.CurrentCulture); + result.Should().Be(expected); + } + + [Fact] + public void BackConversion_Throws() + { + var converter = new AggregateStatusToAnimationConverter(); + Action act = () => converter.ConvertBack(null, typeof(void), null, CultureInfo.CurrentCulture); + act.Should().Throw(); + } +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToForegroundConverterTests.cs b/czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToForegroundConverterTests.cs new file mode 100644 index 0000000..45c4dee --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToForegroundConverterTests.cs @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels.Converters; + +using System.Globalization; + +using Avalonia.Media; + +using netczicompress.ViewModels; +using netczicompress.ViewModels.Converters; + +using Projektanker.Icons.Avalonia; + +/// +/// Tests for . +/// +public class AggregateStatusToForegroundConverterTests +{ + public static IEnumerable DataWithStatusInput => + new List + { + new object[] { AggregateIndicationStatus.NotStarted, Colors.Black }, + new object[] { AggregateIndicationStatus.Started, Colors.CadetBlue }, + new object[] { AggregateIndicationStatus.Success, Colors.Green }, + new object[] { AggregateIndicationStatus.Error, Colors.Red }, + new object[] { AggregateIndicationStatus.Cancelled, Colors.LightGray }, + }; + + public static IEnumerable DataWithOtherInput => + new List + { + new object[] { 999, Colors.Red }, + new object[] { true, Colors.Red }, + new object[] { new(), Colors.Red }, + }; + + [Theory] + [MemberData(nameof(DataWithStatusInput))] + public void ForStatusInput_ConversionWorks(AggregateIndicationStatus status, Color expected) + { + var converter = new AggregateStatusToForegroundConverter(); + var result = converter.Convert(status, typeof(IconAnimation), null, CultureInfo.CurrentCulture); + IImmutableSolidColorBrush? brush = result as IImmutableSolidColorBrush; + brush.Should().NotBeNull(); + brush?.Color.Should().Be(expected); + } + + [Theory] + [MemberData(nameof(DataWithOtherInput))] + public void ForArbitraryInput_ConversionWorks(object status, Color expected) + { + var converter = new AggregateStatusToForegroundConverter(); + var result = converter.Convert(status, typeof(IconAnimation), null, CultureInfo.CurrentCulture); + IImmutableSolidColorBrush? brush = result as IImmutableSolidColorBrush; + brush.Should().NotBeNull(); + brush?.Color.Should().Be(expected); + } +} diff --git a/czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToIconConverterTests.cs b/czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToIconConverterTests.cs new file mode 100644 index 0000000..2c0dbe2 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToIconConverterTests.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels.Converters; + +using System.Globalization; +using netczicompress.ViewModels; +using netczicompress.ViewModels.Converters; +using Projektanker.Icons.Avalonia; + +/// +/// Tests for . +/// +public class AggregateStatusToIconConverterTests +{ + [Theory] + [InlineData(AggregateIndicationStatus.Started, "fa-sync")] + [InlineData(AggregateIndicationStatus.Cancelled, "fa-solid fa-circle-info")] + [InlineData(AggregateIndicationStatus.NotStarted, "fa-solid fa-circle-info")] + [InlineData(AggregateIndicationStatus.Error, "fa-solid fa-circle-info")] + [InlineData(AggregateIndicationStatus.Success, "fa-solid fa-circle-info")] + public void ConversionWorks(AggregateIndicationStatus status, string expected) + { + var converter = new AggregateStatusToIconConverter(); + var result = converter.Convert(status, typeof(IconAnimation), null, CultureInfo.CurrentCulture); + result.Should().Be(expected); + } +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToTextConverterTests.cs b/czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToTextConverterTests.cs new file mode 100644 index 0000000..8556c85 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/Converters/AggregateStatusToTextConverterTests.cs @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels.Converters; + +using System.Globalization; +using netczicompress.ViewModels; +using netczicompress.ViewModels.Converters; +using Projektanker.Icons.Avalonia; + +/// +/// Tests for . +/// +public class AggregateStatusToTextConverterTests +{ + [Theory] + [InlineData(AggregateIndicationStatus.NotStarted, "Compression not started")] + [InlineData(AggregateIndicationStatus.Started, "Compression in progress")] + [InlineData(AggregateIndicationStatus.Success, "Compression succeeded")] + [InlineData(AggregateIndicationStatus.Error, "Compression had errors")] + [InlineData(AggregateIndicationStatus.Cancelled, "Compression was cancelled")] + public void ConversionWorks_ForStatusInput(AggregateIndicationStatus status, string expected) + { + var converter = new AggregateStatusToTextConverter(); + var result = converter.Convert(status, typeof(IconAnimation), null, CultureInfo.CurrentCulture); + result.Should().Be(expected); + } + + [Theory] + [InlineData(7)] + [InlineData(true)] + [InlineData("nonsense")] + public void ConversionReturnsEmptyString_ForOtherInput(object input) + { + var converter = new AggregateStatusToTextConverter(); + var result = converter.Convert(input, typeof(IconAnimation), null, CultureInfo.CurrentCulture); + result.Should().Be(string.Empty); + } +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/ViewModels/Converters/BytesToStringConverterTests.cs b/czishrink/netczicompressTests/ViewModels/Converters/BytesToStringConverterTests.cs new file mode 100644 index 0000000..cd55cb9 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/Converters/BytesToStringConverterTests.cs @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels.Converters; + +using System.Globalization; +using netczicompress.ViewModels.Converters; +using Projektanker.Icons.Avalonia; + +/// +/// Tests for . +/// +public class BytesToStringConverterTests +{ + [Theory] + [InlineData(0, "0 bytes")] + [InlineData(1000, "1.00 kB")] + [InlineData(1234, "1.23 kB")] + [InlineData(2345, "2.35 kB")] + [InlineData(1e6, "1.00 MB")] + [InlineData(1e9, "1.00 GB")] + [InlineData(1e12, "1.00 TB")] + [InlineData(1e15, "1.00 PB")] + [InlineData(1e18, "1.00 EB")] + public void ConversionWorksWithLong(long byteCount, string expected) + { + var converter = new BytesToStringConverter(); + var result = converter.Convert(byteCount, typeof(IconAnimation), null, CultureInfo.InvariantCulture); + result.Should().Be(expected); + } + + [Theory] + [InlineData(0, "0 bytes")] + [InlineData(1000, "1.00 kB")] + [InlineData(1234, "1.23 kB")] + [InlineData(2345, "2.35 kB")] + [InlineData(1e6, "1.00 MB")] + [InlineData(1e9, "1.00 GB")] + public void ConversionWorksWithInt(int byteCount, string expected) + { + var converter = new BytesToStringConverter(); + var result = converter.Convert(byteCount, typeof(IconAnimation), null, CultureInfo.InvariantCulture); + result.Should().Be(expected); + } + + [Theory] + [InlineData(1e6, 0)] + [InlineData(true, 0)] + [InlineData(null, 0)] + public void ConversionWithArbitraryInput_Returns0(object byteCount, object expected) + { + var converter = new BytesToStringConverter(); + var result = converter.Convert(byteCount, typeof(IconAnimation), null, CultureInfo.InvariantCulture); + result.Should().Be(expected); + } +} \ No newline at end of file diff --git a/czishrink/netczicompressTests/ViewModels/Converters/FloatToCompressionRatioStringConverterTests.cs b/czishrink/netczicompressTests/ViewModels/Converters/FloatToCompressionRatioStringConverterTests.cs new file mode 100644 index 0000000..f4b4a87 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/Converters/FloatToCompressionRatioStringConverterTests.cs @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels.Converters; + +using System.Globalization; +using netczicompress.ViewModels.Converters; + +/// +/// Tests for . +/// +public class FloatToCompressionRatioStringConverterTests +{ + [Theory] + [InlineData(0, "0.000")] + [InlineData(1.23f, "1.230")] + [InlineData(float.NaN, "")] + public void CheckConversionWorksWithFloats(float value, string expected) + { + var converter = new FloatToCompressionRatioStringConverter(); + var result = converter.Convert(value, typeof(float), null, CultureInfo.InvariantCulture); + result.Should().Be(expected); + } + + [Theory] + [InlineData(0, "0.000")] + [InlineData(1.23, "1.230")] + [InlineData(double.NaN, "")] + public void CheckConversionWorksWithDoubles(double value, string expected) + { + var converter = new FloatToCompressionRatioStringConverter(); + var result = converter.Convert(value, typeof(double), null, CultureInfo.InvariantCulture); + result.Should().Be(expected); + } + + [Theory] + [InlineData("foo")] + [InlineData(null)] + [InlineData(typeof(string))] + public void Convert_WhenCalledOnSomethingThatIsNeitherFloatNorDouble_ReturnsEmptyString(object value) + { + var converter = new FloatToCompressionRatioStringConverter(); + var result = converter.Convert(value, typeof(double), null, CultureInfo.InvariantCulture); + result.Should().Be(string.Empty); + } +} diff --git a/czishrink/netczicompressTests/ViewModels/CurrentTasksViewModelTests.cs b/czishrink/netczicompressTests/ViewModels/CurrentTasksViewModelTests.cs new file mode 100644 index 0000000..aff64a8 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/CurrentTasksViewModelTests.cs @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using System.Diagnostics; +using System.IO.Abstractions; +using System.Reactive.Subjects; + +using netczicompress.ViewModels; + +using netczicompressTests.Models.Mocks; + +using static netczicompress.Models.CompressorMessage; + +/// +/// Tests for . +/// +public class CurrentTasksViewModelTests +{ + [Fact] + public void OnNext_WhenTaskNotFinished_AddsTaskToList() + { + // ARRANGE + var sut = new CurrentTasksViewModel(ImmediateScheduler.Instance); + + for (int i = 0; i < 10; i++) + { + var message = CreateFixture().Create(); + + // ACT + sut.OnNext(message); + + // ASSERT + sut.CompressionTasks.Count().Should().Be(i + 1); + sut.CompressionTasks[i].FileName.Should().Be(message.InputFile.FullName); + } + } + + [Fact] + public void OnNext_WhenTaskFinished_DoesNotAddTaskToList() + { + // ARRANGE + var sut = new CurrentTasksViewModel(ImmediateScheduler.Instance); + + var message = new FileStarting(Mock.Of(), new BehaviorSubject(100)); + + // ACT + sut.OnNext(message); + + // ASSERT + sut.CompressionTasks.Should().BeEmpty(); + } + + [Fact] + public void OnCompleted_WhenCalled_ClearsTaskList() + { + // ARRANGE + var sut = new CurrentTasksViewModel(ImmediateScheduler.Instance); + + sut.OnNextAll(CreateFixture().CreateMany(10)); + Debug.Assert(sut.CompressionTasks.Count == 10, "Error in ARRANGE"); + + // ACT + sut.OnCompleted(); + + // ASSERT + sut.CompressionTasks.Should().BeEmpty(); + } + + [Fact] + public void OnError_WhenCalled_ClearsTaskList() + { + // ARRANGE + var sut = new CurrentTasksViewModel(ImmediateScheduler.Instance); + + sut.OnNextAll(CreateFixture().CreateMany(10)); + Debug.Assert(sut.CompressionTasks.Count == 10, "Error in ARRANGE"); + + // ACT + sut.OnError(new Exception()); + + // ASSERT + sut.CompressionTasks.Should().BeEmpty(); + } + + [Fact] + public void WhenTaskCompletes_TaskIsRemovedFromListOnGuiScheduler() + { + // ARRANGE + var guiScheduler = new SchedulerMock(); + var sut = new CurrentTasksViewModel(guiScheduler); + + sut.OnNextAll(CreateFixture().CreateMany(5)); + var progress = new BehaviorSubject(0); + var completingTask = new FileStarting( + Mock.Of(f => f.FullName == "Foo_Bar_Baz"), + progress); + sut.OnNext(completingTask); + sut.OnNextAll(CreateFixture().CreateMany(5)); + + var taskViewModel = sut.CompressionTasks[5]; + var initialTasks = sut.CompressionTasks.ToArray(); + Debug.Assert(initialTasks.Length == 11, "Error in ARRANGE"); + Debug.Assert(taskViewModel.FileName == "Foo_Bar_Baz", "Error in ARRANGE"); + + // ACT + progress.OnNext(100); + var beforeSchedulerRun = sut.CompressionTasks.ToArray(); + guiScheduler.RunQueuedActions(); + var afterSchedulerRun = sut.CompressionTasks.ToArray(); + + // ASSERT + beforeSchedulerRun.Should().HaveCount(11); + beforeSchedulerRun.SequenceEqual(initialTasks).Should().BeTrue(); + + afterSchedulerRun.Should().HaveCount(10); + afterSchedulerRun.SequenceEqual(initialTasks.Where(t => t != taskViewModel)).Should().BeTrue(); + } + + private static IFixture CreateFixture() + { + return new Fixture().Customize(new AutoMoqCustomization()); + } +} diff --git a/czishrink/netczicompressTests/ViewModels/ErrorItemTests.cs b/czishrink/netczicompressTests/ViewModels/ErrorItemTests.cs new file mode 100644 index 0000000..65b026b --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/ErrorItemTests.cs @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using System.ComponentModel; +using System.Reflection; +using System.Runtime.CompilerServices; + +using netczicompress.ViewModels; + +/// +/// Tests for . +/// +public class ErrorItemTests +{ + /// + /// Tests that is indeed immutable. + /// + [Fact] + public void WritableProperties_AreInitOnly() + { + foreach (var property in WritableProperties()) + { + var setMethod = property.SetMethod; + + // Get the modifiers applied to the return parameter. + var setMethodReturnParameterModifiers = setMethod!.ReturnParameter.GetRequiredCustomModifiers(); + + // Init-only properties are marked with the IsExternalInit type. + bool isInitOnly = setMethodReturnParameterModifiers.Any(x => + x.FullName == + "System.Runtime.CompilerServices.IsExternalInit"); + + isInitOnly.Should().BeTrue(); + } + } + + /// + /// Tests that is indeed immutable. + /// + [Fact] + public void WritableProperties_AreRequired() + { + foreach (var property in WritableProperties()) + { + property.GetCustomAttributes(false).OfType().Should().NotBeEmpty(); + } + } + + [Fact] + public void ImplementsINotifyPropertyChanged() + { + INotifyPropertyChanged sut = new ErrorItem { File = "foo", ErrorMessage = "bar" }; + var handler = new Mock(MockBehavior.Strict).Object; + + sut.PropertyChanged += handler; + sut.ToString().Should().Be("foo : bar"); + sut.PropertyChanged -= handler; + } + + private static IEnumerable WritableProperties() + { + return typeof(ErrorItem).GetProperties().Where(p => p.CanWrite); + } +} diff --git a/czishrink/netczicompressTests/ViewModels/ErrorListViewModelTests.cs b/czishrink/netczicompressTests/ViewModels/ErrorListViewModelTests.cs new file mode 100644 index 0000000..c445b88 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/ErrorListViewModelTests.cs @@ -0,0 +1,445 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using System.Collections; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO.Abstractions; +using System.Windows.Input; + +using netczicompress.Models; +using netczicompress.ViewModels; +using netczicompressTests.Customizations; +using netczicompressTests.Models.Mocks; +using ReactiveUI; +using static netczicompress.Models.CompressorMessage; + +/// +/// Tests for . +/// +public class ErrorListViewModelTests +{ + [Fact] + public void SelectedErrorItem_WhenSet_RaisesPropertyChanged() + { + var sut = CreateSut(); + var fixture = new Fixture(); + + using (var m = sut.Monitor()) + { + sut.SelectedErrorItem = fixture.Create(); + m.Should().RaisePropertyChangeFor(x => x.SelectedErrorItem); + } + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + [InlineData(7)] + [InlineData(20)] + [InlineData(100)] + public void ObserveRun_WhenItemsAddedBelowBulkAddThreshold_PerformsSingleAdds(int bufferCap) + { + // ARRANGE + const int MaxItemsToShow = 10; + ErrorListViewModel sut = CreateSut(bufferCap, MaxItemsToShow); + Debug.Assert( + sut.BulkAddThreshold > Math.Min(bufferCap, MaxItemsToShow), + "ItemsAddedBelowResetThreshold"); + + var errorListChangesRecorder = ObserveErrorListChanges(sut).StartRecording(); + IFixture fixture = CreateFixture(); + var input = GenerateMessages(fixture).Take(54).ToArray(); + var run = input.ToObservableAction(); + + // ACT + sut.ObserveRun( + fixture.Create(), + run.Output); + run.Start(); + + // ASSERT + // Number of changes: 1 Reset + 1 Add per Item + 1 Add of Truncation Notice + // Changes + var expectedChanges = GetExpectedErrorListChangesForSingleAdds(input, MaxItemsToShow); + errorListChangesRecorder.Data.Count.Should().Be(expectedChanges.Length); + errorListChangesRecorder.Data.Should().BeEquivalentTo(expectedChanges); + + // Final state + sut.Errors.Should().BeEquivalentTo(expectedChanges[^1].SnapShot); + } + + [Fact] + public void ObserveRun_WhenItemsAddedExceedBulkAddThreshold_PerformsBulkAdd() + { + // ARRANGE + const int MaxItemsToShow = 30; + const int BufferCapacity = 30; + ErrorListViewModel sut = CreateSut(BufferCapacity, MaxItemsToShow); + Debug.Assert( + sut.BulkAddThreshold < Math.Min(MaxItemsToShow, BufferCapacity), + "ItemsAddedExceedResetThreshold"); + + var errorListChangesRecorder = ObserveErrorListChanges(sut).StartRecording(); + var fixture = CreateFixture(); + var input = GenerateMessages(fixture).Take(61).ToArray(); + var run = input.ToObservableAction(); + + // ACT + sut.ObserveRun( + fixture.Create(), + run.Output); + run.Start(); + + // ASSERT + // Changes + var expectedRealErrorItems = GetExpectedErrorItems(input).Take(MaxItemsToShow).ToArray(); + var expectedErrorItems = expectedRealErrorItems.Append(GetExpectedTruncationItem()).ToArray(); + var expectation = new[] + { + (Array.Empty(), "Reset"), + (expectedRealErrorItems, "Reset"), + GetExpectedAddLastChange(expectedErrorItems), + }; + + errorListChangesRecorder.Data.Count.Should().Be(expectation.Length); + errorListChangesRecorder.Data.Should().BeEquivalentTo(expectation); + + // Final state + sut.Errors.Should().BeEquivalentTo(expectedErrorItems); + sut.SelectedErrorItem.Should().BeEquivalentTo(expectedRealErrorItems[^1]); + } + + [Theory] + [InlineData(0.2)] + [InlineData(0.5)] + [InlineData(1)] + [InlineData(5)] + public void ObserveRun_WhenRunIsErrored_HasCorrectFinalState(double bufferTimeMillis) + { + // ARRANGE + ErrorListViewModel sut = CreateSut( + bufferCap: 30, + maxItemsToShow: int.MaxValue, + bufferTimeout: TimeSpan.FromMilliseconds(bufferTimeMillis), + scheduler: ImmediateScheduler.Instance); + + var fixture = CreateFixture(); + var input = GenerateMessages(fixture).Take(125).ToArray(); + var observableMock = new Mock>(); + IObserver? subscriber = null; + observableMock + .Setup(x => x.Subscribe(It.IsAny>())) + .Callback>(obs => subscriber = obs); + + // ACT + sut.ObserveRun( + fixture.Create(), + observableMock.Object); + subscriber?.OnNextAll(input); + subscriber?.OnError(new Exception()); + + // ASSERT + var expectedErrorItems = GetExpectedErrorItems(input).ToArray(); + sut.Errors.Should().BeEquivalentTo(expectedErrorItems); + sut.SelectedErrorItem.Should().BeEquivalentTo(expectedErrorItems[^1]); + } + + [Theory] + [InlineData(0.2)] + [InlineData(0.5)] + [InlineData(1)] + [InlineData(5)] + public void ObserveRun_WhenSchedulerIsSlow_HasCorrectFinalState(double bufferTimeMillis) + { + // ARRANGE + var scheduler = new SchedulerMock(); + ErrorListViewModel sut = CreateSut( + bufferCap: 30, + maxItemsToShow: int.MaxValue, + bufferTimeout: TimeSpan.FromMilliseconds(bufferTimeMillis), + scheduler: scheduler); + + var fixture = CreateFixture(); + var input = GenerateMessages(fixture).Take(125).ToArray(); + var run = input.ToObservableAction(); + + // ACT + sut.ObserveRun( + fixture.Create(), + run.Output); + run.Start(); + + scheduler.RunQueuedActions(); + + // ASSERT + var expectedErrorItems = GetExpectedErrorItems(input).ToArray(); + sut.Errors.Should().BeEquivalentTo(expectedErrorItems); + sut.SelectedErrorItem.Should().BeEquivalentTo(expectedErrorItems[^1]); + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(5)] + [InlineData(10)] + [InlineData(20)] + [InlineData(100)] + public void ObserveRun_AsItemsAreDelivered_SelectsLastRealItem(int bufferCap) + { + // ARRANGE + const int MaxItemsToShow = 10; + ErrorListViewModel sut = CreateSut(bufferCap, MaxItemsToShow); + + var recorder = ObserveSelectedItemChanges(sut).StartRecording(); + var fixture = CreateFixture(); + var input = GenerateMessages(fixture, 54).ToArray(); + var run = input.ToObservableAction(); + + // ACT + sut.ObserveRun( + fixture.Create(), + run.Output); + run.Start(); + + // ASSERT + // Number of changes: we expect only one update per buffer + int expectedNumberOfUpdates = (int)Math.Ceiling(1.0 * MaxItemsToShow / bufferCap); + recorder.Data.Count.Should().Be(expectedNumberOfUpdates); + + // State after each change + foreach (var (snapshot, selectedItem) in recorder.Data) + { + selectedItem.Should().BeSameAs(snapshot.LastOrDefault()); + } + + // Final state: Truncation Notice is not selected, last 'real' error remains selected + sut.SelectedErrorItem.Should().BeSameAs(sut.Errors[^2]); + } + + [Fact] + public void ShowSelectedFileCommand_CanExecuteWhenSelectedErrorItemIsNotNull() + { + var sut = CreateSut(); + Command_CanExecuteWhenSelectedErrorItemIsNotNull(sut, sut.ShowSelectedFileCommand); + } + + [Fact] + public void ShowSelectedFileCommand_WhenSelectedErrorItemIsNotNull_RevealsFile() + { + // ARRANGE + var launcherMock = new Mock(); + var sut = CreateSut(launcher: launcherMock.Object); + var fixture = new Fixture(); + var errorItem = fixture.Create(); + sut.SelectedErrorItem = errorItem; + + // ACT + sut.ShowSelectedFileCommand.Execute(null); + + // ASSERT + launcherMock.Verify(x => x.Reveal(errorItem.File), Times.Once); + launcherMock.VerifyNoOtherCalls(); + } + + [Fact] + public void ShowSelectedFileCommand_WhenSelectedErrorItemIsNull_NoError() + { + // ARRANGE + var launcherMock = new Mock(); + var sut = CreateSut(launcher: launcherMock.Object); + sut.SelectedErrorItem = null; + + // ACT + sut.ShowSelectedFileCommand.Execute(null); + + // ASSERT + launcherMock.VerifyNoOtherCalls(); + } + + [Fact] + public void OpenSelectedFileCommand_WhenSelectedErrorItemIsNull_NoError() + { + // ARRANGE + var launcherMock = new Mock(); + var sut = CreateSut(launcher: launcherMock.Object); + sut.SelectedErrorItem = null; + + // ACT + sut.OpenSelectedFileCommand.Execute(null); + + // ASSERT + launcherMock.VerifyNoOtherCalls(); + } + + [Fact] + public void OpenSelectedFileCommand_WhenSelectedErrorItemIsNotNull_OpensFile() + { + // ARRANGE + var launcherMock = new Mock(); + var sut = CreateSut(launcher: launcherMock.Object); + var fixture = new Fixture(); + var errorItem = fixture.Create(); + sut.SelectedErrorItem = errorItem; + + // ACT + sut.OpenSelectedFileCommand.Execute(null); + + // ASSERT + launcherMock.Verify(x => x.Launch(errorItem.File), Times.Once); + launcherMock.VerifyNoOtherCalls(); + } + + [Fact] + public void OpenSelectedFileCommand_CanExecuteWhenSelectedErrorItemIsNotNull() + { + var sut = CreateSut(); + Command_CanExecuteWhenSelectedErrorItemIsNotNull(sut, sut.OpenSelectedFileCommand); + } + + private static void Command_CanExecuteWhenSelectedErrorItemIsNotNull(ErrorListViewModel sut, ICommand command) + { + sut.SelectedErrorItem.Should().BeNull(); + command.CanExecute(null).Should().BeFalse(); + + using (var m = command.Monitor()) + { + var fixture = new Fixture(); + sut.SelectedErrorItem = fixture.Create(); + command.CanExecute(null).Should().BeTrue(); + m.Should().Raise(nameof(ICommand.CanExecuteChanged)); + } + + using (var m = command.Monitor()) + { + sut.SelectedErrorItem = null; + command.CanExecute(null).Should().BeFalse(); + m.Should().Raise(nameof(ICommand.CanExecuteChanged)); + } + } + + private static IFixture CreateFixture() + { + var f = new Fixture().Customize(new AutoMoqCustomization()); + f.Customizations.Add(new ThreadCountSpecimenBuilder()); + return f; + } + + private static ErrorListViewModel CreateSut( + int bufferCap = 50, + int maxItemsToShow = int.MaxValue, + TimeSpan? bufferTimeout = null, + IScheduler? scheduler = null, + IFileLauncher? launcher = null) + { + return new ErrorListViewModel( + maxNumberOfErrorsToShow: maxItemsToShow, + bufferInterval: bufferTimeout ?? TimeSpan.FromMinutes(5), + bufferCapacity: bufferCap, + scheduler: scheduler ?? ImmediateScheduler.Instance, + launcher: launcher ?? Mock.Of()); + } + + private static IEnumerable GenerateMessages(IFixture fixture, int count = int.MaxValue) + { + return from i in Enumerable.Range(0, count) + let error = i % 2 == 0 ? $"E{i}" : null + select new FileFinished( + Mock.Of(f => f.FullName == $"F{i}"), + fixture.Create(), + fixture.Create(), + fixture.Create(), + error); + } + + private static IObservable<(ErrorItem[] Snapshot, string Change)> ObserveErrorListChanges( + ErrorListViewModel sut) + { + INotifyCollectionChanged errorsColl = sut.Errors; + var changes = Observable.FromEventPattern( + e => errorsColl.CollectionChanged += e, + e => errorsColl.CollectionChanged -= e); + var resultSnapshotObservable = from change in changes + select (sut.Errors.ToArray(), ToString(change.EventArgs)); + return resultSnapshotObservable; + } + + private static (IReadOnlyList SnapShot, string Change)[] GetExpectedErrorListChangesForSingleAdds( + IEnumerable inputData, + int maxItemsToShow) + { + int expectedChangeCount = maxItemsToShow + 2; + var changeIndices = Enumerable.Range(0, expectedChangeCount); + + // Changes + var expectedChanges = from changeIndex in changeIndices + select GetExpectedErrorListChangeForSingleAdds(changeIndex, inputData, maxItemsToShow); + return expectedChanges.ToArray(); + } + + private static (IReadOnlyList SnapShot, string Change) GetExpectedErrorListChangeForSingleAdds( + int changeIndex, + IEnumerable inputData, + int maxItemsToShow) + { + if (changeIndex == 0) + { + // First snapshot is the clearing of the list. + return (Array.Empty(), "Reset"); + } + + IEnumerable allExpected = GetExpectedErrorItems(inputData); + var truncatedExpectation = allExpected.Take(maxItemsToShow).Append(GetExpectedTruncationItem()); + var list = truncatedExpectation.Take(changeIndex).ToList(); + return GetExpectedAddLastChange(list); + } + + private static ErrorItem GetExpectedTruncationItem() + { + return new ErrorItem { File = "…", ErrorMessage = "See log file for further errors." }; + } + + private static IEnumerable GetExpectedErrorItems(IEnumerable inputData) + { + return from m in inputData + let e = m.ErrorMessage + where e != null + select new ErrorItem { File = m.InputFile.FullName, ErrorMessage = e }; + } + + private static IObservable<(ErrorItem[] Snapshot, ErrorItem? SelectedItem)> ObserveSelectedItemChanges(ErrorListViewModel sut) + { + var changes = sut.ObservableForProperty(vm => vm.SelectedErrorItem); + return from change in changes select (sut.Errors.ToArray(), sut.SelectedErrorItem); + } + + private static string ToString(NotifyCollectionChangedEventArgs e) + { + return e.Action switch + { + NotifyCollectionChangedAction.Add => FormatAdd(e.NewStartingIndex, ToString(e.NewItems)), + _ => e.Action.ToString(), + }; + + static string ToString(IEnumerable? newItems) + { + var errorItems = newItems!.Cast(); + return string.Join("\t", errorItems); + } + } + + private static string FormatAdd(int addIndex, object? newItems) + { + return $"Add {newItems} at {addIndex}"; + } + + private static (IReadOnlyList SnapShot, string Change) GetExpectedAddLastChange(IReadOnlyList list) + { + string addLastChange = FormatAdd(list.Count - 1, list[^1]); + return (list, addLastChange); + } +} diff --git a/czishrink/netczicompressTests/ViewModels/LogFileViewModelTests.cs b/czishrink/netczicompressTests/ViewModels/LogFileViewModelTests.cs new file mode 100644 index 0000000..4c38da5 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/LogFileViewModelTests.cs @@ -0,0 +1,367 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using System.Collections.Immutable; +using System.IO.Abstractions; +using System.IO.Abstractions.TestingHelpers; +using System.Reactive.Concurrency; +using System.Reactive.Subjects; + +using netczicompress.ViewModels; +using netczicompressTests.Customizations; +using netczicompressTests.Models.Mocks; + +/// +/// Tests for . +/// +public class LogFileViewModelTests +{ + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ObserveRun_WhenCalled_LogsMessages(bool exception) + { + // ARRANGE + var fixture = CreateFixture(); + var p = fixture.Create(); + + fixture.Inject(ImmediateScheduler.Instance); + + var strategyMock = fixture.Freeze>(); + var recorder = new Recorder(); + using var writer = new StreamWriter(new MemoryStream()); + strategyMock.Setup(x => x.CreateLogFile(p)).Returns(Mock.Of(x => x.CreateText() == writer)); + strategyMock.Setup(x => x.CreateLogger(writer)).Returns(recorder); + strategyMock.Setup(x => x.LoggerScheduler).Returns(ImmediateScheduler.Instance); + + var sut = fixture.Create(); + + var subject = new Subject(); + + // ACT + sut.ObserveRun(p, subject); + + var messages = fixture.CreateMany(); + subject.OnNextAll(messages); + + var messagesLoggedBeforeTermination = recorder.Data.ToImmutableArray(); + Complete(subject, exception); + + // ASSERT + recorder.Completed.Should().BeTrue(); + recorder.Data.Should().BeEquivalentTo(messages); + messagesLoggedBeforeTermination.Should().BeEquivalentTo(messages); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ObserveRun_UsesLoggerSchedulerToLogMessages(bool exception) + { + // ARRANGE + var fixture = CreateFixture(); + var p = fixture.Create(); + + fixture.Inject(ImmediateScheduler.Instance); + + var strategyMock = fixture.Freeze>(); + var recorder = new Recorder(); + var loggerScheduler = new SchedulerMock(); + using var writer = new StreamWriter(new MemoryStream()); + strategyMock.Setup(x => x.CreateLogFile(p)).Returns(Mock.Of(x => x.CreateText() == writer)); + strategyMock.Setup(x => x.CreateLogger(writer)).Returns(recorder); + strategyMock.Setup(x => x.LoggerScheduler).Returns(loggerScheduler); + + var sut = fixture.Create(); + + List<(string, ImmutableArray LoggedMessages, bool Completed)> data = new(); + void StoreDataPoint(string name) + { + recorder.Error.Should().BeNull(); + data.Add((name, recorder.Data.ToImmutableArray(), recorder.Completed)); + } + + // ACT + var subject = new Subject(); + sut.ObserveRun(p, subject.AsObservable()); + var messages = fixture.CreateMany(); + subject.OnNextAll(messages); + + Complete(subject, exception); + StoreDataPoint("Completed"); + + loggerScheduler.RunQueuedActions(); + StoreDataPoint("Completed and Scheduler Queue Run"); + + // ASSERT + var allMessages = messages.ToImmutableArray(); + var noMessages = ImmutableArray.Empty; + + data.Should().BeEquivalentTo( + new[] + { + ("Completed", noMessages, false), + ("Completed and Scheduler Queue Run", allMessages, true), + }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ObserveRun_WhenCalled_CorrectlyPublishesLogFile(bool exception) + { + // ARRANGE + var fixture = CreateFixture(); + var p = fixture.Create(); + + fixture.Inject(ImmediateScheduler.Instance); + + var fs = new MockFileSystem(new MockFileSystemOptions { CreateDefaultTempDir = true }); + IFileInfo? logFile = null; + var strategyMock = fixture.Freeze>(); + strategyMock.Setup(x => x.CreateLogFile(p)).Returns(_ => + { + var result = fs.FileInfo.New(fs.Path.GetTempFileName()); + logFile = result; + return result; + }); + strategyMock.Setup(x => x.LoggerScheduler).Returns(ImmediateScheduler.Instance); + + var sut = fixture.Create(); + + Dictionary data = new(); + void LogDataPoint(string name) + { + data[name] = (sut.LogFileOfLastRun, sut.OpenLogFileCommand.CanExecute(null)); + } + + LogDataPoint("Never Run"); + for (int i = 0; i < 2; i++) + { + // ACT + var subject = new Subject(); + sut.ObserveRun(p, subject); + LogDataPoint("Before Run"); + + var messages = fixture.CreateMany(); + subject.OnNextAll(messages); + + LogDataPoint("During Run"); + + using (var monitor = sut.Monitor()) + { + Complete(subject, exception); + monitor.Should().RaisePropertyChangeFor(x => x.LogFileOfLastRun); + } + + LogDataPoint("After Run"); + + // ASSERT + data["Never Run"].Should().Be((null, false)); + data["Before Run"].Should().Be((null, false)); + data["During Run"].Should().Be((null, false)); + data["After Run"].Should().Be((logFile, true)); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ObserveRun_WhenCalled_UsesGuiSchedulerToPublishLogFile(bool exception) + { + // ARRANGE + var fixture = CreateFixture(); + var p = fixture.Create(); + var guiScheduler = new SchedulerMock(); + fixture.Inject(guiScheduler); + + var fs = new MockFileSystem(new MockFileSystemOptions { CreateDefaultTempDir = true }); + var strategyMock = fixture.Freeze>(); + strategyMock.Setup(x => x.CreateLogFile(p)).Returns(_ => + { + return fs.FileInfo.New(fs.Path.GetTempFileName()); + }); + strategyMock.Setup(x => x.LoggerScheduler).Returns(ImmediateScheduler.Instance); + + var sut = fixture.Create(); + + Dictionary data = new(); + void LogDataPoint(string name) + { + data[name] = (sut.LogFileOfLastRun, sut.OpenLogFileCommand.CanExecute(null)); + } + + for (int i = 0; i < 2; i++) + { + // ACT + var subject = new Subject(); + sut.ObserveRun(p, subject); + + var messages = fixture.CreateMany(); + subject.OnNextAll(messages); + + using (var monitor = sut.Monitor()) + { + Complete(subject, exception); + monitor.Should().NotRaisePropertyChangeFor(x => x.LogFileOfLastRun); + LogDataPoint("Before scheduler queue has run"); + + guiScheduler.RunQueuedActions(); + monitor.Should().RaisePropertyChangeFor(x => x.LogFileOfLastRun); + LogDataPoint("After scheduler queue has run"); + } + + // ASSERT + data["Before scheduler queue has run"].Should().Be((null, false)); + var after = data["After scheduler queue has run"]; + after.LogFileOfLastRun.Should().NotBeNull(); + after.CanShowLogFile.Should().BeTrue(); + } + } + + [Fact] + public void ShowLogFileCommand_WhenCannotExecute_ExecuteDoesNothing() + { + // ARRANGE + var fixture = CreateFixture(); + fixture.Inject(ImmediateScheduler.Instance); + var loggingStrategyMock = fixture.Freeze>(); + var sut = fixture.Create(); + + // ACT + sut.OpenLogFileCommand.Execute(null); + + // ASSERT + loggingStrategyMock.Verify(x => x.OpenLogFile(It.IsAny()), Times.Never); + } + + [Fact] + public void ShowLogFile_WhenLogFileExists_OpensLogFile() + { + // ARRANGE + var fixture = CreateFixture(); + fixture.Inject(ImmediateScheduler.Instance); + var loggingStrategyMock = fixture.Freeze>(); + var logFileMock = new Mock(); + var sut = fixture.Create(); + + var p = fixture.Create(); + + loggingStrategyMock.Setup(x => x.CreateLogFile(p)).Returns(logFileMock.Object); + var messages = fixture.CreateMany(); + var run = messages.ToObservableAction(); + + sut.ObserveRun(p, run.Output); + run.Start(); + logFileMock + .Setup( + x => x.Refresh()) + .Callback( + () => logFileMock + .SetupGet( + x => x.Exists) + .Returns( + true)); + sut.LogFileOfLastRun.Should().BeSameAs(logFileMock.Object); + sut.LogFileOfLastRun!.Exists.Should().BeFalse(); + sut.OpenLogFileCommand.CanExecute(null).Should().BeTrue(); + + // ACT + sut.OpenLogFileCommand.Execute(null); + + // ASSERT + loggingStrategyMock.Verify(x => x.OpenLogFile(logFileMock.Object), Times.Once); + sut.LogFileOfLastRun.Should().BeSameAs(logFileMock.Object); + sut.LogFileOfLastRun!.Exists.Should().BeTrue(); + sut.OpenLogFileCommand.CanExecute(null).Should().BeTrue(); + } + + [Fact] + public void ShowLogFile_WhenLogFileDoesNotExist_UpdatesCanExecute() + { + // ARRANGE + var fixture = CreateFixture(); + fixture.Inject(ImmediateScheduler.Instance); + var loggingStrategyMock = fixture.Freeze>(); + var logFileMock = new Mock(); + var sut = fixture.Create(); + + var p = fixture.Create(); + + loggingStrategyMock.Setup(x => x.CreateLogFile(p)).Returns(logFileMock.Object); + var messages = fixture.CreateMany(); + var run = messages.ToObservableAction(); + + sut.ObserveRun(p, run.Output); + run.Start(); + sut.LogFileOfLastRun.Should().BeSameAs(logFileMock.Object); + sut.LogFileOfLastRun!.Exists.Should().BeFalse(); + sut.OpenLogFileCommand.CanExecute(null).Should().BeTrue(); + + // ACT + sut.OpenLogFileCommand.Execute(null); + + // ASSERT + loggingStrategyMock.Verify(x => x.OpenLogFile(logFileMock.Object), Times.Never); + sut.LogFileOfLastRun.Should().BeNull(); + sut.OpenLogFileCommand.CanExecute(null).Should().BeFalse(); + } + + [Fact] + public void ShowLogFile_WhenOpeningTheLogFileFails_UpdatesCanExecute() + { + // ARRANGE + var fixture = CreateFixture(); + fixture.Inject(ImmediateScheduler.Instance); + var loggingStrategyMock = fixture.Freeze>(); + var logFile = Mock.Of(x => x.Exists == true); + var sut = fixture.Create(); + + var p = fixture.Create(); + + loggingStrategyMock.Setup(x => x.CreateLogFile(p)).Returns(logFile); + var messages = fixture.CreateMany(); + var run = messages.ToObservableAction(); + + sut.ObserveRun(p, run.Output); + run.Start(); + sut.LogFileOfLastRun.Should().BeSameAs(logFile); + sut.LogFileOfLastRun!.Exists.Should().BeTrue(); + sut.OpenLogFileCommand.CanExecute(null).Should().BeTrue(); + loggingStrategyMock + .Setup( + x => x.OpenLogFile(logFile)) + .Throws(); + + // ACT + sut.OpenLogFileCommand.Execute(null); + + // ASSERT + loggingStrategyMock.Verify(x => x.OpenLogFile(logFile), Times.Once); + sut.LogFileOfLastRun.Should().BeNull(); + sut.OpenLogFileCommand.CanExecute(null).Should().BeFalse(); + } + + private static IFixture CreateFixture() + { + var result = new Fixture().Customize(new AutoMoqCustomization()); + result.Customizations.Add(new ThreadCountSpecimenBuilder()); + return result; + } + + private static void Complete(IObserver subject, bool exception) + { + if (exception) + { + var ex = new Exception(); + subject.OnError(ex); + } + else + { + subject.OnCompleted(); + } + } +} diff --git a/czishrink/netczicompressTests/ViewModels/MainViewModelTests.ChildViewModels.cs b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.ChildViewModels.cs new file mode 100644 index 0000000..7a08f41 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.ChildViewModels.cs @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using AutoFixture; + +using netczicompress.ViewModels; + +/// +/// Tests for . +/// +/// Tests for child view models. +public partial class MainViewModelTests +{ + [Fact] + public void ChildViewModels_AreThoseInjected() + { + // ARRANGE + IFixture fixture = CreateFixture(); + + var errorList = fixture.Freeze(); + var currentTasks = fixture.Freeze(); + var aggregateStatistics = fixture.Freeze(); + var about = fixture.Freeze(); + + // ACT + var sut = fixture.Create(); + + // ASSERT + sut.About.Should().BeSameAs(about); + sut.AggregateStatisticsViewModel.Should().BeSameAs(aggregateStatistics); + sut.CurrentTasks.Should().BeSameAs(currentTasks); + sut.ErrorList.Should().BeSameAs(errorList); + } +} diff --git a/czishrink/netczicompressTests/ViewModels/MainViewModelTests.Modes.cs b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.Modes.cs new file mode 100644 index 0000000..cff3d8c --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.Modes.cs @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using System.IO.Abstractions; +using System.Reactive; + +using AutoFixture; + +using netczicompress.Models; +using netczicompress.ViewModels; + +using netczicompressTests.Models.Mocks; + +/// +/// Tests for . +/// +/// Tests for members related to mode selection. +public partial class MainViewModelTests +{ + [Fact] + public void Modes_WhenGot_HaveExpectedValues() + { + // ARRANGE + IFixture fixture = CreateFixture(); + + var sut = fixture.Create(); + + // ACT + var actual = sut.Modes.Cast().ToArray(); + + // ASSERT + actual.Should().BeEquivalentTo(new OperationMode[] + { + new(CompressionMode.CompressUncompressed, "Compress uncompressed data", "Compress only uncompressed subblocks and copy others."), + new(CompressionMode.CompressUncompressedAndZstd, "Compress uncompressed and Zstd-compressed data", "Compress subblocks that were originally compressed with zstd or uncompressed."), + new(CompressionMode.CompressAll, "Compress all data", "Compress all subblocks regardless of current compression method (if possible)."), + new(CompressionMode.Decompress, "Decompress all data", "Decompress all possible subblocks."), + new(CompressionMode.NoOp, "Dry Run (enumerate and log files)", "Do not compress/decompress rather just enumerate and log files that would be affected."), + }); + } + + [Fact] + public void SelectedMode_WhenGot_HasExpectedValue() + { + // ARRANGE + IFixture fixture = CreateFixture(); + + var sut = fixture.Create(); + + // ACT + var actual = sut.SelectedMode; + + // ASSERT + actual.Should().BeSameAs(sut.Modes.Cast().First()); + actual.Value.Should().Be(CompressionMode.CompressUncompressed); + } + + [Theory] + [AutoData] + public async Task Start_WhenCalled_UsesSelectedMode(ushort selectedModeIndex) + { + IFixture fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + var compressorMock = fixture.Freeze>().WithAutoData(fixture); + + var sut = fixture.Create(); + SetFolders(sut, fs); + + var modes = sut.Modes.Cast().ToArray(); + var selectedIndex = selectedModeIndex % modes.Length; + var selectedMode = modes[selectedIndex]; + + // ACT + sut.SelectedMode = selectedMode; + await sut.StartCommand.Execute(Unit.Default); + + // ASSERT + compressorMock.Verify( + x => + x.PrepareNewRun( + It.Is(x => x.Mode == selectedMode.Value), + It.Is(c => !c.IsCancellationRequested)), + Times.Once); + } + + [Theory] + [AutoData] + public void OperationMode_ToString_ReturnsDisplayText(CompressionMode mode, string displayText, string tooltipText) + { + var sut = new OperationMode(mode, displayText, tooltipText); + + sut.ToString().Should().BeSameAs(displayText); + } + + private static IFixture CreateFixture(bool omitAutoProperties = true) + { + // ARRANGE + return new Fixture { OmitAutoProperties = omitAutoProperties }.Customize(new AutoMoqCustomization()); + } +} diff --git a/czishrink/netczicompressTests/ViewModels/MainViewModelTests.OverallStatus.cs b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.OverallStatus.cs new file mode 100644 index 0000000..37d7763 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.OverallStatus.cs @@ -0,0 +1,313 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using System.IO.Abstractions; +using System.IO.Abstractions.TestingHelpers; +using System.Reactive; +using System.Reactive.Threading.Tasks; +using System.Windows.Input; + +using AutoFixture; + +using Microsoft.Extensions.Logging; + +using netczicompress.Models; +using netczicompress.ViewModels; + +using netczicompressTests.Models.Mocks; + +/// +/// Tests for . +/// +/// Tests for members related to overall status. +public partial class MainViewModelTests +{ + [Fact] + public void OverallStatus_WhenNotRun_IsEmpty() + { + // ARRANGE + var fixture = CreateFixture(); + + var sut = fixture.Create(); + + // ACT + var actual = sut.OverallStatus; + + // ASSERT + actual.Should().BeEmpty(); + } + + [Fact] + public async Task OverallStatus_WhenRunSuccessful_IsExpected() + { + // ARRANGE + var fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + _ = fixture.Freeze>().WithAutoData(fixture); + var loggerMock = new LoggerMock().Inject(fixture); + + var sut = fixture.Create(); + SetFolders(sut, fs); + + // ACT + await sut.StartCommand.Execute(Unit.Default); + + // ASSERT + sut.OverallStatus.Should().Be("Finished"); + loggerMock.VerifyEntryCount(2); + loggerMock.VerifyEntry(^1, LogLevel.Information, "Run finished normally."); + } + + [Fact] + public async Task OverallStatus_WhenRunCanceled_IsExceptionMessage() + { + // ARRANGE + var fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + var opCanceled = new OperationCanceledException("CaNcElEd"); + _ = fixture.Freeze>().WithException(opCanceled); + var loggerMock = new LoggerMock().Inject(fixture); + + var sut = fixture.Create(); + SetFolders(sut, fs); + + // ACT + await sut.StartCommand.Execute(Unit.Default); + + // ASSERT + sut.OverallStatus.Should().Be(opCanceled.Message); + loggerMock.VerifyEntryCount(2); + loggerMock.VerifyEntry(1, LogLevel.Warning, "Run canceled."); + } + + [Fact] + public async Task OverallStatus_WhenOtherException_IsExceptionMessageWithWarningSign() + { + // ARRANGE + var fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + var invalidOp = new InvalidOperationException("Foo bar baz."); + _ = fixture.Freeze>().WithException(invalidOp); + var loggerMock = new LoggerMock().Inject(fixture); + + var sut = fixture.Create(); + SetFolders(sut, fs); + + // ACT + await sut.StartCommand.Execute(Unit.Default); + + // ASSERT + sut.OverallStatus.Should().Be("⚠ Foo bar baz."); + loggerMock.VerifyEntryCount(2); + loggerMock.VerifyEntry(^1, LogLevel.Error, "Run failed: " + invalidOp); + } + + [Fact] + public async Task OverallStatus_WhenInputDirectoryNotSet_IsExpected() + { + // ARRANGE + var fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + var compressorMock = fixture.Freeze>(); + var loggerMock = new LoggerMock().Inject(fixture); + + var sut = fixture.Create(); + SetFolders(sut, fs, setInput: false); + + // ACT + await sut.StartCommand.Execute(Unit.Default); + + // ASSERT + sut.OverallStatus.Should().Be("⚠ Input folder is not set."); + + loggerMock.VerifySingleEntry(LogLevel.Error, "Run failed: System.InvalidOperationException: Input folder is not set.*at*"); + + compressorMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task OverallStatus_WhenOutputDirectoryNotSet_IsExpected() + { + // ARRANGE + var fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + var compressorMock = fixture.Freeze>(); + var loggerMock = new LoggerMock().Inject(fixture); + + var sut = fixture.Create(); + SetFolders(sut, fs, setOutput: false); + + // ACT + await sut.StartCommand.Execute(Unit.Default); + + // ASSERT + sut.OverallStatus.Should().Be("⚠ Output folder is not set."); + + loggerMock.VerifySingleEntry(LogLevel.Error, "Run failed: System.InvalidOperationException: Output folder is not set.*at*"); + + compressorMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task OverallStatus_WhenInputDirectoryDoesNotExist_IsExpected() + { + // ARRANGE + var fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + var compressorMock = fixture.Freeze>(); + var loggerMock = new LoggerMock().Inject(fixture); + + var sut = fixture.Create(); + SetFolders(sut, fs); + string inputDir = fs.Path.Join(fs.Path.GetTempPath(), "Input"); + sut.InputDirectory = inputDir; + + // ACT + await sut.StartCommand.Execute(Unit.Default); + + // ASSERT + sut.OverallStatus.Should().Be($"⚠ Directory {inputDir} does not exist."); + loggerMock.VerifySingleEntry(LogLevel.Error, $"Run failed: System.IO.DirectoryNotFoundException: Directory {inputDir} does not exist.*at*"); + compressorMock.VerifyNoOtherCalls(); + } + + [Fact] + public async Task OverallStatus_WhenProcessing_HasExpectedValues() + { + // ARRANGE + IFixture fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + _ = fixture.Freeze>().WithWaitForCancellation(); + + var sut = fixture.Create(); + ICommand start = sut.StartCommand; + ICommand stop = sut.StopCommand; + SetFolders(sut, fs); + + Dictionary overallStatusWhen = new(); + + void AddDataPoint(string timePoint) + { + overallStatusWhen[timePoint] = sut.OverallStatus; + } + + // ACT + AddDataPoint("ready to run"); + + for (int i = 0; i < 2; i++) + { + Task startCommandTask; + using (var monitor = sut.Monitor()) + { + startCommandTask = sut.StartCommand.Execute(default).ToTask(); + AddDataPoint("running" + i); + + if (i == 0) + { + monitor.Should().NotRaisePropertyChangeFor(mvm => mvm.OverallStatus); + } + else + { + monitor.Should().RaisePropertyChangeFor(mvm => mvm.OverallStatus); + } + } + + Task stopCommandTask; + using (var monitor = sut.Monitor()) + { + stopCommandTask = sut.StopCommand.Execute(default).ToTask(); + + await startCommandTask; + await stopCommandTask; + + AddDataPoint("stopped" + i); + monitor.Should().RaisePropertyChangeFor(mvm => mvm.OverallStatus); + } + } + + // ASSERT + overallStatusWhen["ready to run"].Should().BeEmpty(); + overallStatusWhen["running0"].Should().BeEmpty(); + overallStatusWhen["stopped0"].Should().Be(new OperationCanceledException().Message); + overallStatusWhen["running1"].Should().BeEmpty(); + overallStatusWhen["stopped1"].Should().Be(new OperationCanceledException().Message); + } + + private static MockFileSystem NewMockFileSystem() + { + return new MockFileSystem( + new MockFileSystemOptions { CreateDefaultTempDir = true }); + } + + private static void SetFolders(MainViewModel sut, IFileSystem fs, bool setInput = true, bool setOutput = true) + { + string someExistingDir = fs.Path.GetTempPath(); + if (setInput) + { + sut.InputDirectory = someExistingDir; + } + + if (setOutput) + { + sut.OutputDirectory = fs.Path.Join(someExistingDir, "output"); + } + } + + private class LoggerMock : ILogger + { + private static readonly EventId Zero = (EventId)0; + + private List<(LogLevel Level, EventId Event, string Message)> LogEntries { get; } = new(); + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + lock (this.LogEntries) + { + this.LogEntries.Add(new(logLevel, eventId, formatter.Invoke(state, exception))); + } + } + + public void VerifyEntryCount(int count) + { + this.LogEntries.Should().HaveCount(count); + } + + public void VerifyEntry(Index index, LogLevel expectedLevel, string messageWildcardPattern) + { + (LogLevel level, EventId eventId, string message) = this.LogEntries[index]; + level.Should().Be(expectedLevel); + eventId.Should().Be(Zero); + message.Should().Match(messageWildcardPattern); + } + + public void VerifySingleEntry(LogLevel expectedLevel, string messageWildcardPattern) + { + this.VerifyEntryCount(1); + this.VerifyEntry(0, expectedLevel, messageWildcardPattern); + } + + public LoggerMock Inject(IFixture fixture) + { + fixture.Inject>(this); + return this; + } + + public bool IsEnabled(LogLevel logLevel) => true; + + public IDisposable? BeginScope(TState state) + where TState : notnull + { + return null; + } + } +} diff --git a/czishrink/netczicompressTests/ViewModels/MainViewModelTests.PropertyChanged.cs b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.PropertyChanged.cs new file mode 100644 index 0000000..64cf707 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.PropertyChanged.cs @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using System.ComponentModel; + +using AutoFixture; + +using netczicompress.ViewModels; + +/// +/// Tests for . +/// +/// Tests for . +public partial class MainViewModelTests +{ + [Fact] + public void InputDirectory_WhenSet_RaisesPropertyChanged() + { + // ARRANGE + var fixture = CreateFixture(); + var sut = fixture.Create(); + + // ACT + using var monitor = sut.Monitor(); + sut.InputDirectory = "foo"; + + // ASSERT + monitor.Should().RaisePropertyChangeFor(x => x.InputDirectory); + } + + [Fact] + public void OutputDirectory_WhenSet_RaisesPropertyChanged() + { + // ARRANGE + var fixture = CreateFixture(); + var sut = fixture.Create(); + + // ACT + using var monitor = sut.Monitor(); + sut.OutputDirectory = "foo"; + + // ASSERT + monitor.Should().RaisePropertyChangeFor(x => x.OutputDirectory); + } + + [Fact] + public void Recursive_WhenSet_RaisesPropertyChanged() + { + // ARRANGE + var fixture = CreateFixture(); + var sut = fixture.Create(); + + // ACT + using var monitor = sut.Monitor(); + sut.Recursive = !sut.Recursive; + + // ASSERT + monitor.Should().RaisePropertyChangeFor(x => x.Recursive); + } + + [Theory] + [AutoData] + public void SelectedMode_WhenSet_RaisesPropertyChanged(OperationMode mode) + { + // ARRANGE + var fixture = CreateFixture(); + var sut = fixture.Create(); + + // ACT + using var monitor = sut.Monitor(); + sut.SelectedMode = mode; + + // ASSERT + monitor.Should().RaisePropertyChangeFor(x => x.SelectedMode); + } +} diff --git a/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StartCommand.cs b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StartCommand.cs new file mode 100644 index 0000000..f148c54 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StartCommand.cs @@ -0,0 +1,232 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using System.IO.Abstractions; +using System.Reactive.Threading.Tasks; +using System.Windows.Input; + +using AutoFixture; + +using netczicompress.ViewModels; + +using netczicompressTests.Models.Mocks; + +/// +/// Tests for . +/// +/// Tests for . +public partial class MainViewModelTests +{ + [Fact] + public void StartCommand_WhenInputFolderNotSet_CannotExecute() + { + // ARRANGE + IFixture fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + + // ACT + var sut = fixture.Create(); + SetFolders(sut, fs, setInput: false); + + // ASSERT + ICommand command = sut.StartCommand; + command.CanExecute(null).Should().BeFalse(); + } + + [Fact] + public void StartCommand_WhenOutputFolderNotSet_CannotExecute() + { + // ARRANGE + IFixture fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + + // ACT + var sut = fixture.Create(); + SetFolders(sut, fs, setOutput: false); + + // ASSERT + ICommand command = sut.StartCommand; + command.CanExecute(null).Should().BeFalse(); + } + + [Fact] + public void StartCommand_WhenBothFoldersSet_CanExecute() + { + // ARRANGE + IFixture fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + + bool expectCanExecute = false; + List<(bool Expectation, bool Actual)> data = new(); + + var sut = fixture.Create(); + sut.StartCommand.CanExecute.Subscribe(onNext: + canExec => data.Add((expectCanExecute, canExec))); + + // ACT + SetFolders(sut, fs, setOutput: false); + + expectCanExecute = true; + SetFolders(sut, fs, setInput: false); + + // ASSERT + ICommand start = sut.StartCommand; + start.CanExecute(null).Should().BeTrue(); + + data.Count.Should().Be(2); + foreach (var (expectation, actual) in data) + { + actual.Should().Be(expectation); + } + } + + [Fact] + public async Task StartCommand_WhenProcessing_CanExecuteHasExpectedValues() + { + // ARRANGE + IFixture fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + _ = fixture.Freeze>().WithWaitForCancellation(); + + var sut = fixture.Create(); + ICommand start = sut.StartCommand; + SetFolders(sut, fs); + + Dictionary canStartWhen = new(); + + void AddDataPoint(string timePoint) + { + Thread.MemoryBarrier(); + canStartWhen[timePoint] = start.CanExecute(null); + } + + // ACT + AddDataPoint("ready to run"); + + var startCommandTask = sut.StartCommand.Execute(default).ToTask(); + AddDataPoint("running"); + + var stopCommandTask = sut.StopCommand.Execute(default).ToTask(); + await startCommandTask; + await stopCommandTask; + AddDataPoint("stopped"); + + for (int k = 0; !canStartWhen["stopped"] && k < 100; k++) + { + // canStartWhen["stopped"] has sporadically been false, + // e. g. in https://github.com/m-ringler/netczicompress/actions/runs/6195449563/job/16820188169?pr=47 + // This is probably due to the fact that ReactiveCommand overrides the passed in CanExecute observable + // with false while it is executing. So, CanExecute becomes true again only _after_ the command has + // stopped executing, i. e. _after_ startCommandTask completes. Thus, we have a race condition. + // => Wait a bit and try again. + await Task.Delay(10); + AddDataPoint("stopped"); + } + + // ASSERT + canStartWhen["ready to run"].Should().BeTrue(); + canStartWhen["running"].Should().BeFalse(); + try + { + canStartWhen["stopped"].Should().BeTrue(); + } + catch (Exception ex) + { + throw new Exception(canStartWhen["stopped"].ToString(), ex); + } + } + + [Fact] + public async Task StartCommands_WhenProcessingCompletes_CanExecuteIsTrue() + { + // ARRANGE + IFixture fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + _ = fixture.Freeze>().WithAutoData(fixture); + + var sut = fixture.Create(); + SetFolders(sut, fs); + + // ACT + var startCommandTask = sut.StartCommand.Execute(default).ToTask(); + await startCommandTask; + + // ASSERT + ICommand start = sut.StartCommand; + start.CanExecute(null).Should().BeTrue(); + } + + [Theory] + [AutoData] + public async Task StartCommands_WhenProcessingStarts_UsesCorrectParameters(OperationMode selectedMode, bool recursive) + { + // ARRANGE + IFixture fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + var folderCompressorMock = fixture.Freeze>().WithWaitForCancellation(); + + var sut = fixture.Create(); + SetFolders(sut, fs); + sut.SelectedMode = selectedMode; + sut.Recursive = recursive; + + // ACT + var startCommandTask = sut.StartCommand.Execute(default).ToTask(); + + // ASSERT + folderCompressorMock.Verify( + x => x.PrepareNewRun( + It.Is( + x => x.InputDir.FullName == sut.InputDirectory && + x.OutputDir.FullName == sut.OutputDirectory && + x.InputDir.FileSystem == fs && + x.OutputDir.FileSystem == fs && + x.Recursive == recursive && + x.Mode == selectedMode.Value), + It.Is(t => !t.IsCancellationRequested)), + Times.Once); + + // CLEANUP + await sut.StopCommand.Execute(default); + await startCommandTask; + folderCompressorMock.VerifyNoOtherCalls(); + } + + [Theory] + [AutoData] + public async Task StartCommands_WhenProcessingStarts_LogsCorrectParameters(OperationMode selectedMode, bool recursive) + { + // ARRANGE + IFixture fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + _ = fixture.Freeze>().WithWaitForCancellation(); + var loggerMock = new LoggerMock().Inject(fixture); + + var sut = fixture.Create(); + SetFolders(sut, fs); + sut.SelectedMode = selectedMode; + sut.Recursive = recursive; + + // ACT + var startCommandTask = sut.StartCommand.Execute(default).ToTask(); + + // ASSERT + loggerMock.VerifySingleEntry( + Microsoft.Extensions.Logging.LogLevel.Information, + $"Starting run: FolderCompressorParameters {{ InputDir = {sut.InputDirectory}, OutputDir = {sut.OutputDirectory}, Recursive = {recursive}, Mode = {selectedMode.Value}, ExecutionOptions = ExecutionOptions {{ ThreadCount = {sut.ThreadCount} }}, ProcessingOptions = ProcessingOptions {{ CompressionLevel = {sut.CompressionLevel}, CopyFailedFiles = {sut.CopyFailedFiles} }} }}"); + + // CLEANUP + await sut.StopCommand.Execute(default); + await startCommandTask; + } +} diff --git a/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StopCommand.cs b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StopCommand.cs new file mode 100644 index 0000000..60860f9 --- /dev/null +++ b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StopCommand.cs @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2023 Carl Zeiss Microscopy GmbH +// +// SPDX-License-Identifier: GPL-3.0-or-later + +namespace netczicompressTests.ViewModels; + +using System.IO.Abstractions; +using System.Reactive.Threading.Tasks; +using System.Windows.Input; + +using AutoFixture; + +using netczicompress.ViewModels; + +using netczicompressTests.Models.Mocks; + +/// +/// Tests for . +/// +/// Tests for . +public partial class MainViewModelTests +{ + [Fact] + public async Task StopCommand_WhenProcessing_CanExecuteHasExpectedValues() + { + // ARRANGE + IFixture fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + _ = fixture.Freeze>().WithWaitForCancellation(); + + var sut = fixture.Create(); + ICommand start = sut.StartCommand; + ICommand stop = sut.StopCommand; + SetFolders(sut, fs); + + Dictionary canStopWhen = new(); + + void AddDataPoint(string timePoint) + { + var canStop = stop.CanExecute(null); + canStopWhen[timePoint] = stop.CanExecute(null); + sut.CanStop.Should().Be(canStop); + } + + // ACT + AddDataPoint("ready to run"); + + Task startCommandTask; + using (var monitor = sut.Monitor()) + { + startCommandTask = sut.StartCommand.Execute(default).ToTask(); + monitor.Should().RaisePropertyChangeFor(mvm => mvm.CanStop); + AddDataPoint("running"); + } + + Task stopCommandTask; + using (var monitor = sut.Monitor()) + { + stopCommandTask = sut.StopCommand.Execute(default).ToTask(); + monitor.Should().RaisePropertyChangeFor(mvm => mvm.CanStop); + AddDataPoint("stopping"); + } + + await startCommandTask; + await stopCommandTask; + AddDataPoint("stopped"); + + // ASSERT + canStopWhen["ready to run"].Should().BeFalse(); + canStopWhen["running"].Should().BeTrue(); + canStopWhen["stopping"].Should().BeFalse(); + canStopWhen["stopped"].Should().BeFalse(); + } + + [Fact] + public async Task StartCommands_WhenProcessingCompletes_CanExecuteIsFalse() + { + // ARRANGE + IFixture fixture = CreateFixture(); + IFileSystem fs = NewMockFileSystem(); + fixture.Inject(fs); + _ = fixture.Freeze>().WithAutoData(fixture); + + var sut = fixture.Create(); + ICommand stop = sut.StopCommand; + SetFolders(sut, fs); + + // ACT + var startCommandTask = sut.StartCommand.Execute(default).ToTask(); + await startCommandTask; + + // ASSERT + stop.CanExecute(null).Should().BeFalse(); + } +} diff --git a/czishrink/netczicompressTests/mandelbrot.czi b/czishrink/netczicompressTests/mandelbrot.czi new file mode 100644 index 0000000000000000000000000000000000000000..94600acf1e4bdd4723418b3f17bd3bf4dc90cb35 GIT binary patch literal 101600 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 + + false + true + x64 + + + + + + + + + PreserveNewest + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + diff --git a/czishrink/packages_local/libczicompressc.0.5.1-alpha.0.nupkg b/czishrink/packages_local/libczicompressc.0.5.1-alpha.0.nupkg new file mode 100644 index 0000000..f0ad1f7 --- /dev/null +++ b/czishrink/packages_local/libczicompressc.0.5.1-alpha.0.nupkg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:552d2554704d6964e3900a80bcee3d73b6f688aafda0981d7c1d8110fa7560bc +size 2039939 diff --git a/czishrink/stylecop.json b/czishrink/stylecop.json new file mode 100644 index 0000000..11eedfa --- /dev/null +++ b/czishrink/stylecop.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + + "settings": { + "namingRules": { + "allowedNamespaceComponents": [ + "netczicompress", + "netczicompressTests" + ] + } + } +} \ No newline at end of file diff --git a/czishrink/upgrade-libczicompressc.ps1 b/czishrink/upgrade-libczicompressc.ps1 new file mode 100644 index 0000000..05385a1 --- /dev/null +++ b/czishrink/upgrade-libczicompressc.ps1 @@ -0,0 +1,363 @@ +<# +.SYNOPSIS + +Upgrades libczicompressc.nupkg in CziShrink with the latest cmake build of main of https://github.com/ZEISS/czicompress. + +.DESCRIPTION + +This script should be run in the czishrink directory of a clone of https://github.com/m-ringler/netczicompress. +Make sure that the working directory is clean. Otherwise the script will stash local changes. + +You must have git and dotnet on your path. + +The script needs to have access to both the https://github.com/ZEISS/czicompress and the https://github.com/m-ringler/netczicompress github repo, +you need to + * create a 'classic' personal access token at https://github.com/settings/tokens or with github cli, + * and authorize it for the ZEISS organization via the "Configure SSO" button, + * and store it in a GITHUB_TOKEN environment variable or pass it as the -GithubToken parameter to this script. + +If you specify a -DownloadFolder the script will use the data downloaded to that folder in a previous run. This provides rudimentary resume-on-error functionality. + +.EXAMPLE + +PS> ./upgrade-libczicompressc.ps1 -DownloadFolder "$env:TEMP/libczicompress/cache" -NoPullRequest + +.PARAMETER DownloadFolder + +The folder where downloads are stored and cached. Defaults to "$env:Temp/libczicompressc/$(Get-Date -Format FileDate)". +Downloads in that folder will be reused unless you use -NoCache. +The folder is created if necessary. +The folder will not be deleted or emptied at the end of the script. + +.PARAMETER NugetExePath + +The path to Nuget.exe. If this is not specified and nuget.exe is found on the $env:PATH we use that nuget.exe. +Otherwise defaults to DownloadFolder/nuget.exe. If nuget.exe is not found it is downloaded and stored at this path. + +.PARAMETER GithubToken + +The github personal access token to use. Can be created at https://github.com/settings/tokens or with github cli (gh auth token). +Defaults to the value of $env:GITHUB_TOKEN. + +.PARAMETER NoPR + +This switch prevents a pull request from being created. + +.PARAMETER NoCache + +This switch prevents previous downloads in the DownloadFolder from being reused. + +.PARAMETER OverwriteExistingBranch + +Overwrites an existing upgrade branch. This can be useful if a previous run of the script has failed. Applies to local and remote. + +#> + +param( + [Parameter(Mandatory=$false)] + [AllowNull()] + [string]$DownloadFolder = $null, + + [Parameter(Mandatory=$false)] + [string]$NugetExePath = $null, + + [Parameter(Mandatory=$false)] + [string]$GithubToken = $env:GITHUB_TOKEN, + + [Parameter(Mandatory=$false)] + [switch]$NoPR, + + [Parameter(Mandatory=$false)] + [switch]$NoCache, + + [Parameter(Mandatory=$false)] + [switch]$OverwriteExistingBranch +) + +$CreatePullRequest = !$NoPR +$UseCache = !$NoCache + +Import-Module -Name Microsoft.PowerShell.Utility +Import-Module -Name Microsoft.PowerShell.Archive + +$ErrorActionPreference = "Stop" + +# The name of the CziCompress repo +$CziCompress = "ZEISS/czicompress" +########################################################## +# PART 1: Download latest CmakeBuild artifacts from GitHub +########################################################## + +if ($null -eq $GithubToken) { + throw "You must set the GITHUB_TOKEN environment variable to a personal access token of yours: https://github.com/settings/tokens. See also: https://docs.github.com/en/enterprise-cloud@latest/authentication/authenticating-with-saml-single-sign-on/authorizing-a-personal-access-token-for-use-with-saml-single-sign-on" +} + +$GithubApiHeaders = @{ + 'Accept' = 'application/vnd.github+json' + 'X-GitHub-Api-Version' = '2022-11-28' + 'Authorization' = "Bearer $GithubToken" +} + +if (!$DownloadFolder) { + $DownloadFolder = "$env:Temp/libczicompressc/$(Get-Date -Format FileDate)" +} + +Write-Output "INFO: Using DownloadFolder $DownloadFolder" +if (!(Test-Path $DownloadFolder)) { + mkdir $DownloadFolder +} + +# We'll use the following properties +# artifacts_url -> download dll/so +# head_sha -> nuspec git commit +$LatestRunJson = "$DownloadFolder/LatestRunOfCMakeBuild.json" + +if ($UseCache -and (Test-Path -Path $LatestRunJson)) { + Write-Output "INFO: Using previously downloaded build info from $LatestRunJson." + $LatestRunOfCMakeBuild = Get-Content $LatestRunJson | ConvertFrom-Json +} else { + # Get latest run + Write-Output "INFO: Getting build info from github.com/$CziCompress." + $ActionRunsUri = "https://api.github.com/repos/$CziCompress/actions/runs?branch=main&status=success&per_page=10" + $ServerResponse = Invoke-WebRequest -Uri $ActionRunsUri -Headers $GithubApiHeaders | ConvertFrom-Json + $RunsOfCmakeBuild = $ServerResponse.workflow_runs | Where-Object Name -eq 'CMake Build' + + $LatestRunOfCMakeBuild = $RunsOfCmakeBuild | Sort-Object -Property 'run_number' | Select-Object -Last 1 + $LatestRunOfCMakeBuild | ConvertTo-Json | Set-Content -Path $LatestRunJson +} + +$LinuxArtifactZip = "$DownloadFolder/linux.zip" +$WindowsArtifactZip = "$DownloadFolder/win.zip" +$DownloadLinux = $NoCache -or !(Test-Path $LinuxArtifactZip) +$DownloadWindows = $NoCache -or !(Test-Path $WindowsArtifactZip) + +if ($DownloadWindows -or $DownloadLinux) +{ + # Download the artifacts + Write-Output "INFO: Downloading artifacts from $($LatestRunOfCMakeBuild.artifacts_url)" + $ServerResponse = Invoke-WebRequest -Uri $LatestRunOfCMakeBuild.artifacts_url -Headers $GithubApiHeaders | ConvertFrom-Json + + $LinuxArtifact = $ServerResponse.artifacts | Where-Object name -eq "czicompress-ubuntu-release-package-on" + $WindowsArtifact = $ServerResponse.artifacts | Where-Object name -eq "czicompress-windows-64-release-msvc-package-on" + + if ($ServerResponse.total_count -eq 0) { + throw "No artifacts found." + } + + try { + if ($DownloadLinux) + { + Invoke-WebRequest -Uri $LinuxArtifact.archive_download_url -Headers $GithubApiHeaders -OutFile $LinuxArtifactZip + } + + if ($DownloadWindows) + { + Invoke-WebRequest -Uri $WindowsArtifact.archive_download_url -Headers $GithubApiHeaders -OutFile $WindowsArtifactZip + } + } catch { + Write-Output "ERROR: unable to download artifacts." + throw + } +} else { + Write-Output "INFO: Using previously download artifact ZIP files in $DownloadFolder" +} + +# Extract libczicompressc binaries +Expand-Archive -Path $LinuxArtifactZip -Force -DestinationPath "$DownloadFolder" +Expand-Archive -Path $WindowsArtifactZip -Force -DestinationPath "$DownloadFolder" + +############################################# +# Part 2: Build and use the new nuget package +############################################# + +# Get the new library version +$NewLibVersion = (Get-Item "$DownloadFolder/libczicompressc.dll").VersionInfo.ProductVersion.Replace(",", ".") +$NewLibVersion_ = $NewLibVersion.Replace(".", "_") +$NewFileVersion = (Get-Item "$DownloadFolder/libczicompressc.dll").VersionInfo.FileVersionRaw + +$BranchName = "feature/upgrade-libczicompressc-$NewLibVersion_" +Write-Output "INFO: The new library version is $NewLibVersion => Create branch $BranchName and switch to it." + +if (![string]::IsNullOrEmpty($PSScriptRoot)) { + Set-Location "$PSScriptRoot/libczicompressc" +} else { + $current_dir_name = Get-Location | Split-Path -Leaf + if ("czishrink" -eq $current_dir_name) + { + Set-Location "libczicompressc" + } elseif ("libczicompress" -ne $current_dir_name) { + throw "Please CD to the /czishrink directory of a netczicompress clone." + } +} + +# Stash any local changes +$GitStatus = git status --porcelain +if($LASTEXITCODE -ne 0) { + throw "git status failed" +} + +if (![string]::IsNullOrEmpty($GitStatus)) { + Write-Output "INFO: Stashing local changes" + git stash --include-untracked + if($LASTEXITCODE -ne 0) { + throw "git stash failed" + } +} + +# Switch to main and pull +git switch main +if($LASTEXITCODE -ne 0) { + throw "git switch failed" +} + +git pull +if($LASTEXITCODE -ne 0) { + throw "git pull failed" +} + +# Create a new branch +$gitSwitchCreateFlag = "-c" +if ($OverwriteExistingBranch) { + $gitSwitchCreateFlag = "-C" +} + +git switch $gitSwitchCreateFlag $BranchName +if($LASTEXITCODE -ne 0) { + throw "Failed to create branch $BranchName. Please delete the branch if it exists." +} + +# Put the binaries into the nuget folder structure +Write-Output "INFO: Creating the new nuget package..." +$libname="libczicompressc" +$LinuxRuntime="runtimes\linux-x64\native\$libname.so" +$WindowsRuntime="runtimes\win-x64\native\$libname.dll" +Copy-Item -Path "$DownloadFolder/$libname.so" -Destination $LinuxRuntime +Copy-Item -Path "$DownloadFolder/$libname.dll" -Destination $WindowsRuntime + +# Modify the nuspec +$nuspec=Get-Item "libczicompressc.nuspec" +$xml = [xml](Get-Content -Path $nuspec.FullName) +$xml.package.metadata.version = $NewLibVersion +$xml.package.metadata.repository.commit = $LatestRunOfCMakeBuild.head_sha +$xml.Save($nuspec.FullName) + +# Download nuget.exe if necessary and run nuget pack +if (!$NugetExePath) { + try { + $NugetExePath = (Get-Command "nuget.exe").Source + } catch { + $NugetExePath = "$DownloadFolder/nuget.exe" + } +} + +if (!(Test-Path -Path $NugetExePath)) { + Write-Output "INFO: Downloading nuget.exe to $NugetExePath" + Invoke-WebRequest -Uri https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile $NugetExePath +} else { + Write-Output "INFO: Using nuget.exe at $NugetExePath" +} + +& $NugetExePath pack "libczicompressc.nuspec" +if($LASTEXITCODE -ne 0) { + throw "nuget failed" +} + +# Revert changes to so/dll +git checkout -- $LinuxRuntime +if($LASTEXITCODE -ne 0) { + throw "git checkout failed" +} + +git checkout -- $WindowsRuntime +if($LASTEXITCODE -ne 0) { + throw "git checkout failed" +} + +# Exchange the nupkg and update version in Directory.Packages.props +Remove-Item ../packages_local/*.nupkg +Move-Item -Path "libczicompressc.$NewLibVersion.nupkg" -Destination ../packages_local + +Write-Output "INFO: Updating Directory.Packages.props" +Set-Location .. +$file = Get-Item "Directory.Packages.props" +$xml = [xml](Get-Content -Path $file.FullName) +$versionElement = $xml.SelectSingleNode('//PackageVersion[@Include="libczicompressc"]') +$versionElement.Version = $NewLibVersion +$xml.Save($file.FullName) + +################################################ +# Part 3: Update, build, and test netczicompress +################################################ + +# Update PInvokeFileProcessor.cs +Write-Output "INFO: Updating PInvokeFileProcessor.cs" +$SourceCode = Get-Content -Path netczicompress/Models/PInvokeFileProcessor.cs +$NewVersionTuple="($($NewFileVersion.Major), $($NewFileVersion.Minor))" +$AlteredSourceCode = $SourceCode -replace '^( *\(int Major, int Minor\) expected = ).*?;$',('$1' + $NewVersionTuple + ";") +Set-Content -Path netczicompress/Models/PInvokeFileProcessor.cs -Value $AlteredSourceCode + +# Increment VersionSuffix +Write-Output "INFO: Incrementing VersionSuffix in Directory.Build.props" +$file = Get-Item "Directory.Build.props" +$xml = [xml](Get-Content -Path $file.FullName) +$versionElement = $xml.SelectSingleNode('//VersionSuffix') +$VersionTokens = $versionElement.'#text'.split(".") +$versionElement.'#comment' = $BranchName +$VersionTokens[1] = [string]([int]($VersionTokens.split(".")[1]) + 1) +$versionElement.'#text' = "$($VersionTokens[0]).$($VersionTokens[1])" +$xml.Save($file.FullName) + +# Git commit everything +Write-Output "INFO: Committing all changes" +$CommitMessage = "Upgrade $libname to $NewLibVersion" +git add . +if($LASTEXITCODE -ne 0) { + throw "git add failed" +} +git commit -m $CommitMessage +if($LASTEXITCODE -ne 0) { + throw "git commit failed" +} + +# Run tests +Write-Output "INFO: Run tests" +dotnet test +if($LASTEXITCODE -ne 0) { + throw "dotnet test failed" +} + +# Git Push +Write-Output "INFO: Push changes" + +$gitForceFlag = "" +if ($OverwriteExistingBranch) { + $gitForceFlag = "--force" +} + +git push $gitForceFlag --set-upstream origin $BranchName +if($LASTEXITCODE -ne 0) { + throw "git push failed" +} + +# Create a pull request +if ($CreatePullRequest) { + $remote = git config --get remote.origin.url + if($LASTEXITCODE -ne 0) { + throw "git config failed" + } + + $CziShrinkRepo = $remote.Replace("https://github.com/", "") -replace "\.git$", "" + + Write-Output "INFO: Create a Pull Request" + $postParams = @{ + 'title'=$CommitMessage + 'body'="Automatic upgrade of $libname" + 'head'=$BranchName + 'base'='main' + } | ConvertTo-Json + + $ServerResponse = Invoke-WebRequest -Headers $GithubApiHeaders -Uri https://api.github.com/repos/$CziShrinkRepo/pulls -Method POST -Body $postParams + $PullRequestUrl = ($ServerResponse | ConvertFrom-Json).html_url + Write-Output "Opened a PR at $PullRequestUrl" + Start-Process $PullRequestUrl +} \ No newline at end of file From 2a89b1590d1fa67e0b7ab5be9ba393e077dc3d58 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 14:10:32 +0100 Subject: [PATCH 07/31] Fix REUSE --- .github/workflows/czishrink_codeql.yml | 2 +- .reuse/dep5 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/czishrink_codeql.yml b/.github/workflows/czishrink_codeql.yml index 1004c0f..891e8d2 100644 --- a/.github/workflows/czishrink_codeql.yml +++ b/.github/workflows/czishrink_codeql.yml @@ -1,5 +1,5 @@ --- -name: "CodeQL" +name: "CodeQL (CziShrink)" on: # push: diff --git a/.reuse/dep5 b/.reuse/dep5 index 9c3ee9d..8da6156 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -11,7 +11,7 @@ Files: .github/* .gitignore czicompress/.editorconfig czicompress/.gitignore czi Copyright: 2023 Carl Zeiss Microscopy GmbH License: CC0-1.0 -Files: czishrink/*.axaml czishrink/*.json czishrink/*.nuspec czishrink/*.txt czishrink/*.ico czishrink/*.png czishrink/*.props czishrink/*.targets czishrink/*.sh czishrink/*.svg czishrink/*.csproj czishrink/*.gitattributes czishrink/*.nupkg czishrink/*.md czishrink/*.pdf czishrink/*.config czishrink/*.ruleset czishrink/*.ps1 czishrink/*.czi czishrink/*.xml czishrink/*.sln czishrink/*.manifest README.md +Files: czishrink/*.axaml czishrink/.gitignore czishrink/*/.gitignore czishrink/*.json czishrink/*.nuspec czishrink/*.txt czishrink/*.ico czishrink/*.png czishrink/*.props czishrink/*.targets czishrink/*.sh czishrink/*.svg czishrink/*.csproj czishrink/*.gitattributes czishrink/*.nupkg czishrink/*.md czishrink/*.pdf czishrink/*.config czishrink/*.ruleset czishrink/*.ps1 czishrink/*.czi czishrink/*.xml czishrink/*.sln czishrink/*.manifest README.md Copyright: 2023 Carl Zeiss Microscopy GmbH License: GPL-3.0-or-later From dc9f6bdb258751314733cd7a7c9240aaf5ff2dc4 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 14:38:02 +0100 Subject: [PATCH 08/31] Activate CodeQL for czishrink --- .github/workflows/czishrink_codeql.yml | 54 +++++++++++++++++--------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/.github/workflows/czishrink_codeql.yml b/.github/workflows/czishrink_codeql.yml index 891e8d2..07dab0c 100644 --- a/.github/workflows/czishrink_codeql.yml +++ b/.github/workflows/czishrink_codeql.yml @@ -2,12 +2,23 @@ name: "CodeQL (CziShrink)" on: - # push: - # branches: [main] - # paths: ["czishrink/**", ".github/workflows/czishrink_codeql.yml"] - # pull_request: - # branches: [main] - # paths: ["czishrink/**", ".github/workflows/czishrink_codeql.yml"] + push: + branches: [main] + paths: ["czishrink/**", ".github/workflows/czishrink_codeql.yml"] + pull_request: + branches: [main] + paths: ["czishrink/**", ".github/workflows/czishrink_codeql.yml"] + schedule: + # ┌───────────── minute (0 - 59) + # │ ┌───────────── hour (0 - 23) + # │ │ ┌───────────── day of the month (1 - 31) + # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) + # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) + # │ │ │ │ │ + # │ │ │ │ │ + # │ │ │ │ │ + # * * * * * + - cron: '30 2 * * 2' workflow_dispatch: {} permissions: read-all @@ -17,28 +28,33 @@ jobs: name: Analyze CziShrink defaults: run: - working-directory: ${{github.workspace}}/czishrink + working-directory: czishrink runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - language: ['csharp'] + permissions: + security-events: write steps: - name: Checkout repository uses: actions/checkout@v3 + with: + lfs: true + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 7.0.x - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: - languages: ${{ matrix.language }} + languages: csharp queries: security-and-quality - - name: Autobuild - uses: github/codeql-action/autobuild@v1 - with: - working-directory: czishrink + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore -c Release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 From 16f92411921c6aa022b402ab49a88e973d36c43f Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 16:28:11 +0100 Subject: [PATCH 09/31] Change workflow name --- .github/workflows/czishrink_dotnet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/czishrink_dotnet.yml b/.github/workflows/czishrink_dotnet.yml index bc3b88d..f358fd5 100644 --- a/.github/workflows/czishrink_dotnet.yml +++ b/.github/workflows/czishrink_dotnet.yml @@ -5,7 +5,7 @@ permissions: pull-requests: write contents: read -name: .NET +name: .NET Build (CziShrink) on: push: From 7a81d43c12583e9815897722307b2354337538f2 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 16:29:11 +0100 Subject: [PATCH 10/31] Change workflow name --- .github/workflows/czicompress_cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/czicompress_cmake.yml b/.github/workflows/czicompress_cmake.yml index 300edd7..0137ef9 100644 --- a/.github/workflows/czicompress_cmake.yml +++ b/.github/workflows/czicompress_cmake.yml @@ -1,5 +1,5 @@ --- -name: CMake Build +name: CMake Build (czicompress) on: push: From fb34d1fe553dfd773a171d5818878ee1ab54dff5 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 16:44:00 +0100 Subject: [PATCH 11/31] Adapt badges in Readme --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1cead74..914ff5e 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,21 @@ # czicompress -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![REUSE status](https://api.reuse.software/badge/github.com/ZEISS/czicompress)](https://api.reuse.software/info/github.com/ZEISS/czicompress) [![MegaLinter](https://github.com/ZEISS/czicompress/actions/workflows/mega-linter.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/mega-linter.yml) Reduce the size of existing CZI files by converting them to zstd-compressed CZI files. # Command line tool and C/C++ libraries +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![CMake](https://github.com/ZEISS/czicompress/actions/workflows/cmake.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_cmake.yml) [![CodeQL](https://github.com/ZEISS/czicompress/actions/workflows/codeql.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_codeql.yml) [czicompress](czicompress/README.md) is a cross-platform command line tool to compress CZIs. czicompress and its source code are provided under the [MIT license](LICENSE). # CziShrink desktop app +[![License: GPL](https://img.shields.io/badge/License-GPL%203+-yellow.svg)](https://spdx.org/licenses/GPL-3.0-or-later.html) +[![Build](https://github.com/ZEISS/czicompress/actions/workflows/czishrink_dotnet.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/czishrink_dotnet.yml) +[![CodeQL](https://github.com/ZEISS/czicompress/actions/workflows/czishrink_codeql.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/czishrink_codeql.yml) + [czishrink](czishrink/README.md) is a desktop app to compress CZIs. CziShrink is provided under the [GPLv3 license](czishrink/LICENSE.txt). ## Guidelines From e37e3b890ddde62076e96531ccebbbf0b890cc49 Mon Sep 17 00:00:00 2001 From: m-ringler <42344272+m-ringler@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:22:41 +0100 Subject: [PATCH 12/31] Increment czishrink version --- czishrink/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/czishrink/Directory.Build.props b/czishrink/Directory.Build.props index 4066629..d154d14 100644 --- a/czishrink/Directory.Build.props +++ b/czishrink/Directory.Build.props @@ -11,6 +11,6 @@ true 1.0.0 - alpha.45 + alpha.46 From 1e91b9f5d59f2dc1530033668300c846340e0497 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 17:34:02 +0100 Subject: [PATCH 13/31] Fix cmake artifact upload --- .github/workflows/czicompress_cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/czicompress_cmake.yml b/.github/workflows/czicompress_cmake.yml index 0137ef9..16c937f 100644 --- a/.github/workflows/czicompress_cmake.yml +++ b/.github/workflows/czicompress_cmake.yml @@ -111,4 +111,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: czicompress-${{matrix.config.name}} - path: czicompress-${{matrix.config.name}}/ + path: czicompress/czicompress-${{matrix.config.name}}/ From e2a849654a06ef6a1f725275d2baa8d41325a5e9 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 18:10:48 +0100 Subject: [PATCH 14/31] Update build name in upgrade script --- czishrink/upgrade-libczicompressc.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/czishrink/upgrade-libczicompressc.ps1 b/czishrink/upgrade-libczicompressc.ps1 index 05385a1..f78fa58 100644 --- a/czishrink/upgrade-libczicompressc.ps1 +++ b/czishrink/upgrade-libczicompressc.ps1 @@ -120,7 +120,7 @@ if ($UseCache -and (Test-Path -Path $LatestRunJson)) { Write-Output "INFO: Getting build info from github.com/$CziCompress." $ActionRunsUri = "https://api.github.com/repos/$CziCompress/actions/runs?branch=main&status=success&per_page=10" $ServerResponse = Invoke-WebRequest -Uri $ActionRunsUri -Headers $GithubApiHeaders | ConvertFrom-Json - $RunsOfCmakeBuild = $ServerResponse.workflow_runs | Where-Object Name -eq 'CMake Build' + $RunsOfCmakeBuild = $ServerResponse.workflow_runs | Where-Object Name -eq 'CMake Build (czicompress)' $LatestRunOfCMakeBuild = $RunsOfCmakeBuild | Sort-Object -Property 'run_number' | Select-Object -Last 1 $LatestRunOfCMakeBuild | ConvertTo-Json | Set-Content -Path $LatestRunJson From 759a643defd3c32225c423edf225b999ff7b6438 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Tue, 7 Nov 2023 18:11:36 +0100 Subject: [PATCH 15/31] Add run ID to VersionSuffix in dotnet build --- .github/workflows/czishrink_dotnet.yml | 24 +++++++++++++------ .../netczicompressTests/AppComposerTests.cs | 2 +- .../Models/ProgramNameAndVersionTests.cs | 4 ++-- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/.github/workflows/czishrink_dotnet.yml b/.github/workflows/czishrink_dotnet.yml index f358fd5..0fd9299 100644 --- a/.github/workflows/czishrink_dotnet.yml +++ b/.github/workflows/czishrink_dotnet.yml @@ -49,6 +49,23 @@ jobs: with: dotnet-version: 7.0.x + - name: Get Version from Directory.Build.props + id: getversion + uses: m-ringler/get-xpath@v3 + with: + xml-file: czishrink/Directory.Build.props + xpath: 'concat(//VersionPrefix/text(), "-", //VersionSuffix/text())' + + - name: Add build ID to version + run: | + Write-Output "Add build ID ${{ github.run_id }} to VersionSuffix in Directory.Build.props" + $file = Get-Item "Directory.Build.props" + $xml = [xml](Get-Content -Path $file.FullName) + $versionElement = $xml.SelectSingleNode('//VersionSuffix') + $versionElement.'#text' += '+${{ github.run_id }}' + $xml.Save($file.FullName) + shell: pwsh + - name: Restore dependencies run: dotnet restore @@ -111,13 +128,6 @@ jobs: -p:PublishReadyToRunShowWarnings=true -o ${{ github.workspace }}/publish - - name: Get Version from Directory.Build.props - id: getversion - uses: m-ringler/get-xpath@v3 - with: - xml-file: czishrink/Directory.Build.props - xpath: 'concat(//VersionPrefix/text(), "-", //VersionSuffix/text())' - - name: Upload published binaries uses: actions/upload-artifact@v3 if: github.event_name == 'push' diff --git a/czishrink/netczicompressTests/AppComposerTests.cs b/czishrink/netczicompressTests/AppComposerTests.cs index 0b57ec4..cae75d8 100644 --- a/czishrink/netczicompressTests/AppComposerTests.cs +++ b/czishrink/netczicompressTests/AppComposerTests.cs @@ -45,6 +45,6 @@ public void ComposeMainViewModel_WhenCalledDoesNotThrow_AndLogsMessage() actual.OverallStatus.Should().BeEmpty(); } - [GeneratedRegex(@"^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+?[+-]\d\d:\d\d\|netczicompress.App\|INFO\|0\|Starting CZI Shrink 1\.0\.0-alpha\.[1-9]\d* using libczicompressc \d*?\.\d+?\.\d+?.*")] + [GeneratedRegex(@"^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+?[+-]\d\d:\d\d\|netczicompress.App\|INFO\|0\|Starting CZI Shrink \d+\.\d+\.\d+.*? using libczicompressc \d+\.\d+\.\d+.*")] private static partial Regex ExpectedLogMessagePattern(); } \ No newline at end of file diff --git a/czishrink/netczicompressTests/Models/ProgramNameAndVersionTests.cs b/czishrink/netczicompressTests/Models/ProgramNameAndVersionTests.cs index 7c395ab..1c15d3f 100644 --- a/czishrink/netczicompressTests/Models/ProgramNameAndVersionTests.cs +++ b/czishrink/netczicompressTests/Models/ProgramNameAndVersionTests.cs @@ -18,7 +18,7 @@ public void ToString_WhenCalled_ReturnsExpected() var actual = new ProgramNameAndVersion().ToString(); // ASSERT - var re = new Regex(@"^CZI Shrink 1\.0\.0-alpha\.[1-9]\d\d*$"); + var re = new Regex(@"^CZI Shrink 1\.0\.0-alpha\.[1-9]\d\d*(\+\d+)?$"); re.IsMatch(actual).Should().BeTrue(); } @@ -39,7 +39,7 @@ public void Version_WhenCalled_ReturnsExpected() var actual = new ProgramNameAndVersion().Version; // ASSERT - var re = new Regex(@"^1\.0\.0-alpha\.[1-9]\d\d*$"); + var re = new Regex(@"^1\.0\.0-alpha\.[1-9]\d\d*(\+\d+)?$"); re.IsMatch(actual).Should().BeTrue(); } } From a1e577a1766e5e1f4cbd599c1d79d78679e76e39 Mon Sep 17 00:00:00 2001 From: m-ringler <42344272+m-ringler@users.noreply.github.com> Date: Tue, 7 Nov 2023 21:23:55 +0100 Subject: [PATCH 16/31] Fix links in README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 914ff5e..97ac11b 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ Reduce the size of existing CZI files by converting them to zstd-compressed CZI # Command line tool and C/C++ libraries [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![CMake](https://github.com/ZEISS/czicompress/actions/workflows/cmake.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_cmake.yml) -[![CodeQL](https://github.com/ZEISS/czicompress/actions/workflows/codeql.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_codeql.yml) +[![CMake](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_cmake.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_cmake.yml) +[![CodeQL](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_codeql.yml/badge.svg?branch=main&event=push)](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_codeql.yml) -[czicompress](czicompress/README.md) is a cross-platform command line tool to compress CZIs. czicompress and its source code are provided under the [MIT license](LICENSE). +[czicompress](czicompress/README.md) is a cross-platform command line tool to compress CZIs. czicompress and its source code are provided under the [MIT license](czicompress/LICENSE). # CziShrink desktop app [![License: GPL](https://img.shields.io/badge/License-GPL%203+-yellow.svg)](https://spdx.org/licenses/GPL-3.0-or-later.html) @@ -23,4 +23,4 @@ Reduce the size of existing CZI files by converting them to zstd-compressed CZI [Contributing](./CONTRIBUTING.md) ## Disclaimer -ZEISS, ZEISS.com are registered trademarks of Carl Zeiss AG. \ No newline at end of file +ZEISS, ZEISS.com are registered trademarks of Carl Zeiss AG. From dd541b0fed9c9d949b224947f0479aa2ae0e910b Mon Sep 17 00:00:00 2001 From: m-ringler <42344272+m-ringler@users.noreply.github.com> Date: Tue, 7 Nov 2023 21:47:23 +0100 Subject: [PATCH 17/31] Update upgrade-libczicompressc.ps1 --- czishrink/upgrade-libczicompressc.ps1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/czishrink/upgrade-libczicompressc.ps1 b/czishrink/upgrade-libczicompressc.ps1 index f78fa58..85c9703 100644 --- a/czishrink/upgrade-libczicompressc.ps1 +++ b/czishrink/upgrade-libczicompressc.ps1 @@ -1,22 +1,22 @@ <# .SYNOPSIS -Upgrades libczicompressc.nupkg in CziShrink with the latest cmake build of main of https://github.com/ZEISS/czicompress. +Upgrades libczicompressc.nupkg in CziShrink with the latest cmake build of the main branch of czicompress. .DESCRIPTION -This script should be run in the czishrink directory of a clone of https://github.com/m-ringler/netczicompress. -Make sure that the working directory is clean. Otherwise the script will stash local changes. +This script should be run in the czishrink directory of a clone of https://github.com/ZEISS/czicompress. +Make sure that you do not have any uncommited changes before you run the script. Otherwise the script will stash such changes. You must have git and dotnet on your path. -The script needs to have access to both the https://github.com/ZEISS/czicompress and the https://github.com/m-ringler/netczicompress github repo, +The script needs to have access to the https://github.com/ZEISS/czicompress github repo (or your fork of that repo), you need to * create a 'classic' personal access token at https://github.com/settings/tokens or with github cli, * and authorize it for the ZEISS organization via the "Configure SSO" button, * and store it in a GITHUB_TOKEN environment variable or pass it as the -GithubToken parameter to this script. - -If you specify a -DownloadFolder the script will use the data downloaded to that folder in a previous run. This provides rudimentary resume-on-error functionality. + +If you specify a -DownloadFolder the script will reuse data downloaded to that folder in a previous run. This provides rudimentary resume-on-error functionality. .EXAMPLE @@ -186,7 +186,7 @@ if (![string]::IsNullOrEmpty($PSScriptRoot)) { { Set-Location "libczicompressc" } elseif ("libczicompress" -ne $current_dir_name) { - throw "Please CD to the /czishrink directory of a netczicompress clone." + throw "Please CD to the /czishrink directory of a czicompress clone." } } @@ -360,4 +360,4 @@ if ($CreatePullRequest) { $PullRequestUrl = ($ServerResponse | ConvertFrom-Json).html_url Write-Output "Opened a PR at $PullRequestUrl" Start-Process $PullRequestUrl -} \ No newline at end of file +} From 9d8f3d9b3183b86d1e2657057bd6bb6f543c0091 Mon Sep 17 00:00:00 2001 From: m-ringler <42344272+m-ringler@users.noreply.github.com> Date: Tue, 7 Nov 2023 21:59:44 +0100 Subject: [PATCH 18/31] Update upgrade-libczicompressc.ps1 --- czishrink/upgrade-libczicompressc.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/czishrink/upgrade-libczicompressc.ps1 b/czishrink/upgrade-libczicompressc.ps1 index 85c9703..09da8d8 100644 --- a/czishrink/upgrade-libczicompressc.ps1 +++ b/czishrink/upgrade-libczicompressc.ps1 @@ -15,7 +15,7 @@ you need to * create a 'classic' personal access token at https://github.com/settings/tokens or with github cli, * and authorize it for the ZEISS organization via the "Configure SSO" button, * and store it in a GITHUB_TOKEN environment variable or pass it as the -GithubToken parameter to this script. - + If you specify a -DownloadFolder the script will reuse data downloaded to that folder in a previous run. This provides rudimentary resume-on-error functionality. .EXAMPLE From 968743bb033e228d12905a549d74fc7198f2554d Mon Sep 17 00:00:00 2001 From: m-ringler <42344272+m-ringler@users.noreply.github.com> Date: Tue, 7 Nov 2023 22:09:14 +0100 Subject: [PATCH 19/31] Update README.md --- czishrink/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/czishrink/README.md b/czishrink/README.md index 7ceeaea..60d6f1d 100644 --- a/czishrink/README.md +++ b/czishrink/README.md @@ -136,7 +136,7 @@ There is no guarantee that any items on this list will be added, rather it is ju 2. Open a Powershell terminal and run [./upgrade-libczicompressc.ps1](./upgrade-libczicompressc.ps1). Run `get-help ./upgrade-libczicompressc.ps1` for more info. ## How to upgrade libczicompressc manually -1. Build libczicompressc.so on linux-x64 and libczicompressc.dll on win-x64 in release mode, or (preferred) get them from the [github CI build](https://github.com/zeissmicroscopy/czicompress/actions/workflows/cmake.yml). +1. Build libczicompressc.so on linux-x64 and libczicompressc.dll on win-x64 in release mode, or (preferred) get them from the [github CI build](https://github.com/zeissmicroscopy/czicompress/actions/workflows/czicompress_cmake.yml). 1. Put the binaries into [libczicompressc/runtimes/linux-x64/native](libczicompressc/runtimes/linux-x64/native) and [libczicompressc/runtimes/win-x64/native](libczicompressc/runtimes/win-x64/native) 1. Update the nuspec file [libczicompressc/libczicompressc.nuspec](libczicompressc/libczicompressc.nuspec): * `package/metadata/version` must be the 'ProductVersion' of libczicompressc.dll (explorer: Properties/Details) @@ -145,7 +145,7 @@ There is no guarantee that any items on this list will be added, rather it is ju 1. Open a shell in [libczicompressc](libczicompressc), and run `path/to/nuget pack libczicompressc.nuspec` 1. Move the resulting nupkg into [packages_local](packages_local) and delete the old package from there. 1. Change the version of libczicompressc in [Directory.Packages.props](Directory.Packages.props) -1. If major or minor version has changed, change the expected version number in [PInvokeFileProcessor](https://github.com/m-ringler/netczicompress/blob/a254a4a919120ad61aad707f39749709f0a36e1a/netczicompress/Models/PInvokeFileProcessor.cs#L124). +1. If major or minor version has changed, change the expected version number in [PInvokeFileProcessor](netczicompress/Models/PInvokeFileProcessor.cs). 1. Rebuild netczicompress.sln 1. Run netczicompressTests 1. Undo git changes to the libczicompressc.dll and libczicompressc.so files (no need to commit them, they are in the nupkg). From 968436f288da18d6f975dc8c6c83841975d3ef22 Mon Sep 17 00:00:00 2001 From: m-ringler <42344272+m-ringler@users.noreply.github.com> Date: Tue, 7 Nov 2023 22:09:46 +0100 Subject: [PATCH 20/31] Update README.md --- czishrink/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/czishrink/README.md b/czishrink/README.md index 60d6f1d..87dfcf1 100644 --- a/czishrink/README.md +++ b/czishrink/README.md @@ -1,5 +1,5 @@ # CziShrink -An open source and cross-platform GUI for [CziCompress](https://github.com/zeissmicroscopy/czicompress), made with Avalonia UI and .NET. +An open source and cross-platform GUI for [CziCompress](https://github.com/ZEISS/czicompress), made with Avalonia UI and .NET. ## Table of contents From 98531d404198a741b7fb8bb4556901e94e08bc74 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Wed, 8 Nov 2023 09:35:13 +0100 Subject: [PATCH 21/31] Remove path filters from github workflows This is necessary for them to be usable in branch protection rules. --- .github/workflows/czicompress_cmake.yml | 2 -- .github/workflows/czicompress_codeql.yml | 1 - .github/workflows/czishrink_codeql.yml | 2 -- .github/workflows/czishrink_dotnet.yml | 2 -- 4 files changed, 7 deletions(-) diff --git a/.github/workflows/czicompress_cmake.yml b/.github/workflows/czicompress_cmake.yml index 16c937f..7447d00 100644 --- a/.github/workflows/czicompress_cmake.yml +++ b/.github/workflows/czicompress_cmake.yml @@ -4,10 +4,8 @@ name: CMake Build (czicompress) on: push: branches: ["main"] # run only when merge with main branch - paths: ["czicompress/**"] pull_request: branches: ["main"] # run only when merge with main branch - paths: ["czicompress/**"] workflow_dispatch: {} permissions: diff --git a/.github/workflows/czicompress_codeql.yml b/.github/workflows/czicompress_codeql.yml index 6dceeb9..8b07149 100644 --- a/.github/workflows/czicompress_codeql.yml +++ b/.github/workflows/czicompress_codeql.yml @@ -5,7 +5,6 @@ on: push: branches: ["main"] pull_request: - # The branches below must be a subset of the branches above branches: ["main"] schedule: - cron: "22 16 * * 4" diff --git a/.github/workflows/czishrink_codeql.yml b/.github/workflows/czishrink_codeql.yml index 07dab0c..28ef76c 100644 --- a/.github/workflows/czishrink_codeql.yml +++ b/.github/workflows/czishrink_codeql.yml @@ -4,10 +4,8 @@ name: "CodeQL (CziShrink)" on: push: branches: [main] - paths: ["czishrink/**", ".github/workflows/czishrink_codeql.yml"] pull_request: branches: [main] - paths: ["czishrink/**", ".github/workflows/czishrink_codeql.yml"] schedule: # ┌───────────── minute (0 - 59) # │ ┌───────────── hour (0 - 23) diff --git a/.github/workflows/czishrink_dotnet.yml b/.github/workflows/czishrink_dotnet.yml index 0fd9299..98b790d 100644 --- a/.github/workflows/czishrink_dotnet.yml +++ b/.github/workflows/czishrink_dotnet.yml @@ -10,10 +10,8 @@ name: .NET Build (CziShrink) on: push: branches: ["main"] - paths: ["czishrink/**"] pull_request: branches: ["main"] - paths: ["czishrink/**"] workflow_dispatch: {} jobs: From 34cdbdfef9803c4e90823f5e79e212738c5be22c Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Wed, 8 Nov 2023 10:11:22 +0100 Subject: [PATCH 22/31] Address CodeQL warnings --- .../netczicompressTests/Models/CompressionLevelTests.cs | 7 ++----- .../Models/PInvokeFileProcessorTests.cs | 1 - .../Models/StatisticsRunObserverTests.cs | 2 +- czishrink/netczicompressTests/Models/ThreadCountTests.cs | 5 +---- .../ViewModels/CompressionTaskViewModelTests.cs | 2 +- .../ViewModels/CurrentTasksViewModelTests.cs | 7 ++++--- czishrink/netczicompressTests/ViewModels/ErrorItemTests.cs | 7 ++++--- .../ViewModels/ErrorListViewModelTests.cs | 3 ++- .../ViewModels/LogFileViewModelTests.cs | 4 ++-- .../ViewModels/MainViewModelTests.OverallStatus.cs | 3 --- .../ViewModels/MainViewModelTests.StartCommand.cs | 4 ++-- .../ViewModels/MainViewModelTests.StopCommand.cs | 1 - 12 files changed, 19 insertions(+), 27 deletions(-) diff --git a/czishrink/netczicompressTests/Models/CompressionLevelTests.cs b/czishrink/netczicompressTests/Models/CompressionLevelTests.cs index 4ff193e..1a0cd70 100644 --- a/czishrink/netczicompressTests/Models/CompressionLevelTests.cs +++ b/czishrink/netczicompressTests/Models/CompressionLevelTests.cs @@ -23,10 +23,7 @@ public void DefaultConstructor_HasDefaultValue() [InlineData(230)] public void OutOfRange_ShouldThrowOutOfRangeException(int value) { - var act = () => - { - var sut = new CompressionLevel() { Value = value }; - }; + var act = () => _ = new CompressionLevel { Value = value }; act.Should().Throw(); } @@ -37,7 +34,7 @@ public void OutOfRange_ShouldThrowOutOfRangeException(int value) [InlineData(0)] public void InRangeValue_ShouldBeEqual(int value) { - var sut = new CompressionLevel() { Value = value }; + var sut = new CompressionLevel { Value = value }; sut.Value.Should().Be(value); } } \ No newline at end of file diff --git a/czishrink/netczicompressTests/Models/PInvokeFileProcessorTests.cs b/czishrink/netczicompressTests/Models/PInvokeFileProcessorTests.cs index 2fbea63..c89cc99 100644 --- a/czishrink/netczicompressTests/Models/PInvokeFileProcessorTests.cs +++ b/czishrink/netczicompressTests/Models/PInvokeFileProcessorTests.cs @@ -143,7 +143,6 @@ public void CompressWithLevel_FilesHaveCorrectSize( var compressedSize = GetLength(compressed); var decompressedSize = GetLength(uncompressed); - var originalSize = GetLength(testFile); // ASSERT compressedSize.Should().Be(expectedCompressedSize); diff --git a/czishrink/netczicompressTests/Models/StatisticsRunObserverTests.cs b/czishrink/netczicompressTests/Models/StatisticsRunObserverTests.cs index 5488bfa..223bade 100644 --- a/czishrink/netczicompressTests/Models/StatisticsRunObserverTests.cs +++ b/czishrink/netczicompressTests/Models/StatisticsRunObserverTests.cs @@ -45,7 +45,7 @@ public void ObserveRun_WhenRunCompletes_CorrectFirstAndFinalResult(int scanTimeM new(fileMock, 10, 1000, null, "Failed"), }; - var observable = new Subject(); + using var observable = new Subject(); var watch = Stopwatch.StartNew(); var sut = new StatisticsRunObserver( diff --git a/czishrink/netczicompressTests/Models/ThreadCountTests.cs b/czishrink/netczicompressTests/Models/ThreadCountTests.cs index d9c3fbc..bb7466e 100644 --- a/czishrink/netczicompressTests/Models/ThreadCountTests.cs +++ b/czishrink/netczicompressTests/Models/ThreadCountTests.cs @@ -22,10 +22,7 @@ public void DefaultConstructor_HasDefaultValue() [InlineData(230)] public void OutOfRange_ShouldThrowOutOfRangeException(int value) { - var act = () => - { - var sut = new ThreadCount() { Value = value }; - }; + var act = () => _ = new ThreadCount { Value = value }; act.Should().Throw(); } diff --git a/czishrink/netczicompressTests/ViewModels/CompressionTaskViewModelTests.cs b/czishrink/netczicompressTests/ViewModels/CompressionTaskViewModelTests.cs index 772f406..24bb369 100644 --- a/czishrink/netczicompressTests/ViewModels/CompressionTaskViewModelTests.cs +++ b/czishrink/netczicompressTests/ViewModels/CompressionTaskViewModelTests.cs @@ -22,7 +22,7 @@ public void PublicProperties_WhenGot_HaveExpectedValues() { // ARRANGE var fileName = CreateFixture().Create(); - var progress = new BehaviorSubject(0); + using var progress = new BehaviorSubject(0); var sut = new CompressionTaskViewModel( fileName, progress); diff --git a/czishrink/netczicompressTests/ViewModels/CurrentTasksViewModelTests.cs b/czishrink/netczicompressTests/ViewModels/CurrentTasksViewModelTests.cs index aff64a8..424e005 100644 --- a/czishrink/netczicompressTests/ViewModels/CurrentTasksViewModelTests.cs +++ b/czishrink/netczicompressTests/ViewModels/CurrentTasksViewModelTests.cs @@ -44,7 +44,8 @@ public void OnNext_WhenTaskFinished_DoesNotAddTaskToList() // ARRANGE var sut = new CurrentTasksViewModel(ImmediateScheduler.Instance); - var message = new FileStarting(Mock.Of(), new BehaviorSubject(100)); + using var progress = new BehaviorSubject(100); + var message = new FileStarting(Mock.Of(), progress); // ACT sut.OnNext(message); @@ -93,7 +94,7 @@ public void WhenTaskCompletes_TaskIsRemovedFromListOnGuiScheduler() var sut = new CurrentTasksViewModel(guiScheduler); sut.OnNextAll(CreateFixture().CreateMany(5)); - var progress = new BehaviorSubject(0); + using var progress = new BehaviorSubject(0); var completingTask = new FileStarting( Mock.Of(f => f.FullName == "Foo_Bar_Baz"), progress); @@ -116,7 +117,7 @@ public void WhenTaskCompletes_TaskIsRemovedFromListOnGuiScheduler() beforeSchedulerRun.SequenceEqual(initialTasks).Should().BeTrue(); afterSchedulerRun.Should().HaveCount(10); - afterSchedulerRun.SequenceEqual(initialTasks.Where(t => t != taskViewModel)).Should().BeTrue(); + afterSchedulerRun.SequenceEqual(initialTasks.Where(t => !object.ReferenceEquals(t, taskViewModel))).Should().BeTrue(); } private static IFixture CreateFixture() diff --git a/czishrink/netczicompressTests/ViewModels/ErrorItemTests.cs b/czishrink/netczicompressTests/ViewModels/ErrorItemTests.cs index 65b026b..d0f8e18 100644 --- a/czishrink/netczicompressTests/ViewModels/ErrorItemTests.cs +++ b/czishrink/netczicompressTests/ViewModels/ErrorItemTests.cs @@ -21,10 +21,11 @@ public class ErrorItemTests [Fact] public void WritableProperties_AreInitOnly() { - foreach (var property in WritableProperties()) - { - var setMethod = property.SetMethod; + var writablePropertySetters = from p in WritableProperties() + select p.SetMethod; + foreach (var setMethod in writablePropertySetters) + { // Get the modifiers applied to the return parameter. var setMethodReturnParameterModifiers = setMethod!.ReturnParameter.GetRequiredCustomModifiers(); diff --git a/czishrink/netczicompressTests/ViewModels/ErrorListViewModelTests.cs b/czishrink/netczicompressTests/ViewModels/ErrorListViewModelTests.cs index c445b88..7a7dbec 100644 --- a/czishrink/netczicompressTests/ViewModels/ErrorListViewModelTests.cs +++ b/czishrink/netczicompressTests/ViewModels/ErrorListViewModelTests.cs @@ -414,7 +414,8 @@ private static IEnumerable GetExpectedErrorItems(IEnumerable ObserveSelectedItemChanges(ErrorListViewModel sut) { var changes = sut.ObservableForProperty(vm => vm.SelectedErrorItem); - return from change in changes select (sut.Errors.ToArray(), sut.SelectedErrorItem); + + return from change in changes select (sut.Errors.ToArray(), change.Value); } private static string ToString(NotifyCollectionChangedEventArgs e) diff --git a/czishrink/netczicompressTests/ViewModels/LogFileViewModelTests.cs b/czishrink/netczicompressTests/ViewModels/LogFileViewModelTests.cs index 4c38da5..656c063 100644 --- a/czishrink/netczicompressTests/ViewModels/LogFileViewModelTests.cs +++ b/czishrink/netczicompressTests/ViewModels/LogFileViewModelTests.cs @@ -85,7 +85,7 @@ void StoreDataPoint(string name) } // ACT - var subject = new Subject(); + using var subject = new Subject(); sut.ObserveRun(p, subject.AsObservable()); var messages = fixture.CreateMany(); subject.OnNextAll(messages); @@ -317,7 +317,7 @@ public void ShowLogFile_WhenOpeningTheLogFileFails_UpdatesCanExecute() var fixture = CreateFixture(); fixture.Inject(ImmediateScheduler.Instance); var loggingStrategyMock = fixture.Freeze>(); - var logFile = Mock.Of(x => x.Exists == true); + var logFile = Mock.Of(x => x.Exists); var sut = fixture.Create(); var p = fixture.Create(); diff --git a/czishrink/netczicompressTests/ViewModels/MainViewModelTests.OverallStatus.cs b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.OverallStatus.cs index 37d7763..ecbda7a 100644 --- a/czishrink/netczicompressTests/ViewModels/MainViewModelTests.OverallStatus.cs +++ b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.OverallStatus.cs @@ -8,7 +8,6 @@ namespace netczicompressTests.ViewModels; using System.IO.Abstractions.TestingHelpers; using System.Reactive; using System.Reactive.Threading.Tasks; -using System.Windows.Input; using AutoFixture; @@ -190,8 +189,6 @@ public async Task OverallStatus_WhenProcessing_HasExpectedValues() _ = fixture.Freeze>().WithWaitForCancellation(); var sut = fixture.Create(); - ICommand start = sut.StartCommand; - ICommand stop = sut.StopCommand; SetFolders(sut, fs); Dictionary overallStatusWhen = new(); diff --git a/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StartCommand.cs b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StartCommand.cs index f148c54..3ac05ff 100644 --- a/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StartCommand.cs +++ b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StartCommand.cs @@ -188,8 +188,8 @@ public async Task StartCommands_WhenProcessingStarts_UsesCorrectParameters(Opera It.Is( x => x.InputDir.FullName == sut.InputDirectory && x.OutputDir.FullName == sut.OutputDirectory && - x.InputDir.FileSystem == fs && - x.OutputDir.FileSystem == fs && + object.ReferenceEquals(x.InputDir.FileSystem, fs) && + object.ReferenceEquals(x.OutputDir.FileSystem, fs) && x.Recursive == recursive && x.Mode == selectedMode.Value), It.Is(t => !t.IsCancellationRequested)), diff --git a/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StopCommand.cs b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StopCommand.cs index 60860f9..de8be12 100644 --- a/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StopCommand.cs +++ b/czishrink/netczicompressTests/ViewModels/MainViewModelTests.StopCommand.cs @@ -30,7 +30,6 @@ public async Task StopCommand_WhenProcessing_CanExecuteHasExpectedValues() _ = fixture.Freeze>().WithWaitForCancellation(); var sut = fixture.Create(); - ICommand start = sut.StartCommand; ICommand stop = sut.StopCommand; SetFolders(sut, fs); From 530c8fe36f5b788e2225903d00266b008840b963 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Thu, 9 Nov 2023 09:29:03 +0100 Subject: [PATCH 23/31] Add workflow_dispatch trigger to all workflows --- .github/workflows/mega-linter.yml | 1 + .github/workflows/reuse.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/mega-linter.yml b/.github/workflows/mega-linter.yml index 9c21664..cc86c78 100644 --- a/.github/workflows/mega-linter.yml +++ b/.github/workflows/mega-linter.yml @@ -8,6 +8,7 @@ on: branches: ["main"] # run only when merge with main branch pull_request: branches: ["main"] # run only when merge with main branch + workflow_dispatch: {} concurrency: group: ${{ github.ref }}-${{ github.workflow }} diff --git a/.github/workflows/reuse.yml b/.github/workflows/reuse.yml index bf0ae35..7cc344e 100644 --- a/.github/workflows/reuse.yml +++ b/.github/workflows/reuse.yml @@ -6,6 +6,7 @@ on: branches: ["main"] pull_request: branches: ["main"] + workflow_dispatch: {} permissions: contents: read From be19476356a5bc1395cc57d7eaa90920be9bb843 Mon Sep 17 00:00:00 2001 From: m-ringler <42344272+m-ringler@users.noreply.github.com> Date: Thu, 9 Nov 2023 09:31:10 +0100 Subject: [PATCH 24/31] Update czishrink/README.md Co-authored-by: Felix Scheffler --- czishrink/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/czishrink/README.md b/czishrink/README.md index 87dfcf1..4217701 100644 --- a/czishrink/README.md +++ b/czishrink/README.md @@ -136,7 +136,7 @@ There is no guarantee that any items on this list will be added, rather it is ju 2. Open a Powershell terminal and run [./upgrade-libczicompressc.ps1](./upgrade-libczicompressc.ps1). Run `get-help ./upgrade-libczicompressc.ps1` for more info. ## How to upgrade libczicompressc manually -1. Build libczicompressc.so on linux-x64 and libczicompressc.dll on win-x64 in release mode, or (preferred) get them from the [github CI build](https://github.com/zeissmicroscopy/czicompress/actions/workflows/czicompress_cmake.yml). +1. Build libczicompressc.so on linux-x64 and libczicompressc.dll on win-x64 in release mode, or (preferred) get them from the [github CI build](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_cmake.yml). 1. Put the binaries into [libczicompressc/runtimes/linux-x64/native](libczicompressc/runtimes/linux-x64/native) and [libczicompressc/runtimes/win-x64/native](libczicompressc/runtimes/win-x64/native) 1. Update the nuspec file [libczicompressc/libczicompressc.nuspec](libczicompressc/libczicompressc.nuspec): * `package/metadata/version` must be the 'ProductVersion' of libczicompressc.dll (explorer: Properties/Details) From bcdf0ee11203015613799c111146ac863dad22fa Mon Sep 17 00:00:00 2001 From: m-ringler <42344272+m-ringler@users.noreply.github.com> Date: Thu, 9 Nov 2023 09:31:35 +0100 Subject: [PATCH 25/31] Update czicompress/README.md Co-authored-by: Felix Scheffler --- czicompress/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/czicompress/README.md b/czicompress/README.md index 712816c..46df990 100644 --- a/czicompress/README.md +++ b/czicompress/README.md @@ -36,7 +36,7 @@ The tool is based on [libczi](https://github.com/ZEISS/libczi.git). - [Disclaimer](#disclaimer) ## Download -We have not yet published any Releases. Meanwhile, you can download binaries from the artifacts of the latest [Build workflow run](https://github.com/zeissmicroscopy/czicompress/actions/workflows/cmake.yml?query=branch%3Amain). +We have not yet published any Releases. Meanwhile, you can download binaries from the artifacts of the latest [Build workflow run](https://github.com/ZEISS/czicompress/actions/workflows/czicompress_cmake.yml?query=branch%3Amain). Click on the topmost successful run and download the binaries for your platform: * czicompress-windows-64-release-msvc-package-off for Windows * czicompress-ubuntu-release-llvm-package-off for Linux From 72f8334523080acf79db02ba5654288e36d13c9e Mon Sep 17 00:00:00 2001 From: m-ringler <42344272+m-ringler@users.noreply.github.com> Date: Thu, 9 Nov 2023 09:54:36 +0100 Subject: [PATCH 26/31] Update czishrink/README.md Co-authored-by: Felix Scheffler --- czishrink/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/czishrink/README.md b/czishrink/README.md index 4217701..c25d915 100644 --- a/czishrink/README.md +++ b/czishrink/README.md @@ -51,7 +51,7 @@ The intent of the tool is to be a bulk-compression utility. | Operation | Description | |-----------|-------------| | Compress uncompressed data |Compresses only uncompresed subblocks and copies others.| -| Compress uncompressed and Zstd-compressed data|Compresses subblocks that were orginally uncompressed or compressed with zstd. | +| Compress uncompressed and Zstd-compressed data|Compresses subblocks that were originally uncompressed or compressed with zstd. | | Compress all data |Compresses all subblocks regardless of current compression method (if possible). | | Decompress all data |Decompresses all possible subblocks. | | Dry Run |Finds all CZI files in the input folder but does not create any output CZI files. Like the other operations, it creates a CSV report in the output folder.| From deaf34582cc502818dd2c0ac4c9d97de229a1da2 Mon Sep 17 00:00:00 2001 From: m-ringler <42344272+m-ringler@users.noreply.github.com> Date: Thu, 9 Nov 2023 09:55:20 +0100 Subject: [PATCH 27/31] Apply suggestions from code review Co-authored-by: Felix Scheffler --- czishrink/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/czishrink/README.md b/czishrink/README.md index c25d915..d913df3 100644 --- a/czishrink/README.md +++ b/czishrink/README.md @@ -50,7 +50,7 @@ The intent of the tool is to be a bulk-compression utility. | Operation | Description | |-----------|-------------| -| Compress uncompressed data |Compresses only uncompresed subblocks and copies others.| +| Compress uncompressed data |Compresses only uncompressed subblocks and copies others.| | Compress uncompressed and Zstd-compressed data|Compresses subblocks that were originally uncompressed or compressed with zstd. | | Compress all data |Compresses all subblocks regardless of current compression method (if possible). | | Decompress all data |Decompresses all possible subblocks. | From 7b37bb9250aac3f3544f1180ae8ae42bb927d861 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Thu, 9 Nov 2023 10:10:38 +0100 Subject: [PATCH 28/31] One .gitignore and one .gitattributes --- czishrink/.gitattributes => .gitattributes | 2 + czishrink/.gitignore => .gitignore | 147 +++++++++------------ czishrink/.images/.gitattributes | 2 - 3 files changed, 64 insertions(+), 87 deletions(-) rename czishrink/.gitattributes => .gitattributes (50%) rename czishrink/.gitignore => .gitignore (83%) delete mode 100644 czishrink/.images/.gitattributes diff --git a/czishrink/.gitattributes b/.gitattributes similarity index 50% rename from czishrink/.gitattributes rename to .gitattributes index 9f77107..8caee0f 100644 --- a/czishrink/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ +*.gif filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text *.nupkg filter=lfs diff=lfs merge=lfs -text *.pdf filter=lfs diff=lfs merge=lfs -text diff --git a/czishrink/.gitignore b/.gitignore similarity index 83% rename from czishrink/.gitignore rename to .gitignore index 5777b2a..ef0b4bf 100644 --- a/czishrink/.gitignore +++ b/.gitignore @@ -1,7 +1,39 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +out/ +build/ + +megalinter-reports/ + +#################################### +### BEGIN VisualStudio.gitignore ### +#################################### ## 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 +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore # User-specific files *.rsuser @@ -43,7 +75,6 @@ Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -coverage/ # NUnit *.VisualState.xml @@ -63,9 +94,6 @@ project.lock.json project.fragment.lock.json artifacts/ -# Tye -.tye/ - # ASP.NET Scaffolding ScaffoldingReadMe.txt @@ -94,6 +122,7 @@ StyleCopReport.xml *.tmp_proj *_wpftmp.csproj *.log +*.tlog *.vspscc *.vssscc .builds @@ -196,9 +225,7 @@ publish/ PublishScripts/ # NuGet Packages -# *.nupkg -!packages_local/libczicompress.*.*.*.nupkg - +*.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore @@ -299,6 +326,17 @@ node_modules/ # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -355,6 +393,9 @@ ASALocalRun/ # Local History for Visual Studio .localhistory/ +# Visual Studio History (VSHistory) files +.vshistory/ + # BeatPulse healthcheck temp database healthchecksdb @@ -367,91 +408,27 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd -## -## Visual studio for Mac -## - - -# globs -Makefile.in -*.userprefs -*.usertasks -config.make -config.status -aclocal.m4 -install-sh -autom4te.cache/ -*.tar.gz -tarballs/ -test-results/ - -# Mac bundle stuff -*.dmg -*.app - -# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace -# Recycle Bin used on file shares -$RECYCLE.BIN/ +# Local History for Visual Studio Code +.history/ -# Windows Installer files +# Windows Installer files from build outputs *.cab *.msi *.msix *.msm *.msp -# Windows shortcuts -*.lnk - # JetBrains Rider -.idea/ *.sln.iml -## -## Visual Studio Code -## -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json +################################## +### END VisualStudio.gitignore ### +################################## \ No newline at end of file diff --git a/czishrink/.images/.gitattributes b/czishrink/.images/.gitattributes deleted file mode 100644 index 5f9b1ec..0000000 --- a/czishrink/.images/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -*.gif filter=lfs diff=lfs merge=lfs -text -*.png filter=lfs diff=lfs merge=lfs -text From 42a03d1e1b09b978ed38bc6713bcabec12f46fca Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Thu, 9 Nov 2023 10:48:57 +0100 Subject: [PATCH 29/31] Get rid of custom xpath action --- .github/workflows/czishrink_dotnet.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/czishrink_dotnet.yml b/.github/workflows/czishrink_dotnet.yml index 98b790d..67f43d7 100644 --- a/.github/workflows/czishrink_dotnet.yml +++ b/.github/workflows/czishrink_dotnet.yml @@ -49,12 +49,13 @@ jobs: - name: Get Version from Directory.Build.props id: getversion - uses: m-ringler/get-xpath@v3 - with: - xml-file: czishrink/Directory.Build.props - xpath: 'concat(//VersionPrefix/text(), "-", //VersionSuffix/text())' + run: | + $xml = [xml](Get-Content -Path "Directory.Build.props") + $version = $xml.SelectSingleNode('//VersionPrefix').'#text' + "-" + $xml.SelectSingleNode('//VersionSuffix').'#text' + "version=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + shell: pwsh - - name: Add build ID to version + - name: Add build ID to version in Directory.Build.props run: | Write-Output "Add build ID ${{ github.run_id }} to VersionSuffix in Directory.Build.props" $file = Get-Item "Directory.Build.props" @@ -130,5 +131,5 @@ jobs: uses: actions/upload-artifact@v3 if: github.event_name == 'push' with: - name: CziShrink_${{ steps.getversion.outputs.info }}_${{ matrix.config.osfamily}}-x64 + name: CziShrink_${{ steps.getversion.outputs.version }}_${{ matrix.config.osfamily}}-x64 path: publish From 87143a013d1434358ad5b6ce0114585d252ce035 Mon Sep 17 00:00:00 2001 From: "Ringler, Moritz" Date: Thu, 9 Nov 2023 11:18:22 +0100 Subject: [PATCH 30/31] Fix REUSE --- .github/workflows/czicompress_codeql.yml | 1 + .reuse/dep5 | 4 +-- czicompress/.gitignore | 38 ------------------------ 3 files changed, 3 insertions(+), 40 deletions(-) delete mode 100644 czicompress/.gitignore diff --git a/.github/workflows/czicompress_codeql.yml b/.github/workflows/czicompress_codeql.yml index 8b07149..00bfad7 100644 --- a/.github/workflows/czicompress_codeql.yml +++ b/.github/workflows/czicompress_codeql.yml @@ -8,6 +8,7 @@ on: branches: ["main"] schedule: - cron: "22 16 * * 4" + workflow_dispatch: {} permissions: actions: read diff --git a/.reuse/dep5 b/.reuse/dep5 index 8da6156..793be8c 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -7,11 +7,11 @@ Files: cla_*.txt czicompress/CMakeSettings.json CODE_OF_CONDUCT.md CONTRIBUTING. Copyright: 2023 Carl Zeiss Microscopy GmbH License: MIT -Files: .github/* .gitignore czicompress/.editorconfig czicompress/.gitignore czicompress/.runsettings czicompress/CPPLINT.cfg czicompress/.clang-format +Files: .github/* .gitignore .gitattributes czicompress/.editorconfig czicompress/.runsettings czicompress/CPPLINT.cfg czicompress/.clang-format Copyright: 2023 Carl Zeiss Microscopy GmbH License: CC0-1.0 -Files: czishrink/*.axaml czishrink/.gitignore czishrink/*/.gitignore czishrink/*.json czishrink/*.nuspec czishrink/*.txt czishrink/*.ico czishrink/*.png czishrink/*.props czishrink/*.targets czishrink/*.sh czishrink/*.svg czishrink/*.csproj czishrink/*.gitattributes czishrink/*.nupkg czishrink/*.md czishrink/*.pdf czishrink/*.config czishrink/*.ruleset czishrink/*.ps1 czishrink/*.czi czishrink/*.xml czishrink/*.sln czishrink/*.manifest README.md +Files: czishrink/*.axaml czishrink/*.json czishrink/*.nuspec czishrink/*.txt czishrink/*.ico czishrink/*.png czishrink/*.props czishrink/*.targets czishrink/*.sh czishrink/*.svg czishrink/*.csproj czishrink/*.gitattributes czishrink/*.nupkg czishrink/*.md czishrink/*.pdf czishrink/*.config czishrink/*.ruleset czishrink/*.ps1 czishrink/*.czi czishrink/*.xml czishrink/*.sln czishrink/*.manifest README.md Copyright: 2023 Carl Zeiss Microscopy GmbH License: GPL-3.0-or-later diff --git a/czicompress/.gitignore b/czicompress/.gitignore deleted file mode 100644 index 930b51f..0000000 --- a/czicompress/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app - -.vs/ -out/ -build/ - -megalinter-reports/ From 0f9cbaf6ed50d3428ff5c6edd52c20bed5555df3 Mon Sep 17 00:00:00 2001 From: DaveyJonesBitPail <119518234+DaveyJonesBitPail@users.noreply.github.com> Date: Thu, 9 Nov 2023 14:21:08 +0100 Subject: [PATCH 31/31] Feature/czishrink third party artifact text (#4) * Added czishrink specific artifact distributions * Add transitive czishrink notices --- ...D_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt | 773 +++++++++++++++++- 1 file changed, 769 insertions(+), 4 deletions(-) diff --git a/czishrink/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt b/czishrink/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt index 6686074..5c2321b 100644 --- a/czishrink/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt +++ b/czishrink/THIRD_PARTY_LICENSES_ARTIFACT_DISTRIBUTION.txt @@ -1,6 +1,6 @@ ============= Free and Open Source Software Disclosure Statement ============== -For czicompress +For czishrink This product includes copyrighted software from third parties that may be distributed under different licenses than this product as given below. @@ -28,6 +28,773 @@ List of third-party software: =============================================================================== +------------------------------------------------------------------------------- +| Avalonia (used under MIT License) https://github.com/AvaloniaUI/Avalonia +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) .NET Foundation and Contributors All Rights Reserved + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +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. + +------------------------------------------------------------------------------- +| Splat (used under MIT License) https://github.com/reactiveui/splat +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) .NET Foundation and Contributors + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| System.IO.Abstractions (used under MIT License) https://github.com/TestableIO/System.IO.Abstractions +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) Tatham Oddie and Contributors + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| MicroCom (used under MIT License) https://github.com/kekekeks/MicroCom +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2021 Nikita Tsukanov + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| Markdown.Avalonia (used under MIT License) https://github.com/whistyun/Markdown.Avalonia +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2010 Bevan Arps, 2020 Whistyun + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| Icons.Avalonia (used under MIT License) https://github.com/Projektanker/Icons.Avalonia +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2020 Projektanker + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| MessageBox.Avalonia (used under MIT License) https://github.com/AvaloniaCommunity/MessageBox.Avalonia +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2019 CreateLab + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| Reactive.UI (used under MIT License) https://github.com/reactiveui/reactiveui +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) .NET Foundation and Contributors + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| Fody (used under MIT License) https://github.com/Fody/Fody +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) Simon Cropp + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| InlineIL.Fody (used under MIT License) https://github.com/ltrzesniewski/InlineIL.Fody +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2018 Lucas Trzesniewski + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| SkiaSharp (used under MIT License) https://github.com/mono/SkiaSharp +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2015-2016 Xamarin, Inc. +Copyright (c) 2017-2018 Microsoft Corporation. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| Tmds.DBus (used under MIT License) https://github.com/tmds/Tmds.DBus +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright 2006 Alp Toker +Copyright 2010 Other Contributors +Copyright 2016 Tom Deseyn + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| Newtonsoft.Json (used under MIT License) https://github.com/JamesNK/Newtonsoft.Json +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2007 James Newton-King + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + + +------------------------------------------------------------------------------- +| WPF (used under MIT License) https://github.com/dotnet/wpf +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) .NET Foundation and Contributors All Rights Reserved + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +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. + +------------------------------------------------------------------------------- +| SharpDX (used under MIT License) https://github.com/sharpdx/SharpDX +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2010-2014 SharpDX - Alexandre Mutel + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +Copyright (c) 2010-2014 SharpDX - Alexandre Mutel + +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. + +------------------------------------------------------------------------------- +| Silverlight Toolkit (used under Microsoft Public License) https://github.com/microsoftarchive/SilverlightToolkit +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Microsoft Public License (MS-PL) + +This license governs use of the accompanying software. If you use the software, +you accept this license. If you do not accept the license, do not use the software. + +Definitions The terms "reproduce," "reproduction," "derivative works," and +"distribution" have the same meaning here as under U.S. copyright law. A +"contribution" is the original software, or any additions or changes to the +software. A "contributor" is any person that distributes its contribution under +this license. "Licensed patents" are a contributor's patent claims that read +directly on its contribution. + +Grant of Rights (A) Copyright Grant- Subject to the terms of this license, +including the license conditions and limitations in section 3, each contributor +grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce +its contribution, prepare derivative works of its contribution, and distribute its +contribution or any derivative works that you create. (B) Patent Grant- Subject +to the terms of this license, including the license conditions and limitations in +section 3, each contributor grants you a non-exclusive, worldwide, royalty-free +license under its licensed patents to make, have made, use, sell, offer for sale, +import, and/or otherwise dispose of its contribution in the software or derivative +works of the contribution in the software. + +Conditions and Limitations (A) No Trademark License- This license does not grant +you rights to use any contributors' name, logo, or trademarks. (B) If you bring a +patent claim against any contributor over patents that you claim are infringed by +the software, your patent license from such contributor to the software ends +automatically. (C) If you distribute any portion of the software, you must retain +all copyright, patent, trademark, and attribution notices that are present in the +software. (D) If you distribute any portion of the software in source code form, +you may do so only under this license by including a complete copy of this license +with your distribution. If you distribute any portion of the software in compiled +or object code form, you may only do so under a license that complies with this +license. (E) The software is licensed "as-is." You bear the risk of using it. +The contributors give no express warranties, guarantees or conditions. You may +have additional consumer rights under your local laws which this license cannot +change. To the extent permitted under your local laws, the contributors exclude +the implied warranties of merchantability, fitness for a particular purpose +and non-infringement. + +------------------------------------------------------------------------------- +| wayland-protocols (used under MIT License) https://github.com/wayland-project/wayland-protocols +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright © 2008-2013 Kristian Høgsberg +Copyright © 2010-2013 Intel Corporation +Copyright © 2013 Rafael Antognolli +Copyright © 2013 Jasper St. Pierre +Copyright © 2014 Jonas Ådahl +Copyright © 2014 Jason Ekstrand +Copyright © 2014-2015 Collabora, Ltd. +Copyright © 2015 Red Hat Inc.l + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +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. + +------------------------------------------------------------------------------- +| Metsys.Bson +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2010, Karl Seguin - https://www.openmymind.net/ All rights reserved. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: * Redistributions +of source code must retain the above copyright notice, this list of conditions +and the following disclaimer. * Redistributions in binary form must reproduce +the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. +* Neither the name of the nor the names of its contributors may be used to +endorse or promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +| RichTextKit (used under Apache License, Version 2.0) https://github.com/toptensoftware/RichTextKit +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright © 2019 Topten Software. All Rights Reserved. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this product except in compliance with the License. You may obtain a copy of the +License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +------------------------------------------------------------------------------- +| Mono (used under MIT License) https://github.com/mono/mono +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| Collections.Pooled (used under MIT License) https://github.com/jtmueller/Collections.Pooled +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) Joel Mueller + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| EllipticalArc.java (used under MIT License) http://www.spaceroots.org/documents/ellipse/EllipticalArc.java +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2003-2004, Luc Maisonobe All rights reserved. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| WinUI (used under MIT License) https://github.com/microsoft/microsoft-ui-xaml +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) Microsoft Corporation. All rights reserved. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| Chromium (used under BSD 3-Clause) https://github.com/chromium/chromium +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright 2015 The Chromium Authors. All rights reserved. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +| Flutter (used under BSD 3-Clause) https://github.com/flutter/flutter +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright 2014 The Flutter Authors. All rights reserved. + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- +| Reactive Extensions (used under MIT License) https://github.com/dotnet/reactive +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) .NET Foundation and Contributors + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + +------------------------------------------------------------------------------- +| CziCompress (used under MIT License) https://github.com/ZEISS/czicompress +------------------------------------------------------------------------------- + +--- --- --- --- --- --- --- Copyright notice(s) --- --- --- --- --- --- --- --- +Copyright (c) 2023 Carl Zeiss Microscopy GmbH + +--- --- --- --- --- --- --- License text(s) --- --- --- --- --- --- --- --- --- +The MIT License (MIT) + +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. + ------------------------------------------------------------------------------- | Catch2 3.4.0 (used under Boost Software License 1.0) https://discord.gg/4CWS9zD ------------------------------------------------------------------------------- @@ -1519,6 +2286,4 @@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVE IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- - -Generated on Fri, 28 Jul 2023 15:38:21 +0200 using Black Duck 2023.4.2. \ No newline at end of file +--- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- \ No newline at end of file