diff --git a/.dockerignore b/.dockerignore index a95d5c6f..44ceb9da 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,27 +1,37 @@ -**/.mount -**/.classpath -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/azds.yaml -**/bin -**/charts -**/docker-compose* -**/Dockerfile* -**/node_modules -**/npm-debug.log -**/obj -**/secrets.dev.yaml -**/values.dev.yaml -LICENSE -README.md -auth.conf \ No newline at end of file +# .NET build artifacts +**/bin/ +**/obj/ +**/out/ +.artifacts/ +**/.artifacts/ + +# Visual Studio / VS Code +.vs/ +.vscode/ +*.user +*.suo +*.userosscache +*.sln.docstates + +# Test results +TestResults/ +**/TestResults/ + +# NuGet packages +*.nupkg +*.snupkg +packages/ + +# Git +.git/ +.gitignore +.gitattributes + +# CI/CD +.github/ +.codex + +# Other +*.log +*.tmp +*.cache diff --git a/.editorconfig b/.editorconfig index 9719e019..c218e00a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,7 +8,7 @@ # https://github.com/dotnet/runtime/blob/main/.editorconfig # https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-format -# dotnet format style --verify-no-changes --severity=info --verbosity=detailed --exclude-diagnostics=IDE0055 +# dotnet format style --verify-no-changes --severity=info --verbosity=detailed # Root config root = true @@ -22,7 +22,7 @@ insert_final_newline = true trim_trailing_whitespace = true # Markdown files -[*.{md}] +[*.md] end_of_line = crlf trim_trailing_whitespace = false @@ -39,7 +39,6 @@ indent_size = 2 # Json files [*.json] end_of_line = crlf -indent_size = 4 # Linux scripts [*.sh] @@ -54,6 +53,29 @@ end_of_line = crlf end_of_line = crlf dotnet_diagnostic.IDE0055.severity = none dotnet_analyzer_diagnostic.severity = suggestion + +# Public API surface rules: not applicable, this is a console app not a reusable library +dotnet_diagnostic.CA1002.severity = none # Do not expose List +dotnet_diagnostic.CA1024.severity = none # Use properties where appropriate +dotnet_diagnostic.CA1034.severity = none # Nested types should not be visible +dotnet_diagnostic.CA1052.severity = none # Static holder types (builder pattern uses partial classes) +dotnet_diagnostic.CA1055.severity = none # URI return values should not be strings +dotnet_diagnostic.CA1056.severity = none # URI properties should not be strings +dotnet_diagnostic.CA1062.severity = none # Validate parameters of public methods +dotnet_diagnostic.CA1515.severity = none # Consider making public types internal +dotnet_diagnostic.CA1724.severity = none # Type names should not match namespaces +dotnet_diagnostic.CA2227.severity = none # Collection properties should be read only + +# Console app context: no localization, no SynchronizationContext, intentional case usage +dotnet_diagnostic.CA1303.severity = none # Do not pass literals as localized parameters +dotnet_diagnostic.CA1307.severity = none # Specify StringComparison for clarity +dotnet_diagnostic.CA1308.severity = none # Normalize strings to uppercase (code intentionally lowercases) +dotnet_diagnostic.CA2007.severity = none # ConfigureAwait (console app has no SynchronizationContext) +dotnet_diagnostic.CA2234.severity = none # Pass System.Uri instead of string + +# False positives or inapplicable in this codebase +dotnet_diagnostic.CA1508.severity = none # Dead code (false positive with ??= pattern) +dotnet_diagnostic.CA5392.severity = none # DefaultDllImportSearchPaths (limited intentional P/Invoke, risk accepted) csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true @@ -73,7 +95,7 @@ csharp_prefer_simple_using_statement = true csharp_prefer_static_anonymous_function = true csharp_prefer_static_local_function = true csharp_prefer_system_threading_lock = true -csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_preferred_modifier_order = public,private,protected,internal,file,static,abstract,sealed,virtual,override,readonly,unsafe,volatile,async,extern,new,partial:warning csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = false csharp_space_after_cast = false @@ -204,3 +226,9 @@ dotnet_style_qualification_for_method = false dotnet_style_qualification_for_property = false dotnet_style_readonly_field = true dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# ReSharper settings +resharper_csharp_trailing_comma_in_multiline_lists = true +resharper_csharp_var_for_built_in_types = false +resharper_csharp_var_when_type_is_apparent = false +resharper_csharp_var_when_type_is_not_apparent = false diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 5433ae5e..b6863ac2 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -13,6 +13,18 @@ PlexCleaner is a .NET 10.0 CLI utility that optimizes media files for Direct Pla The tool orchestrates external media processing tools (FFmpeg, HandBrake, MkvToolNix, MediaInfo, 7-Zip) via CLI wrappers. +## Branching, Releases, and Bot Behavior + +For full rationale see [`AGENTS.md`](../AGENTS.md). Quick rules: + +- `feature → develop → main`. PRs only. +- Develop accepts **squash merges only**; main accepts **merge commits only**. Don't suggest rebase-merge — it's disabled at the repo level. +- Both branches **auto-publish on push**: develop produces NBGV prereleases (`X.Y.Z-g{sha}`) tagged `develop` on Docker Hub; main produces stable releases (`X.Y.Z`) tagged `latest`. +- Dependabot targets **both** `main` and `develop` with the same ecosystems; major NuGet bumps gate on human review, everything else auto-merges via App-token-driven merge-bot. +- Every third-party GitHub Action is pinned to a full commit SHA with a `# vX.Y.Z` comment. Don't introduce `@v6` / `@main` / `@master` floating refs. +- Don't recommend `git push --force` or `--force-with-lease`; both rulesets enforce `non_fast_forward`. +- `version.json`'s `publicReleaseRefSpec` is `^refs/heads/main$` — bumping the base `version` field is the only manual versioning action. + ## Documentation User-facing documentation is organized as follows: @@ -215,6 +227,13 @@ Parser design patterns: - Logging: Serilog with thread IDs (`Log.Information/Warning/Error`) - Exception handling: Currently uses broad `catch(Exception)` - TODO to specialize - Global usings: `GlobalUsing.cs` defines project-wide type aliases (`ConfigFileJsonSchema`, `SidecarFileJsonSchema`) +- `Directory.Build.props`: Common MSBuild properties (`TargetFramework`, `Nullable`, `ImplicitUsings`, + `AnalysisLevel`, etc.) shared across all projects live here at the solution root. Do not duplicate + these in individual `.csproj` files -- only add a property to a `.csproj` when it is project-specific + or overrides the shared default. +- `Directory.Packages.props`: All NuGet package versions are centralised here via `PackageVersion` items. + `PackageReference` elements in `.csproj` files must not include a `Version` attribute. Asset metadata + (`PrivateAssets`, `IncludeAssets`) stays in the `.csproj` `PackageReference` element. ### Naming and Structure @@ -281,15 +300,15 @@ dotnet husky run ### GitHub Actions -- **BuildGitHubRelease.yml**: Multi-runtime matrix build (win, linux, osx × x64/arm/arm64) -- **BuildDockerPush.yml**: Multi-arch Docker builds (linux/amd64, linux/arm64) -- **TestBuildPr.yml** / **TestDockerPr.yml**: PR validation +- **publish-release.yml**: Multi-runtime matrix build (win, linux, osx x x64/arm/arm64) +- **publish-periodic-docker-release.yml**: Multi-arch Docker builds (linux/amd64, linux/arm64) +- **test-pull-request.yml**: PR validation - Version info: `version.json` with Nerdbank.GitVersioning format - Branches: `main` (stable releases), `develop` (pre-releases) ### Docker -- Multi-stage builds in `Docker/Ubuntu.Rolling.Dockerfile` +- Multi-stage builds in `Docker/Dockerfile` - Base image: `ubuntu:rolling` only (no longer publishing Alpine or Debian variants) - Supported architectures: `linux/amd64`, `linux/arm64` (no longer supporting `linux/arm/v7`) - Tool installation: Ubuntu package manager (apt) @@ -440,3 +459,12 @@ Check states with `HasFlag()`, combine with `|=` - **KeepAwake.cs**: System sleep prevention - **PlexCleaner.defaults.json**: Canonical configuration reference - **.editorconfig** / **.csharpier.json**: Code style definitions + +## Git and Commit Rules + +**These rules are absolute — no exceptions:** + +- **Never make git commits.** All commits must be cryptographically signed (SSH/GPG). AI coding agents cannot produce signed commits. Stage changes with `git add` and leave `git commit` to the developer, who must run it in their own environment where signing keys are available. +- **Never force push.** Do not run `git push --force` or `git push --force-with-lease`. Force pushing rewrites shared branch history and is blocked by branch protection rules. +- **Never run destructive git commands** (`git reset --hard`, `git checkout .`, `git restore .`, `git clean -f`) without explicit developer instruction. +- **Staging is the limit.** Prepare changes and stage files; the developer handles all commits and pushes. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fbc6fce9..152efecb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,43 +1,43 @@ -# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file -version: 2 -updates: - - # main -- package-ecosystem: "nuget" - target-branch: "main" - directory: "/" - schedule: - interval: "daily" - groups: - nuget-deps: - patterns: - - "*" -- package-ecosystem: "github-actions" - target-branch: "main" - directory: "/" - schedule: - interval: "daily" - groups: - actions-deps: - patterns: - - "*" - - # develop -- package-ecosystem: "nuget" - target-branch: "develop" - directory: "/" - schedule: - interval: "daily" - groups: - nuget-deps: - patterns: - - "*" -- package-ecosystem: "github-actions" - target-branch: "develop" - directory: "/" - schedule: - interval: "daily" - groups: - actions-deps: - patterns: - - "*" +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +version: 2 +updates: + + # main + - package-ecosystem: "nuget" + target-branch: "main" + directory: "/" + schedule: + interval: "daily" + groups: + nuget-deps: + patterns: + - "*" + - package-ecosystem: "github-actions" + target-branch: "main" + directory: "/" + schedule: + interval: "daily" + groups: + actions-deps: + patterns: + - "*" + + # develop + - package-ecosystem: "nuget" + target-branch: "develop" + directory: "/" + schedule: + interval: "daily" + groups: + nuget-deps: + patterns: + - "*" + - package-ecosystem: "github-actions" + target-branch: "develop" + directory: "/" + schedule: + interval: "daily" + groups: + actions-deps: + patterns: + - "*" diff --git a/.github/workflows/BuildDockerPush.yml b/.github/workflows/BuildDockerPush.yml deleted file mode 100644 index 0a810d90..00000000 --- a/.github/workflows/BuildDockerPush.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: Build and push docker images - -on: - push: - branches: [ main, develop ] - workflow_dispatch: - schedule: - - cron: '0 2 * * MON' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - - build-docker: - name: Build docker images - uses: ./.github/workflows/BuildDockerTask.yml - secrets: inherit - with: - push: true - - tool-version-matrix: - name: Get tool versions - runs-on: ubuntu-latest - needs: build-docker - strategy: - matrix: - include: - - tag: ${{ endsWith(github.ref, 'refs/heads/main') && 'latest' || 'develop' }} - file: latest.ver - - steps: - - - name: Get image size - run: | - mkdir -p ${{ runner.temp }}/versions - touch ${{ runner.temp }}/versions/${{ matrix.file }} - echo Image: docker.io/ptr727/plexcleaner:${{ matrix.tag }} >> ${{ runner.temp }}/versions/${{ matrix.file }} - echo Size: $(docker manifest inspect -v docker.io/ptr727/plexcleaner:${{ matrix.tag }} | jq '.[] | select(.Descriptor.platform.architecture=="amd64") | [.OCIManifest.layers[].size] | add' | numfmt --to=iec) >> ${{ runner.temp }}/versions/${{ matrix.file }} - - - name: Write tool versions to file - uses: addnab/docker-run-action@v3 - with: - image: docker.io/ptr727/plexcleaner:${{ matrix.tag }} - options: --volume ${{ runner.temp }}/versions:/versions - run: | - echo OS: $(. /etc/os-release; echo $PRETTY_NAME) >> /versions/${{ matrix.file }} - echo dotNET: $(dotnet --info) >> /versions/${{ matrix.file }} - echo PlexCleaner: $(/PlexCleaner/PlexCleaner --version) >> /versions/${{ matrix.file }} - echo HandBrakeCLI: $(HandBrakeCLI --version) >> /versions/${{ matrix.file }} - echo MediaInfo: $(mediainfo --version) >> /versions/${{ matrix.file }} - echo MkvMerge: $(mkvmerge --version) >> /versions/${{ matrix.file }} - echo FfMpeg: $(ffmpeg -version) >> /versions/${{ matrix.file }} - - - name: Print versions - run: cat ${{ runner.temp }}/versions/${{ matrix.file }} - - - name: Upload version artifacts - uses: actions/upload-artifact@v7 - with: - name: versions-${{ matrix.file }} - path: ${{ runner.temp }}/versions/${{ matrix.file }} - - update-readme: - name: Create Docker README.md - runs-on: ubuntu-latest - needs: tool-version-matrix - if: ${{ endsWith(github.ref, 'refs/heads/main') }} - - steps: - - - name: Checkout - uses: actions/checkout@v6 - - - name: Download version artifacts - uses: actions/download-artifact@v8 - with: - pattern: versions-* - merge-multiple: true - path: ${{ runner.temp }}/versions - - - name: Create README.md from README.m4 - run: m4 --include=${{ runner.temp }}/versions ./Docker/README.m4 > ${{ runner.temp }}/README.md - - - name: Update Docker Hub README.md - uses: peter-evans/dockerhub-description@v5 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_PASSWORD }} - repository: ptr727/plexcleaner - short-description: ${{ github.event.repository.description }} - readme-filepath: ${{ runner.temp }}/README.md - - date-badge: - name: Create date badge - runs-on: ubuntu-latest - needs: build-docker - if: ${{ endsWith(github.ref, 'refs/heads/main') }} - - steps: - - - id: date - run: | - echo "date=$(date)" >> $GITHUB_OUTPUT - - - name: Build date badge - uses: RubbaBoy/BYOB@v1 - with: - name: lastbuild - label: "Last Build" - icon: "github" - status: ${{ steps.date.outputs.date }} - color: "blue" - github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/BuildDockerTask.yml b/.github/workflows/BuildDockerTask.yml deleted file mode 100644 index dae2f7c0..00000000 --- a/.github/workflows/BuildDockerTask.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Build docker image task - -on: - workflow_call: - inputs: - push: - required: false - type: boolean - default: false - workflow_dispatch: - -jobs: - - get-version: - name: Get version information - uses: ./.github/workflows/GetVersionTask.yml - secrets: inherit - - build-matrix: - name: Build docker matrix - runs-on: ubuntu-latest - needs: [get-version] - strategy: - matrix: - include: - - file: ./Docker/Ubuntu.Rolling.Dockerfile - platforms: linux/amd64,linux/arm64 - tags: | - docker.io/ptr727/plexcleaner:${{ endsWith(github.ref, 'refs/heads/main') && 'latest' || 'develop' }} - docker.io/ptr727/plexcleaner:${{ needs.get-version.outputs.SemVer2 }} - - steps: - - - name: Checkout - uses: actions/checkout@v6 - - - name: Setup QEMU - uses: docker/setup-qemu-action@v4 - with: - platforms: linux/amd64,linux/arm64 - - - name: Setup Buildx - uses: docker/setup-buildx-action@v4 - with: - platforms: linux/amd64,linux/arm64 - - - name: Login to Docker Hub - uses: docker/login-action@v4 - with: - registry: docker.io - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - - - name: Docker build and push - uses: docker/build-push-action@v7 - with: - context: . - push: ${{ inputs.push }} - file: ${{ matrix.file }} - tags: ${{ matrix.tags }} - platforms: ${{ matrix.platforms }} - build-args: | - LABEL_VERSION=${{ needs.get-version.outputs.SemVer2 }} - BUILD_CONFIGURATION=${{ endsWith(github.ref, 'refs/heads/main') && 'Release' || 'Debug' }} - BUILD_VERSION=${{ needs.get-version.outputs.AssemblyVersion }} - BUILD_FILE_VERSION=${{ needs.get-version.outputs.AssemblyFileVersion }} - BUILD_ASSEMBLY_VERSION=${{ needs.get-version.outputs.AssemblyFileVersion }} - BUILD_INFORMATION_VERSION=${{ needs.get-version.outputs.AssemblyInformationalVersion }} - BUILD_PACKAGE_VERSION=${{ needs.get-version.outputs.SemVer2 }} diff --git a/.github/workflows/BuildGitHubRelease.yml b/.github/workflows/BuildGitHubRelease.yml deleted file mode 100644 index dea007e3..00000000 --- a/.github/workflows/BuildGitHubRelease.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Build and publish release - -on: - push: - branches: [ main, develop ] - paths-ignore: - - 'Docker/**' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - - test-build: - name: Run build tests - uses: ./.github/workflows/TestBuildTask.yml - secrets: inherit - - get-version: - name: Get version information - uses: ./.github/workflows/GetVersionTask.yml - secrets: inherit - - build-matrix: - name: Build matrix - runs-on: ubuntu-latest - needs: [test-build, get-version] - strategy: - matrix: - runtime: [ win-x64, linux-x64, linux-musl-x64, linux-arm, linux-arm64, osx-x64, osx-arm64 ] - - steps: - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: "10" - - - name: Checkout code - uses: actions/checkout@v6 - - - name: Build project - run: >- - dotnet publish ./PlexCleaner/PlexCleaner.csproj - --runtime ${{ matrix.runtime }} - --output ${{ runner.temp }}/publish/${{ matrix.runtime }} - --configuration ${{ endsWith(github.ref, 'refs/heads/main') && 'Release' || 'Debug' }} - -property:PublishAot=false - -property:Version=${{ needs.get-version.outputs.AssemblyVersion }} - -property:FileVersion=${{ needs.get-version.outputs.AssemblyFileVersion }} - -property:AssemblyVersion=${{ needs.get-version.outputs.AssemblyVersion }} - -property:InformationalVersion=${{ needs.get-version.outputs.AssemblyInformationalVersion }} - -property:PackageVersion=${{ needs.get-version.outputs.SemVer2 }} - - - name: Upload build artifacts - uses: actions/upload-artifact@v7 - with: - name: publish-${{ matrix.runtime }} - path: ${{ runner.temp }}/publish - - github-release: - name: Create GitHub release - runs-on: ubuntu-latest - needs: [ build-matrix, get-version ] - - steps: - - - name: Download build artifacts - uses: actions/download-artifact@v8 - with: - pattern: publish-* - merge-multiple: true - path: ${{ runner.temp }}/publish - - - name: Zip build output - run: 7z a -t7z ${{ runner.temp }}/publish/PlexCleaner.7z ${{ runner.temp }}/publish/* - - - name: Create GitHub release - uses: softprops/action-gh-release@v3 - with: - generate_release_notes: true - tag_name: ${{ needs.get-version.outputs.SemVer2 }} - prerelease: ${{ !endsWith(github.ref, 'refs/heads/main') }} - files: ${{ runner.temp }}/publish/PlexCleaner.7z diff --git a/.github/workflows/DependabotAutoMerge.yml b/.github/workflows/DependabotAutoMerge.yml deleted file mode 100644 index f19dcccb..00000000 --- a/.github/workflows/DependabotAutoMerge.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Dependabot auto-merge - -on: - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - - dependabot: - name: Merge dependabot PRs - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - if: github.actor == 'dependabot[bot]' - - steps: - - - name: Get dependabot metadata - id: metadata - uses: dependabot/fetch-metadata@v3 - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" - - name: Merge dependabot PR - if: steps.metadata.outputs.update-type != 'version-update:semver-major' - run: gh pr merge --auto --merge "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/TestBuildPr.yml b/.github/workflows/TestBuildPr.yml deleted file mode 100644 index ee9d5edf..00000000 --- a/.github/workflows/TestBuildPr.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Test build PRs - -on: - pull_request: - branches: [ main, develop ] - paths-ignore: - - 'Docker/**' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - - test-build: - name: Run build tests - uses: ./.github/workflows/TestBuildTask.yml - secrets: inherit diff --git a/.github/workflows/TestBuildTask.yml b/.github/workflows/TestBuildTask.yml deleted file mode 100644 index fec28633..00000000 --- a/.github/workflows/TestBuildTask.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Test build task - -on: - workflow_call: - workflow_dispatch: - -jobs: - - test-build: - name: Run build tests - runs-on: ubuntu-latest - - steps: - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: "10" - - - name: Checkout code - uses: actions/checkout@v6 - - - name: Check code style - run: | - dotnet tool restore - dotnet husky install - dotnet husky run - - - name: Run unit tests - run: dotnet test - - - name: Build solution - run: dotnet build diff --git a/.github/workflows/TestDockerPr.yml b/.github/workflows/TestDockerPr.yml deleted file mode 100644 index 10ccb298..00000000 --- a/.github/workflows/TestDockerPr.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Test docker PRs - -on: - pull_request: - branches: [ main, develop ] - paths: - - 'Docker/**' - - '.github/workflows/TestDocker.yml' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - - test-docker: - name: Test docker images - uses: ./.github/workflows/TestDockerTask.yml - secrets: inherit diff --git a/.github/workflows/TestDockerTask.yml b/.github/workflows/TestDockerTask.yml deleted file mode 100644 index 8bc33545..00000000 --- a/.github/workflows/TestDockerTask.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Test docker task - -on: - workflow_call: - workflow_dispatch: - -jobs: - - build-docker: - name: Build docker images - uses: ./.github/workflows/BuildDockerTask.yml - secrets: inherit - with: - push: false diff --git a/.github/workflows/build-datebadge-task.yml b/.github/workflows/build-datebadge-task.yml new file mode 100644 index 00000000..e6cb9dfc --- /dev/null +++ b/.github/workflows/build-datebadge-task.yml @@ -0,0 +1,27 @@ +name: Build BYOB date badge task + +on: + workflow_call: + +jobs: + + date-badge: + name: Build BYOB date badge job + runs-on: ubuntu-latest + + steps: + + - name: Get current date step + id: date + run: echo "date=$(date)" >> $GITHUB_OUTPUT + + - name: Build BYOB date badge step + if: ${{ github.ref_name == 'main' }} + uses: RubbaBoy/BYOB@24f464284c1fd32028524b59607d417a2e36fee7 # v1.3.0 + with: + name: lastbuild + label: "Last Build" + icon: "github" + status: ${{ steps.date.outputs.date }} + color: "blue" + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build-docker-task.yml b/.github/workflows/build-docker-task.yml new file mode 100644 index 00000000..922af0fe --- /dev/null +++ b/.github/workflows/build-docker-task.yml @@ -0,0 +1,72 @@ +name: Build Docker image task + +on: + workflow_call: + inputs: + # Input to control whether to push the Docker image to Docker Hub + push: + required: false + type: boolean + default: false + +jobs: + + get-version: + name: Get version information job + uses: ./.github/workflows/get-version-task.yml + secrets: inherit + + build-docker: + name: Build Docker image job + runs-on: ubuntu-latest + needs: [get-version] + strategy: + matrix: + include: + - file: ./Docker/Dockerfile + platforms: linux/amd64,linux/arm64 + tags: | + docker.io/ptr727/plexcleaner:${{ github.ref_name == 'main' && 'latest' || 'develop' }} + docker.io/ptr727/plexcleaner:${{ needs.get-version.outputs.SemVer2 }} + + steps: + + - name: Checkout step + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup QEMU step + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 + with: + platforms: linux/amd64,linux/arm64 + + - name: Setup Buildx step + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + with: + platforms: linux/amd64,linux/arm64 + + # Always login to Docker Hub, not just on push, to benefit from + # higher rate limits with a Docker subscription for pulls and cache + - name: Login to Docker Hub step + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + - name: Docker build and push step + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + context: . + push: ${{ inputs.push }} + file: ${{ matrix.file }} + tags: ${{ matrix.tags }} + platforms: ${{ matrix.platforms }} + cache-from: type=registry,ref=docker.io/ptr727/plexcleaner:buildcache + cache-to: type=registry,ref=docker.io/ptr727/plexcleaner:buildcache,mode=max + build-args: | + LABEL_VERSION=${{ needs.get-version.outputs.SemVer2 }} + BUILD_CONFIGURATION=${{ github.ref_name == 'main' && 'Release' || 'Debug' }} + BUILD_VERSION=${{ needs.get-version.outputs.AssemblyVersion }} + BUILD_FILE_VERSION=${{ needs.get-version.outputs.AssemblyFileVersion }} + BUILD_ASSEMBLY_VERSION=${{ needs.get-version.outputs.AssemblyVersion }} + BUILD_INFORMATION_VERSION=${{ needs.get-version.outputs.AssemblyInformationalVersion }} + BUILD_PACKAGE_VERSION=${{ needs.get-version.outputs.SemVer2 }} diff --git a/.github/workflows/build-executable-task.yml b/.github/workflows/build-executable-task.yml new file mode 100644 index 00000000..d85fd93d --- /dev/null +++ b/.github/workflows/build-executable-task.yml @@ -0,0 +1,78 @@ +name: Build executable task + +on: + workflow_call: + outputs: + # Output of the uploaded artifact id + artifact-id: + value: ${{ jobs.upload-build-artifacts.outputs.artifact-id }} + +jobs: + + get-version: + name: Get version information job + uses: ./.github/workflows/get-version-task.yml + secrets: inherit + + build-executable-matrix: + name: Build executable project matrix job + runs-on: ubuntu-latest + needs: [get-version] + strategy: + matrix: + runtime: [ win-x64, linux-x64, linux-musl-x64, linux-arm, linux-arm64, osx-x64, osx-arm64 ] + + steps: + + - name: Setup .NET SDK step + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + dotnet-version: 10.x + + - name: Checkout code step + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Build executable project step + run: | + dotnet publish ./PlexCleaner/PlexCleaner.csproj \ + --runtime ${{ matrix.runtime }} \ + -property:PublishDir=${{ runner.temp }}/publish/${{ matrix.runtime }}/ \ + --configuration ${{ github.ref_name == 'main' && 'Release' || 'Debug' }} \ + -property:PublishAot=false \ + -property:Version=${{ needs.get-version.outputs.AssemblyVersion }} \ + -property:FileVersion=${{ needs.get-version.outputs.AssemblyFileVersion }} \ + -property:AssemblyVersion=${{ needs.get-version.outputs.AssemblyVersion }} \ + -property:InformationalVersion=${{ needs.get-version.outputs.AssemblyInformationalVersion }} \ + -property:PackageVersion=${{ needs.get-version.outputs.SemVer2 }} + + - name: Upload matrix build artifacts step + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: publish-${{ matrix.runtime }} + path: ${{ runner.temp }}/publish + + upload-build-artifacts: + name: Upload matrix build artifacts job + outputs: + artifact-id: ${{ steps.artifact-upload-step.outputs.artifact-id }} + runs-on: ubuntu-latest + needs: [ build-executable-matrix ] + + steps: + + - name: Download matrix build artifacts step + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: publish-* + merge-multiple: true + path: ${{ runner.temp }}/publish + + - name: Zip build output step + run: 7z a -t7z ${{ runner.temp }}/PlexCleaner.7z ${{ runner.temp }}/publish/* + + - name: Upload build artifacts step + id: artifact-upload-step + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: executable-build + path: ${{ runner.temp }}/PlexCleaner.7z diff --git a/.github/workflows/build-release-task.yml b/.github/workflows/build-release-task.yml new file mode 100644 index 00000000..e49a381d --- /dev/null +++ b/.github/workflows/build-release-task.yml @@ -0,0 +1,65 @@ +name: Build project release task + +on: + workflow_call: + inputs: + # Input to control whether to create a GitHub release + github: + required: false + type: boolean + default: false + # Input to control whether to push the docker image to Docker Hub + dockerhub: + required: false + type: boolean + default: false + +jobs: + + get-version: + name: Get version information job + uses: ./.github/workflows/get-version-task.yml + secrets: inherit + + build-executable: + name: Build executable job + uses: ./.github/workflows/build-executable-task.yml + secrets: inherit + + build-docker: + name: Build Docker job + uses: ./.github/workflows/build-docker-task.yml + secrets: inherit + with: + # Conditional push to Docker Hub + push: ${{ inputs.dockerhub }} + + github-release: + name: Publish GitHub release job + if: ${{ inputs.github }} + runs-on: ubuntu-latest + needs: [get-version, build-executable, build-docker] + + steps: + + - name: Checkout code step + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Download executable build artifacts step + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + artifact-ids: ${{ needs.build-executable.outputs.artifact-id }} + path: ./Publish + + - name: Create GitHub release step + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 + with: + tag_name: ${{ needs.get-version.outputs.SemVer2 }} + target_commitish: ${{ github.sha }} + prerelease: ${{ github.ref_name != 'main' }} + generate_release_notes: true + fail_on_unmatched_files: true + files: | + LICENSE + README.md + ./Publish/* diff --git a/.github/workflows/GetVersionTask.yml b/.github/workflows/get-version-task.yml similarity index 64% rename from .github/workflows/GetVersionTask.yml rename to .github/workflows/get-version-task.yml index 55b4ffdd..c8b522cb 100644 --- a/.github/workflows/GetVersionTask.yml +++ b/.github/workflows/get-version-task.yml @@ -1,41 +1,41 @@ -name: Get version information task - -on: - workflow_call: - outputs: - SemVer2: - value: ${{ jobs.get-version.outputs.SemVer2 }} - AssemblyVersion: - value: ${{ jobs.get-version.outputs.AssemblyVersion }} - AssemblyFileVersion: - value: ${{ jobs.get-version.outputs.AssemblyFileVersion }} - AssemblyInformationalVersion: - value: ${{ jobs.get-version.outputs.AssemblyInformationalVersion }} - workflow_dispatch: - -jobs: - - get-version: - name: Get version information - runs-on: ubuntu-latest - outputs: - SemVer2: ${{ steps.nbgv.outputs.SemVer2 }} - AssemblyVersion: ${{ steps.nbgv.outputs.AssemblyVersion }} - AssemblyFileVersion: ${{ steps.nbgv.outputs.AssemblyFileVersion }} - AssemblyInformationalVersion: ${{ steps.nbgv.outputs.AssemblyInformationalVersion }} - - steps: - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: "10" - - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Run Nerdbank.GitVersioning tool - id: nbgv - uses: dotnet/nbgv@master +name: Get version information task + +on: + workflow_call: + outputs: + # Version information outputs + SemVer2: + value: ${{ jobs.get-version.outputs.SemVer2 }} + AssemblyVersion: + value: ${{ jobs.get-version.outputs.AssemblyVersion }} + AssemblyFileVersion: + value: ${{ jobs.get-version.outputs.AssemblyFileVersion }} + AssemblyInformationalVersion: + value: ${{ jobs.get-version.outputs.AssemblyInformationalVersion }} + +jobs: + + get-version: + name: Get version information job + runs-on: ubuntu-latest + outputs: + SemVer2: ${{ steps.nbgv.outputs.SemVer2 }} + AssemblyVersion: ${{ steps.nbgv.outputs.AssemblyVersion }} + AssemblyFileVersion: ${{ steps.nbgv.outputs.AssemblyFileVersion }} + AssemblyInformationalVersion: ${{ steps.nbgv.outputs.AssemblyInformationalVersion }} + + steps: + + - name: Setup .NET SDK step + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + dotnet-version: 10.x + + - name: Checkout code step + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Run Nerdbank.GitVersioning tool step + id: nbgv + uses: dotnet/nbgv@3cf2d96c2aa00675081b59f401356ac1fb81092f # v0.5.1 diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml new file mode 100644 index 00000000..d2f3f73f --- /dev/null +++ b/.github/workflows/merge-bot-pull-request.yml @@ -0,0 +1,74 @@ +name: Merge bot pull request action + +"on": + pull_request: + types: [opened, reopened, synchronize] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + merge-dependabot: + name: Merge dependabot pull request job + runs-on: ubuntu-latest + # Restrict to dependabot PRs that originate from this repository, not a + # fork. Check the PR author rather than the event actor so maintainer + # repair commits on Dependabot branches can still auto-merge after CI + # passes. + if: >- + github.event.pull_request.user.login == 'dependabot[bot]' && + github.event.pull_request.head.repo.full_name == github.repository + permissions: + contents: write + pull-requests: write + + steps: + + - name: Generate GitHub App token step + # Use an App token (not GITHUB_TOKEN) so the resulting merge push is + # committed by the App and fires downstream workflows on develop/main. + # Pushes from GITHUB_TOKEN are blocked from triggering further workflow + # runs by GitHub's recursion guard, which would silently skip + # publish-release.yml and publish-periodic-docker-release.yml on the + # merge commit and prevent develop's auto-prerelease/Docker rebuild. + id: app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }} + private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} + + - name: Get dependabot metadata step + id: metadata + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + # Skip semver-major NuGet bumps: they often build cleanly but break + # runtime behavior, so they should land via human review. GitHub Actions + # majors auto-merge because the workflow execution itself validates them. + # + # Merge method must match the base branch's ruleset: + # develop -> squash only (linear history) + # main -> merge commits only (preserves develop ancestry) + # A mismatch fails enablePullRequestAutoMerge with + # "Merge method ... is not allowed on this repository". + - name: Merge pull request step + if: >- + (steps.metadata.outputs.package-ecosystem != 'nuget') || + (steps.metadata.outputs.update-type != 'version-update:semver-major') + run: | + set -euo pipefail + case "${{ github.event.pull_request.base.ref }}" in + develop) method=--squash ;; + main) method=--merge ;; + *) + echo "::error::Unsupported base branch: ${{ github.event.pull_request.base.ref }}" + exit 1 + ;; + esac + gh pr merge --auto "$method" "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/publish-periodic-docker-release.yml b/.github/workflows/publish-periodic-docker-release.yml new file mode 100644 index 00000000..5651069d --- /dev/null +++ b/.github/workflows/publish-periodic-docker-release.yml @@ -0,0 +1,103 @@ +name: Publish weekly Docker image to Docker Hub action + +on: + push: + branches: [ main, develop ] + workflow_dispatch: + schedule: + # Run weekly on Mondays at 02:00 UTC + - cron: '0 2 * * MON' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + build-docker: + name: Build Docker image job + uses: ./.github/workflows/build-docker-task.yml + secrets: inherit + with: + # Push to registry + push: true + + tool-version-matrix: + name: Get tool versions + runs-on: ubuntu-latest + needs: build-docker + strategy: + matrix: + include: + - tag: ${{ github.ref_name == 'main' && 'latest' || 'develop' }} + file: latest.ver + + steps: + + - name: Get image size step + run: | + mkdir -p ${{ runner.temp }}/versions + touch ${{ runner.temp }}/versions/${{ matrix.file }} + echo Image: docker.io/ptr727/plexcleaner:${{ matrix.tag }} >> ${{ runner.temp }}/versions/${{ matrix.file }} + echo Size: $(docker manifest inspect -v docker.io/ptr727/plexcleaner:${{ matrix.tag }} | jq '.[] | select(.Descriptor.platform.architecture=="amd64") | [.OCIManifest.layers[].size] | add' | numfmt --to=iec) >> ${{ runner.temp }}/versions/${{ matrix.file }} + + - name: Write tool versions to file step + uses: addnab/docker-run-action@4f65fabd2431ebc8d299f8e5a018d79a769ae185 # v3 + with: + image: docker.io/ptr727/plexcleaner:${{ matrix.tag }} + options: --volume ${{ runner.temp }}/versions:/versions + run: | + echo OS: $(. /etc/os-release; echo $PRETTY_NAME) >> /versions/${{ matrix.file }} + echo dotNET: $(dotnet --info) >> /versions/${{ matrix.file }} + echo PlexCleaner: $(/PlexCleaner/PlexCleaner --version) >> /versions/${{ matrix.file }} + echo HandBrakeCLI: $(HandBrakeCLI --version) >> /versions/${{ matrix.file }} + echo MediaInfo: $(mediainfo --version) >> /versions/${{ matrix.file }} + echo MkvMerge: $(mkvmerge --version) >> /versions/${{ matrix.file }} + echo FfMpeg: $(ffmpeg -version) >> /versions/${{ matrix.file }} + + - name: Print versions step + run: cat ${{ runner.temp }}/versions/${{ matrix.file }} + + - name: Upload version artifacts step + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: versions-${{ matrix.file }} + path: ${{ runner.temp }}/versions/${{ matrix.file }} + + update-readme: + name: Create Docker README.md job + runs-on: ubuntu-latest + needs: tool-version-matrix + if: ${{ github.ref_name == 'main' }} + + steps: + + - name: Checkout step + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Download version artifacts step + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: versions-* + merge-multiple: true + path: ${{ runner.temp }}/versions + + - name: Create README.md from README.m4 step + run: m4 --include=${{ runner.temp }}/versions ./Docker/README.m4 > ${{ runner.temp }}/README.md + + - name: Update Docker Hub README.md step + uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5.0.0 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_PASSWORD }} + repository: ptr727/plexcleaner + short-description: ${{ github.event.repository.description }} + readme-filepath: ${{ runner.temp }}/README.md + + date-badge: + name: Create BYOB date badge job + needs: [build-docker] + uses: ./.github/workflows/build-datebadge-task.yml + secrets: inherit + permissions: + contents: write diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 00000000..e9390888 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,24 @@ +name: Publish project release action + +on: + push: + branches: [ main, develop ] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + create-release: + name: Publish project release job + uses: ./.github/workflows/build-release-task.yml + secrets: inherit + permissions: + contents: write + with: + # Push to GitHub; Docker image is built (validates Dockerfile) but not pushed + # Docker Hub publishing is handled by publish-periodic-docker-release.yml + github: true + dockerhub: false diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml new file mode 100644 index 00000000..7322ef08 --- /dev/null +++ b/.github/workflows/test-pull-request.yml @@ -0,0 +1,36 @@ +name: Test pull request action + +on: + pull_request: + branches: [ main, develop ] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + test-release: + name: Test release job + uses: ./.github/workflows/test-release-task.yml + secrets: inherit + + # TODO: Workaround for GitHub Actions not supporting status checks on conditional jobs + # https://github.com/orgs/community/discussions/12395#discussioncomment-12970019 + check-workflow-status: + name: Check pull request workflow status + runs-on: ubuntu-latest + needs: + [ test-release ] + if: always() + steps: + - name: Check workflow results + run: | + exit_on_result() { + if [[ "$2" == "failure" || "$2" == "cancelled" ]]; then + echo "Job '$1' failed or was cancelled." + exit 1 + fi + } + exit_on_result "test-release" "${{ needs.test-release.result }}" diff --git a/.github/workflows/test-release-task.yml b/.github/workflows/test-release-task.yml new file mode 100644 index 00000000..6b5cfae3 --- /dev/null +++ b/.github/workflows/test-release-task.yml @@ -0,0 +1,40 @@ +name: Test release task + +on: + workflow_call: + workflow_dispatch: + +jobs: + + unit-test: + name: Run unit tests job + runs-on: ubuntu-latest + + steps: + + - name: Setup .NET SDK step + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + dotnet-version: 10.x + + - name: Checkout code step + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Check code style step + run: | + dotnet tool restore + dotnet husky install + dotnet husky run + + - name: Run unit tests step + run: dotnet test + + build-release: + name: Build release without publishing job + needs: [unit-test] + uses: ./.github/workflows/build-release-task.yml + secrets: inherit + with: + # Do not publish + github: false + dockerhub: false diff --git a/.gitignore b/.gitignore index 8ae884af..f326ab60 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ .idea .vs +.codex .DS_Store +*.log *.user diff --git a/.vscode/launch.json b/.vscode/launch.json index 079badb8..ead5d35b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,11 +11,11 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "--help", ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "console": "internalConsole", "stopAtEntry": false }, @@ -24,12 +24,12 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "process", "--help", ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "console": "internalConsole", "stopAtEntry": false }, @@ -38,11 +38,11 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "--version", ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "console": "internalConsole", "stopAtEntry": false }, @@ -51,12 +51,12 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "getversioninfo", "--settingsfile=PlexCleaner.json", ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "console": "internalConsole", "stopAtEntry": false }, @@ -65,12 +65,12 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "defaultsettings", "--settingsfile=PlexCleaner.defaults.json", ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "console": "internalConsole", "stopAtEntry": false }, @@ -79,7 +79,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "process", "--logfile=PlexCleaner_Single.log", @@ -87,7 +87,7 @@ "--resultsfile=Results_Single.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "stopAtEntry": false, "enableStepFiltering": false, "justMyCode": false @@ -97,7 +97,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "process", "--logfile=PlexCleaner_Single_Quickscan.log", @@ -106,7 +106,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -117,7 +117,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "process", "--logfile=PlexCleaner_Single_Quickscan.log", @@ -127,7 +127,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -138,7 +138,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "process", "--logfile=PlexCleaner_Parallel_TestSnippets_Quickscan.log", @@ -150,7 +150,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -161,7 +161,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "monitor", "--logfile=PlexCleaner.log", @@ -169,7 +169,7 @@ "--preprocess", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -180,7 +180,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "remux", "--logfile=PlexCleaner.log", @@ -188,7 +188,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -199,7 +199,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "reencode", "--logfile=PlexCleaner.log", @@ -207,7 +207,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -218,7 +218,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "deinterlace", "--logfile=PlexCleaner.log", @@ -226,7 +226,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -237,7 +237,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "removeclosedcaptions", "--logfile=PlexCleaner.log", @@ -245,7 +245,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -256,14 +256,14 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "removesubtitles", "--logfile=PlexCleaner.log", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -274,7 +274,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "verify", "--logfile=PlexCleaner.log", @@ -282,7 +282,7 @@ "--quickscan", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -293,13 +293,13 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "gettagmap", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -310,13 +310,13 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "getmediainfo", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -327,14 +327,14 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "testmediainfo", "--logfile=PlexCleaner.log", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -345,13 +345,13 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "gettoolinfo", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -362,12 +362,12 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "checkfornewtools", "--settingsfile=PlexCleaner.json", ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -378,13 +378,13 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "createsidecar", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -395,13 +395,13 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "updatesidecar", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -412,13 +412,13 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "getsidecarinfo", "--settingsfile=PlexCleaner.json", "--mediafiles=D:\\Test" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -429,12 +429,12 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0/PlexCleaner.dll", + "program": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug/PlexCleaner.dll", "args": [ "createschema", "--schemafile=PlexCleaner.schema.json" ], - "cwd": "${workspaceFolder}/PlexCleaner/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/PlexCleaner/debug", "externalConsole": false, "stopAtEntry": false, "enableStepFiltering": false, @@ -445,9 +445,9 @@ "type": "coreclr", "request": "launch", "preLaunchTask": ".Net Build", - "program": "${workspaceFolder}/Sandbox/bin/Debug/net10.0/Sandbox.dll", + "program": "${workspaceFolder}/.artifacts/bin/Sandbox/debug/Sandbox.dll", "args": [], - "cwd": "${workspaceFolder}/Sandbox/bin/Debug/net10.0", + "cwd": "${workspaceFolder}/.artifacts/bin/Sandbox/debug", "stopAtEntry": false, "enableStepFiltering": false, "justMyCode": false diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 957454dc..1cc35902 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,280 +1,213 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": ".Net Build", - "type": "process", - "command": "dotnet", - "args": [ - "build", - "${workspaceFolder}", - "--verbosity=diagnostic" - ], - "group": "build", - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": ".Net Format", - "type": "process", - "command": "dotnet", - "args": [ - "format", - "style", - "--verify-no-changes", - "--severity=info", - "--verbosity=detailed" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - }, - "dependsOn": [ - "CSharpier Format", - ".Net Build" - ] - }, - { - "label": "CSharpier Format", - "type": "process", - "command": "dotnet", - "args": [ - "csharpier", - "format", - "--log-level=debug", - "." - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": ".Net Tool Update", - "type": "process", - "command": "dotnet", - "args": [ - "tool", - "update", - "--all" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Husky.Net Run", - "type": "process", - "command": "dotnet", - "args": [ - "husky", - "run" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Build Ubuntu.Rolling Dockerfile", - "type": "shell", - "command": "docker", - "args": [ - "buildx", - "build", - "--platform=linux/amd64,linux/arm64", - "--file=./Docker/Ubuntu.Rolling.Dockerfile", - "${workspaceFolder}" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Build Ubuntu.Devel Dockerfile", - "type": "shell", - "command": "docker", - "args": [ - "buildx", - "build", - "--platform=linux/amd64,linux/arm64", - "--file=./Docker/Ubuntu.Devel.Dockerfile", - "${workspaceFolder}" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Build all Dockerfiles", - "dependsOrder": "parallel", - "dependsOn": [ - "Build Ubuntu.Rolling Dockerfile", - ], - "problemMatcher": [], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Load Ubuntu.Rolling Dockerfile", - "type": "shell", - "command": "docker", - "args": [ - "buildx", - "build", - "--load", - "--platform=linux/amd64", - "--tag=plexcleaner:ubuntu", - "--file=./Docker/Ubuntu.Rolling.Dockerfile", - "${workspaceFolder}" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Load Ubuntu.Devel Dockerfile", - "type": "shell", - "command": "docker", - "args": [ - "buildx", - "build", - "--load", - "--platform=linux/amd64", - "--tag=plexcleaner:ubuntu-devel", - "--file=./Docker/Ubuntu.Devel.Dockerfile", - "${workspaceFolder}" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Load all Dockerfiles", - "dependsOrder": "parallel", - "dependsOn": [ - "Load Ubuntu.Rolling Dockerfile", - ], - "problemMatcher": [], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Test Ubuntu.Rolling Dockerfile", - "type": "shell", - "command": "docker", - "args": [ - "run", - "-it", - "--rm", - "--name=PlexCleaner-Test-Ubuntu.Rolling", - "plexcleaner:ubuntu", - "/Test/Test.sh" - ], - "dependsOrder": "sequence", - "dependsOn": [ - "Load Ubuntu.Rolling Dockerfile" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Test Ubuntu.Devel Dockerfile", - "type": "shell", - "command": "docker", - "args": [ - "run", - "-it", - "--rm", - "--name=PlexCleaner-Test-Ubuntu.Devel", - "plexcleaner:ubuntu-devel", - "/Test/Test.sh" - ], - "dependsOrder": "sequence", - "dependsOn": [ - "Load Ubuntu.Devel Dockerfile" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": "Test all Dockerfiles", - "dependsOrder": "parallel", - "dependsOn": [ - "Test Ubuntu.Rolling Dockerfile", - ], - "problemMatcher": [], - "presentation": { - "showReuseMessage": false, - "clear": false - } - }, - { - "label": ".NET Format", - "type": "process", - "command": "dotnet", - "args": [ - "format", - "--verify-no-changes", - "--severity=info", - "--verbosity=detailed" - ], - "problemMatcher": [ - "$msCompile" - ], - "presentation": { - "showReuseMessage": false, - "clear": true, - "panel": "dedicated" - } - }, - ] -} +{ + "version": "2.0.0", + "tasks": [ + { + "label": ".Net Build", + "type": "process", + "command": "dotnet", + "args": [ + "build", + "${workspaceFolder}", + "--verbosity=diagnostic" + ], + "group": "build", + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": ".Net Format", + "type": "process", + "command": "dotnet", + "args": [ + "format", + "style", + "--verify-no-changes", + "--severity=info", + "--verbosity=detailed" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + }, + "dependsOn": [ + "CSharpier Format", + ".Net Build" + ] + }, + { + "label": "CSharpier Format", + "type": "process", + "command": "dotnet", + "args": [ + "csharpier", + "format", + "--log-level=debug", + "." + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": ".Net Tool Update", + "type": "process", + "command": "dotnet", + "args": [ + "tool", + "update", + "--all" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Husky.Net Run", + "type": "process", + "command": "dotnet", + "args": [ + "husky", + "run" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": ".Net Outdated Upgrade", + "type": "process", + "command": "dotnet", + "args": [ + "outdated", + "--upgrade:prompt" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Build Dockerfile", + "type": "shell", + "command": "docker", + "args": [ + "buildx", + "build", + "--platform=linux/amd64,linux/arm64", + "--file=./Docker/Dockerfile", + "${workspaceFolder}" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Build all Dockerfiles", + "dependsOrder": "parallel", + "dependsOn": [ + "Build Dockerfile" + ], + "problemMatcher": [], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Load Dockerfile", + "type": "shell", + "command": "docker", + "args": [ + "buildx", + "build", + "--load", + "--platform=linux/amd64", + "--tag=plexcleaner:ubuntu", + "--file=./Docker/Dockerfile", + "${workspaceFolder}" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Load all Dockerfiles", + "dependsOrder": "parallel", + "dependsOn": [ + "Load Dockerfile" + ], + "problemMatcher": [], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Test Dockerfile", + "type": "shell", + "command": "docker", + "args": [ + "run", + "-it", + "--rm", + "--name=PlexCleaner-Test", + "plexcleaner:ubuntu", + "/Test/Test.sh" + ], + "dependsOrder": "sequence", + "dependsOn": [ + "Load Dockerfile" + ], + "problemMatcher": [ + "$msCompile" + ], + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "Test all Dockerfiles", + "dependsOrder": "parallel", + "dependsOn": [ + "Test Dockerfile" + ], + "problemMatcher": [], + "presentation": { + "showReuseMessage": false, + "clear": false + } + } + ] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..815bd456 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,118 @@ +# Instructions for AI Coding Agents + +**PlexCleaner** is a C# .NET utility that optimizes media files for Direct Play in Plex, Emby, Jellyfin, etc. + +For comprehensive coding standards and detailed conventions, refer to [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) and [`CODESTYLE.md`](./CODESTYLE.md). + +## Git and Commit Rules + +**These rules are absolute — no exceptions:** + +- **Never make git commits.** AI coding agents cannot produce cryptographically signed commits. All commits must be signed (SSH/GPG) and must be made by the developer. Stage changes with `git add` and leave the commit to the developer. +- **Never force push.** Do not run `git push --force` or `git push --force-with-lease` under any circumstances. Force pushing rewrites shared history and can cause data loss. +- **Never run destructive git commands** (`git reset --hard`, `git checkout .`, `git restore .`, `git clean -f`) without explicit developer instruction. +- **Staging is the limit.** Prepare and stage file changes; the developer runs `git commit` in their own environment where signing keys are available. + +## Branches and merging + +- Pipeline is `feature → develop → main`. Both branches are protected by branch rulesets; everything lands via PR. +- **Feature → develop PRs squash-merge** (single commit on develop, PR title becomes the commit message; never rebase-merge). +- **Develop → main PRs merge-commit** (one merge commit on main per release, develop's tip becomes a second parent and stays in main's ancestry — see [Develop → Main Promotion](#develop--main-promotion)). +- Open feature PRs against `develop`. `develop → main` is how stable releases are cut. + +Repo settings reflect this: `allow_merge_commit=true`, `allow_squash_merge=true`, `allow_rebase_merge=false`, `allow_auto_merge=true`. The `develop` ruleset enforces `allowed_merge_methods=["squash"]` and `required_linear_history`. The `main` ruleset enforces `allowed_merge_methods=["merge"]` and intentionally omits linear-history (the develop → main merge commit is non-linear by design). + +## Develop → Main Promotion + +Use the **"Create a merge commit"** option on develop → main PRs. Repo rulesets are split: PRs into `develop` are squash-only (linear history); PRs into `main` are merge-commit only. Clicking "Create a merge commit" on a develop → main PR produces a merge commit on main whose second parent is develop's tip — so develop becomes a real ancestor of main, and the *next* develop → main PR has a clean merge base (no recurring conflicts, no behind-base churn). + +Under any squash-only setup this would be a recurring pain point: each develop → main squash drops develop's ancestry and forces a per-cycle admin-bypass merge commit on develop to resync. With merge-commit on main, that resync is unnecessary — main's history shows one merge commit per release (a feature, not a defect: each promotion is visible as a single auditable node), and develop stays linear. + +## Release flow + +PlexCleaner is a "pull" project: consumers (`docker pull ptr727/plexcleaner:latest`, `docker pull ptr727/plexcleaner:develop`, GitHub Releases) track both branches. **Both `main` and `develop` auto-publish on every push** — there is no manual `workflow_dispatch` gate. + +[publish-release.yml](.github/workflows/publish-release.yml) drives both prereleases and stable releases off the same [build-release-task.yml](.github/workflows/build-release-task.yml). It triggers on `push: [main, develop]`: + +- **Push to `develop`** — automatic prerelease. Merging any PR into `develop` (feature, bug fix, dependabot) calls [get-version-task.yml](.github/workflows/get-version-task.yml) for an NBGV-computed version like `3.16.42-g1a2b3c4` (because develop does not match `publicReleaseRefSpec` in [version.json](version.json)) and creates a GitHub Release with `prerelease: true`. The Docker image is tagged `develop` by [publish-periodic-docker-release.yml](.github/workflows/publish-periodic-docker-release.yml). +- **Push to `main`** — automatic stable release. NBGV produces a clean version like `3.16.42` and creates a GitHub Release with `prerelease: false`. The Docker image is tagged `latest`. + +Branch-aware logic lives in three places: + +- [build-release-task.yml](.github/workflows/build-release-task.yml) — `prerelease: ${{ github.ref_name != 'main' }}` and `target_commitish: ${{ github.sha }}` (the latter is critical: without it, softprops creates the tag against the repo's default branch, mis-tagging develop builds onto main's tip). +- [publish-periodic-docker-release.yml](.github/workflows/publish-periodic-docker-release.yml) — `tag: ${{ github.ref_name == 'main' && 'latest' || 'develop' }}`. + +Bot-merged PRs (Dependabot) trigger the publish workflows automatically because the merge-bot uses an App token — see the merge-bot section below. + +## Dependabot + +[.github/dependabot.yml](.github/dependabot.yml) targets **both `main` and `develop`** with two ecosystems each (`nuget`, `github-actions`), grouped per ecosystem, daily. The duplication is intentional: because both branches auto-publish, develop must not drift from main's dependency baseline. A NuGet major bump landing on develop should land on main on the next promotion cycle, not weeks later. + +Major NuGet bumps are not auto-merged by [merge-bot-pull-request.yml](.github/workflows/merge-bot-pull-request.yml) — they require human review. Major GitHub Actions bumps are auto-merged because the workflow execution itself is the validation surface. + +## GitHub Actions pinning + +Every third-party action in `.github/workflows/*.yml` is pinned to a full commit SHA with a trailing comment matching the upstream release tag, e.g. `uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2`. The comment is whatever tag the action's repo actually publishes — typically `# vX.Y.Z`, but use `# v3` if upstream only publishes major-only tags (e.g. `addnab/docker-run-action`) and `# master` if the action ships only a moving branch (rare). Floating refs without a SHA (`@v6`, `@main`, `@master`) are never used. Local reusable workflows (`./.github/workflows/*.yml`) are referenced by path and don't need pinning. + +**Why:** Floating tags can be silently re-pointed by the action's owner (or by a compromised account) to malicious code; a SHA pin is immutable. Matching the comment to upstream's actual release tag (rather than fabricating one) lets dependabot rewrite both the SHA and the comment together when bumping. + +When adding a new `uses:` line, resolve the latest release's commit SHA (`gh api repos///releases/latest`) and copy its `tag_name` into the comment verbatim. Don't ship a floating tag and "pin it later". + +## Merge bot + +[merge-bot-pull-request.yml](.github/workflows/merge-bot-pull-request.yml) auto-merges Dependabot PRs. Two key design choices: + +- **Branch-aware merge method**: the script picks `--squash` for PRs targeting develop and `--merge` for PRs targeting main, matching each ruleset's `allowed_merge_methods`. An unknown base branch is a hard error. +- **App token, not GITHUB_TOKEN**: the merge step uses a token minted by `actions/create-github-app-token` from `CODEGEN_APP_CLIENT_ID` / `CODEGEN_APP_PRIVATE_KEY` secrets. Pushes authored by `GITHUB_TOKEN` are blocked from triggering downstream workflows by GitHub's recursion guard; without the App token, a Dependabot merge to develop would silently skip `publish-release.yml` and `publish-periodic-docker-release.yml`, leaving the develop Docker tag and prerelease stale. + +The App secrets (`CODEGEN_APP_CLIENT_ID`, `CODEGEN_APP_PRIVATE_KEY`) must exist in **both** secret namespaces: Settings → Secrets and variables → **Actions**, and Settings → Secrets and variables → **Dependabot**. Since Sept 2021, GitHub injects only the Dependabot-namespace secrets when a Dependabot-authored `pull_request` event fires; the regular Actions namespace is not visible to that run. Without the Dependabot duplicate the App-token step gets empty inputs and merge-bot silently fails to auto-merge. (The trigger remains `pull_request`, not `pull_request_target` — the merge-bot doesn't check out PR code, but `pull_request` plus duplicated secrets is the simpler, less-permissive setup.) + +## Key Requirements for All Projects Derived from This Template + +### Build & Quality Standards + +- **Zero Warnings Policy**: All builds must complete without errors or warnings + - Use `CSharpier Format`, `.Net Format`, and `Husky.Net Run` tasks + +- **Code Analysis**: Enable all .NET analyzers + - `true` + - `latest-all` + +### Project Configuration + +- Common MSBuild properties (`TargetFramework`, `Nullable`, `ImplicitUsings`, `AnalysisLevel`, etc.) + live in `Directory.Build.props` at the solution root. Do not duplicate these in individual `.csproj` + files — only add a property to a `.csproj` when it is project-specific or overrides the shared default. +- All NuGet package versions are centralised in `Directory.Packages.props`. `PackageReference` elements + in `.csproj` files must not include a `Version` attribute. Asset metadata (`PrivateAssets`, + `IncludeAssets`) stays in the `.csproj` `PackageReference` element. + +### Development Environment + +- Target latest .NET SDK (currently .NET 10 with C# 14) +- Support Visual Studio Code (`.code-workspace`) and Visual Studio Community (`.slnx`) +- Support Linux, Windows, and macOS with correct line endings and permissions +- Use `.editorconfig` for style enforcement + +### Project Structure + +- **PlexCleaner**: CLI application +- **PlexCleanerTests**: Unit tests using xUnit and AwesomeAssertions +- **Sandbox**: Sandbox/testing utility project +- **Docker**: Multi-platform Linux containers + +### Testing + +- Use xUnit v3 and AwesomeAssertions +- Organize tests logically in separate files +- Follow Arrange-Act-Assert pattern +- Test naming: `MethodName_Scenario_ExpectedBehavior()` + +## Authoritative References + +For detailed specifications, see: + +- [`.github/copilot-instructions.md`](./.github/copilot-instructions.md) - Complete coding conventions and style guide +- [`CODESTYLE.md`](./CODESTYLE.md) - Code style and formatting rules +- [`.editorconfig`](./.editorconfig) - Automated style enforcement +- Project task definitions - `CSharpier Format`, `.Net Build`, `.Net Format`, `.Net Outdated Upgrade`, `Husky.Net Run` diff --git a/CODESTYLE.md b/CODESTYLE.md new file mode 100644 index 00000000..96cd1da8 --- /dev/null +++ b/CODESTYLE.md @@ -0,0 +1,314 @@ +# Code Style and Formatting Rules + +## Build Requirements + +### Zero Warnings Policy + +**CRITICAL**: All builds must complete without warnings. The project enforces this through: + +1. **VS Code tasks** + - `CSharpier Format` → `.Net Build` → `.Net Format` + - `.Net Format` must pass with `--verify-no-changes` before commit + - Command: `dotnet format style --verify-no-changes --severity=info --verbosity=detailed` + +2. **Analyzer configuration** + - `latest-all` + - `true` + - Analyzer severity is `suggestion`, but all warnings must be addressed + +3. **Husky.Net pre-commit hooks** + - Automated checks run before commits + +### Build Tasks + +Available VS Code tasks (use via `run_task` tool): + +- `.Net Build`: Build with diagnostic verbosity +- `.Net Format`: Verify formatting and style (must pass) +- `CSharpier Format`: Auto-format code with CSharpier +- `.Net Tool Update`: Update dotnet tools +- `.Net Outdated Upgrade`: Upgrade outdated NuGet dependencies (interactive prompt) +- `Husky.Net Run`: Run pre-commit hooks manually + +## Tooling and Editor + +### Code Formatting and Tooling + +1. **CSharpier**: Primary code formatter + - Run before committing: `dotnet csharpier format --log-level=debug .` + +2. **dotnet format**: Style verification + - Verify no changes: `dotnet format style --verify-no-changes --severity=info --verbosity=detailed` + +3. **Husky.Net**: Git hooks for automated checks + - Installed as a local dotnet tool (via `dotnet tool restore`) + - Install Git hooks locally with `dotnet husky install` + - Pre-commit hooks run formatting and style checks + +4. **Other tools** + - `dotnet-outdated-tool`: Dependency update checks + - Nerdbank.GitVersioning: Version management + +### Editor Baseline + +1. **Required VS Code extensions**: CSharpier, markdownlint, CSpell +2. **VS Code settings**: Use the workspace settings without overrides + +### Markdown Files + +1. **Linting**: All `.md` files must be linted with the VS Code `markdownlint` extension (local only; no CI) +2. **Zero warnings**: Markdown linting must be error and warning free + +### Spelling + +1. **CSpell**: All spelling checks must be error free using the CSpell VS Code integration +2. **Accepted spellings**: Words must be correctly spelled in US or UK English +3. **Allowed exceptions**: Project-specific terms must be added to the workspace CSpell config + +## Coding Standards and Conventions + +Note: Code snippets are illustrative examples only. Replace namespaces/types to match your project. + +### C# Language Features + +1. **File-scoped namespaces** + + ```csharp + namespace PlexCleaner; + ``` + +2. **Nullable reference types**: Enabled (`enable`) + - Use nullable annotations appropriately + - Use `required` for mandatory properties + +3. **Modern C# features**: Prefer modern language constructs + - Primary constructors when appropriate + - Top-level statements for console apps + - Pattern matching over traditional checks + - Collection expressions when types loosely match + - Extension methods using `extension()` syntax + - Implicit object creation when type is apparent + - Range and index operators + +4. **Expression-bodied members**: Use for applicable members + - Methods, properties, accessors, operators, lambdas, local functions + +5. **`var` keyword**: Do NOT use `var` (always use explicit types) + + ```csharp + // Correct + int count = 42; + string name = "test"; + + // Incorrect + var count = 42; + var name = "test"; + ``` + +### Naming Conventions + +1. **Private fields**: underscore prefix with camelCase + + ```csharp + private readonly HttpClient _httpClient; + private int _counter; + ``` + +2. **Static fields**: `s_` prefix with camelCase + + ```csharp + private static int s_instanceCount; + ``` + +3. **Constants**: PascalCase + + ```csharp + private const int MaxRetries = 3; + ``` + +### Code Structure + +1. **Global usings**: Use `GlobalUsings.cs` for common namespaces + + ```csharp + global using System; + global using System.Net.Http; + global using System.Threading.Tasks; + global using Serilog; + ``` + +2. **Usings placement**: Outside namespace, sorted with `System` directives first + + ```csharp + using System.CommandLine; + using System.Runtime.CompilerServices; + using PlexCleaner; + + namespace PlexCleaner; + ``` + +3. **Braces**: Allman style + + ```csharp + public void Method() + { + if (condition) + { + // code + } + } + ``` + +4. **Indentation** + - C# files: 4 spaces + - XML/csproj files: 2 spaces + - YAML files: 2 spaces + - JSON files: 4 spaces + +5. **Line endings** + - C#, XML, YAML, JSON, Windows scripts: CRLF + - Linux scripts (`.sh`): LF + +6. **`#region`**: Do not use regions. Prefer logical file/folder/namespace organization. +7. **Member ordering (StyleCop SA1201)**: const → static readonly → static fields → instance readonly fields → instance fields → constructors → public (events → properties → indexers → methods → operators) → non-public in same order → nested types + +### Comments and Documentation + +1. **XML documentation** + - `true` + - Missing XML comments for public APIs are suppressed (`.editorconfig`) + - Must document all public surfaces. + - Single-line summaries, additional details in remarks, document input parameters, returns values, exceptions, and add crefs + + ```csharp + /// + /// Example of a single line summary. + /// + /// + /// Additional important details about usage. + /// Multiple lines if needed. + /// + /// + /// The quote category to request + /// + /// + /// A that can be used to cancel the request. + /// + /// + /// A containing the quote text. + /// + /// + /// Thrown when is not a supported value. + /// + public async Task GetQuoteOfTheDayAsync(string category, CancellationToken cancellationToken) {} + ``` + +2. **Code analysis suppressions** + - Do not use `#pragma` sections to disable analyzers + - For one-off cases, use suppression attributes with justifications + - For project-wide suppressions, add rules to `.editorconfig` + + ```csharp + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1034:Nested types should not be visible", + Justification = "https://github.com/dotnet/sdk/issues/51681" + )] + ``` + +### Error Handling and Logging + +1. **Serilog logging**: Use structured logging + + ```csharp + logger.Error(exception, "{Function}", function); + ``` + +2. **Library log configuration**: Libraries must expose logging configuration + - Provide options or settings to supply an `ILoggerFactory` and/or `ILogger` + - Offer a global fallback logger for static usage when needed + +3. **CallerMemberName**: Use for automatic function name tracking + + ```csharp + public bool LogAndPropagate( + Exception exception, + [CallerMemberName] string function = "unknown" + ) + ``` + +4. **Logger extensions**: Use `Extensions.cs` for logger and other extension methods + + ```csharp + extension(ILogger logger) + { + public bool LogAndPropagate(Exception exception, ...) { } + } + ``` + +5. **Exceptions**: Do not swallow exceptions; log and rethrow or translate to a domain-specific exception + +### Code Patterns + +1. **Guard clauses**: Prefer early returns for validation and error handling +2. **Async all the way**: Avoid blocking calls (`.Result`, `.Wait()`); use `async`/`await` +3. **Cancellation tokens**: Accept `CancellationToken` as the last parameter and pass it through +4. **ConfigureAwait**: In library code, use `ConfigureAwait(false)` unless context is required + - Do not call `ConfigureAwait(false)` in xUnit tests (see xUnit1030) +5. **Disposables**: Use `await using` for async disposables; prefer `using` declarations +6. **LINQ vs loops**: Use LINQ for clarity, loops for hot paths or allocations +7. **HTTP**: Reuse `HttpClient` via factory; avoid per-request instantiation +8. **Collections**: Prefer `IReadOnlyList`/`IReadOnlyCollection` for public APIs +9. **Immutability**: Prefer immutable records; use init-only setters when records are not suitable; prefer immutable or frozen collections for read-only data +10. **Exceptions as control flow**: Avoid using exceptions for expected flow +11. **Sealing classes**: Seal classes that are not designed for inheritance +12. **Read-only data**: Use immutable or frozen collections for read-only data sets +13. **Lazy initialization**: Use `Lazy` for static, thread-safe instantiation (e.g., logger factory, HTTP factory) + +### Testing Conventions + +1. **Framework**: xUnit with AwesomeAssertions + + ```csharp + [Fact] + public void MethodName_Scenario_ExpectedBehavior() + { + // Arrange + int expected = 42; + + // Act + int actual = GetValue(); + + // Assert + actual.Should().Be(expected); + } + ``` + +2. **Organization**: Arrange-Act-Assert pattern +3. **Naming**: Descriptive names with underscores +4. **Theory tests**: Use `[Theory]` with `[InlineData]` + +## Project Configuration + +1. **Target framework**: .NET 10.0 (`net10.0`) + +2. **AOT compatibility** + - `true` + - `true` + +3. **Assembly information** + - Use semantic versioning + - Include SourceLink: `true` + - Embed untracked sources: `true` + +4. **Internal visibility**: Use `InternalsVisibleTo` for test access + + ```xml + + + + ``` + +## Best Practices + +1. **Code reviews**: All changes go through pull requests diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..da26fe38 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,14 @@ + + + net10.0 + enable + enable + latest-all + All + true + true + $(MSBuildThisFileDirectory).artifacts + false + true + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 00000000..c8dc0620 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Docker/Build.sh b/Docker/Build.sh index 1d43903e..e3d50453 100644 --- a/Docker/Build.sh +++ b/Docker/Build.sh @@ -6,10 +6,16 @@ set -x # Exit on error set -e +# Build the solution +dotnet build ./PlexCleaner/PlexCleaner.csproj + +# Test the solution +dotnet test ./PlexCleanerTests/PlexCleanerTests.csproj + # Build release and debug builds dotnet publish ./PlexCleaner/PlexCleaner.csproj \ --arch $TARGETARCH \ - --output ./Build/Release \ + -property:PublishDir=$(pwd)/Build/Release/ \ --configuration release \ -property:PublishAot=false \ -property:Version=$BUILD_VERSION \ @@ -20,7 +26,7 @@ dotnet publish ./PlexCleaner/PlexCleaner.csproj \ dotnet publish ./PlexCleaner/PlexCleaner.csproj \ --arch $TARGETARCH \ - --output ./Build/Debug \ + -property:PublishDir=$(pwd)/Build/Debug/ \ --configuration debug \ -property:PublishAot=false \ -property:Version=$BUILD_VERSION \ @@ -29,7 +35,7 @@ dotnet publish ./PlexCleaner/PlexCleaner.csproj \ -property:InformationalVersion=$BUILD_INFORMATION_VERSION \ -property:PackageVersion=$BUILD_PACKAGE_VERSION -# Copy build output +# Copy configured build target as default output mkdir -p ./Publish/PlexCleaner/Debug mkdir -p ./Publish/PlexCleaner/Release if [ "$BUILD_CONFIGURATION" = "Debug" ] || [ "$BUILD_CONFIGURATION" = "debug" ] diff --git a/Docker/Ubuntu.Rolling.Dockerfile b/Docker/Dockerfile similarity index 88% rename from Docker/Ubuntu.Rolling.Dockerfile rename to Docker/Dockerfile index ebfb559a..0a36c7bb 100644 --- a/Docker/Ubuntu.Rolling.Dockerfile +++ b/Docker/Dockerfile @@ -15,10 +15,13 @@ # Build Dockerfile # docker buildx create --name "plexcleaner" --use -# docker buildx build --platform linux/amd64,linux/arm64 --file ./Docker/Ubuntu.Rolling.Dockerfile . +# docker buildx build --platform linux/amd64,linux/arm64 --file ./Docker/Dockerfile . + +# Build and log output +# docker buildx build --no-cache --progress=plain --platform linux/amd64 --file ./Docker/Dockerfile . 2>&1 | tee build.log # Test linux/amd64 target -# docker buildx build --load --platform linux/amd64 --tag plexcleaner:ubuntu --file ./Docker/Ubuntu.Rolling.Dockerfile . +# docker buildx build --load --platform linux/amd64 --tag plexcleaner:ubuntu --file ./Docker/Dockerfile . # docker run -it --rm --name PlexCleaner-Test plexcleaner:ubuntu /bin/bash @@ -53,16 +56,15 @@ RUN apt update \ # https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu-install RUN apt install -y --install-suggests dotnet-sdk-10.0 -# Copy source and unit tests +# Copy source COPY ./Samples/. ./Samples/. COPY ./PlexCleanerTests/. ./PlexCleanerTests/. COPY ./PlexCleaner/. ./PlexCleaner/. +COPY ./.editorconfig ./ +COPY ./Directory.Build.props ./ +COPY ./Directory.Packages.props ./ -# Unit Test -COPY --chmod=ug=rwx,o=rx ./Docker/UnitTest.sh ./ -RUN ./UnitTest.sh - -# Build +# Build project COPY --chmod=ug=rwx,o=rx ./Docker/Build.sh ./ RUN ./Build.sh diff --git a/Docker/UnitTest.sh b/Docker/UnitTest.sh deleted file mode 100644 index 8748d2aa..00000000 --- a/Docker/UnitTest.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -# Echo commands -set -x - -# Exit on error -set -e - -# Test -dotnet test ./PlexCleanerTests/PlexCleanerTests.csproj diff --git a/HISTORY.md b/HISTORY.md index 560ffb91..f3c8aa3c 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,14 @@ Utility to optimize media files for Direct Play in Plex, Emby, Jellyfin, etc. ## Release History +- Version 3.16: + - Structural changes only, no functional changes. + - Consolidated project structure, build configuration, CI/CD workflows, and Docker configuration across projects. + - Project changes: Added `Directory.Build.props` and `Directory.Packages.props`, refactored `.csproj` files, added `CODESTYLE.md` and `AGENTS.md`. + - Build changes: Enabled `TreatWarningsAsErrors`, `AnalysisLevel=latest-all`, centralized NuGet package versions. + - CI/CD changes: Renamed workflows to kebab-case, restructured into reusable task workflows, consolidated Docker test workflows. + - Docker changes: Merged build and test scripts, renamed Dockerfile. + - Editor changes: Updated `.editorconfig`, `.dockerignore`, VS Code tasks and workspace settings. - Version 3.15: - This is primarily a code refactoring release. - Updated from .NET 9 to .NET 10. diff --git a/LICENSE b/LICENSE index 1f66aab9..fccbd913 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Pieter Viljoen +Copyright (c) 2020-2026 Pieter Viljoen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/PlexCleaner.code-workspace b/PlexCleaner.code-workspace index 8eb52a44..6879b8e4 100644 --- a/PlexCleaner.code-workspace +++ b/PlexCleaner.code-workspace @@ -249,26 +249,27 @@ "editor.formatOnSave": true, "editor.defaultFormatter": "csharpier.csharpier-vscode" }, - "[json][jsonc]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, "diffEditor.ignoreTrimWhitespace": false, "editor.renderWhitespace": "boundary", "dotnet.formatting.organizeImportsOnFormat": true, "csharp.debug.symbolOptions.searchNuGetOrgSymbolServer": true, "csharp.debug.symbolOptions.searchMicrosoftSymbolServer": true, "files.encoding": "utf8", + "files.trimTrailingWhitespaceInRegexAndStrings": false, + "git.alwaysSignOff": true, "markdown.extension.toc.levels": "2..3" }, "extensions": { "recommendations": [ + "csharpier.csharpier-vscode", "davidanson.vscode-markdownlint", + "editorconfig.editorconfig", + "github.vscode-github-actions", "gruntfuggly.todo-tree", + "ms-azuretools.vscode-docker", "ms-dotnettools.csdevkit", "streetsidesoftware.code-spell-checker", - "editorconfig.editorconfig", - "csharpier.csharpier-vscode", - "github.vscode-github-actions" + "yzhang.markdown-all-in-one" ] } } diff --git a/PlexCleaner.slnx b/PlexCleaner.slnx index 8ab77210..87e6fd95 100644 --- a/PlexCleaner.slnx +++ b/PlexCleaner.slnx @@ -1,14 +1,15 @@ - - - - - - - - - - + + + + + + + + + + + @@ -21,8 +22,7 @@ - - + @@ -50,11 +50,15 @@ - + + + + + diff --git a/PlexCleaner/Bitrate.cs b/PlexCleaner/Bitrate.cs index 985254b0..6f5f6748 100644 --- a/PlexCleaner/Bitrate.cs +++ b/PlexCleaner/Bitrate.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; using InsaneGenius.Utilities; using Serilog; diff --git a/PlexCleaner/BitrateInfo.cs b/PlexCleaner/BitrateInfo.cs index 8b4010c0..bb035b39 100644 --- a/PlexCleaner/BitrateInfo.cs +++ b/PlexCleaner/BitrateInfo.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; namespace PlexCleaner; diff --git a/PlexCleaner/CommandLineOptions.cs b/PlexCleaner/CommandLineOptions.cs index 9c8ddc32..f6d65406 100644 --- a/PlexCleaner/CommandLineOptions.cs +++ b/PlexCleaner/CommandLineOptions.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Invocation; using System.CommandLine.Parsing; -using System.Linq; namespace PlexCleaner; diff --git a/PlexCleaner/ConfigFileJsonSchema.cs b/PlexCleaner/ConfigFileJsonSchema.cs index b32406bd..54d50d08 100644 --- a/PlexCleaner/ConfigFileJsonSchema.cs +++ b/PlexCleaner/ConfigFileJsonSchema.cs @@ -7,8 +7,6 @@ // Update the Upgrade() method to handle upgrading from the previous version // Update GlobalUsing.cs global using statements to the latest version -using System; -using System.IO; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Schema; diff --git a/PlexCleaner/Convert.cs b/PlexCleaner/Convert.cs index 70b7a3b2..069c8f96 100644 --- a/PlexCleaner/Convert.cs +++ b/PlexCleaner/Convert.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.IO; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/ConvertOptions.cs b/PlexCleaner/ConvertOptions.cs index 1788d63f..b2e26089 100644 --- a/PlexCleaner/ConvertOptions.cs +++ b/PlexCleaner/ConvertOptions.cs @@ -1,4 +1,3 @@ -using System; using System.Text.Json.Serialization; using Serilog; diff --git a/PlexCleaner/Extensions.cs b/PlexCleaner/Extensions.cs index 44fa5a87..bb4e1c79 100644 --- a/PlexCleaner/Extensions.cs +++ b/PlexCleaner/Extensions.cs @@ -1,4 +1,3 @@ -using System; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/FfMpegBuilder.cs b/PlexCleaner/FfMpegBuilder.cs index b95deed0..89aa80eb 100644 --- a/PlexCleaner/FfMpegBuilder.cs +++ b/PlexCleaner/FfMpegBuilder.cs @@ -1,4 +1,3 @@ -using System; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/FfMpegIdetInfo.cs b/PlexCleaner/FfMpegIdetInfo.cs index 280deac6..33f455ec 100644 --- a/PlexCleaner/FfMpegIdetInfo.cs +++ b/PlexCleaner/FfMpegIdetInfo.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics; using System.Globalization; using System.Text.RegularExpressions; diff --git a/PlexCleaner/FfMpegTool.cs b/PlexCleaner/FfMpegTool.cs index 989f8eba..4245d307 100644 --- a/PlexCleaner/FfMpegTool.cs +++ b/PlexCleaner/FfMpegTool.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; -using System.Linq; using System.Text; using System.Text.RegularExpressions; using CliWrap; diff --git a/PlexCleaner/FfMpegToolJsonSchema.cs b/PlexCleaner/FfMpegToolJsonSchema.cs index 95e494f9..1aeb8903 100644 --- a/PlexCleaner/FfMpegToolJsonSchema.cs +++ b/PlexCleaner/FfMpegToolJsonSchema.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/PlexCleaner/FfProbeBuilder.cs b/PlexCleaner/FfProbeBuilder.cs index ae8077e5..b33ee588 100644 --- a/PlexCleaner/FfProbeBuilder.cs +++ b/PlexCleaner/FfProbeBuilder.cs @@ -1,4 +1,3 @@ -using System; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/FfProbeTool.cs b/PlexCleaner/FfProbeTool.cs index b8d7e76b..3e90f354 100644 --- a/PlexCleaner/FfProbeTool.cs +++ b/PlexCleaner/FfProbeTool.cs @@ -1,14 +1,8 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.Json.Stream; -using System.Threading; -using System.Threading.Tasks; using CliWrap; using CliWrap.Buffered; using Serilog; diff --git a/PlexCleaner/HandBrakeBuilder.cs b/PlexCleaner/HandBrakeBuilder.cs index 7230fda4..30612adc 100644 --- a/PlexCleaner/HandBrakeBuilder.cs +++ b/PlexCleaner/HandBrakeBuilder.cs @@ -1,4 +1,3 @@ -using System; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/HandBrakeTool.cs b/PlexCleaner/HandBrakeTool.cs index ae62e4f8..cf6c2172 100644 --- a/PlexCleaner/HandBrakeTool.cs +++ b/PlexCleaner/HandBrakeTool.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.IO; using System.Text.RegularExpressions; using CliWrap; using CliWrap.Buffered; diff --git a/PlexCleaner/JsonSerialization.cs b/PlexCleaner/JsonSerialization.cs index 8b6ae494..fd483509 100644 --- a/PlexCleaner/JsonSerialization.cs +++ b/PlexCleaner/JsonSerialization.cs @@ -1,4 +1,3 @@ -using System; using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/PlexCleaner/KeepAwake.cs b/PlexCleaner/KeepAwake.cs index 6b4d1d6c..2f98ab73 100644 --- a/PlexCleaner/KeepAwake.cs +++ b/PlexCleaner/KeepAwake.cs @@ -1,4 +1,3 @@ -using System; using System.Runtime.InteropServices; using System.Timers; diff --git a/PlexCleaner/Language.cs b/PlexCleaner/Language.cs index 6ce4c42a..0c6989d8 100644 --- a/PlexCleaner/Language.cs +++ b/PlexCleaner/Language.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using ptr727.LanguageTags; namespace PlexCleaner; diff --git a/PlexCleaner/MediaInfoBuilder.cs b/PlexCleaner/MediaInfoBuilder.cs index 8b00b698..083b38d6 100644 --- a/PlexCleaner/MediaInfoBuilder.cs +++ b/PlexCleaner/MediaInfoBuilder.cs @@ -1,4 +1,3 @@ -using System; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/MediaInfoTool.cs b/PlexCleaner/MediaInfoTool.cs index fb02e3f9..85a2d8c2 100644 --- a/PlexCleaner/MediaInfoTool.cs +++ b/PlexCleaner/MediaInfoTool.cs @@ -1,7 +1,5 @@ -using System; using System.Diagnostics; using System.Globalization; -using System.IO; using System.Text.RegularExpressions; using CliWrap; using CliWrap.Buffered; diff --git a/PlexCleaner/MediaInfoToolJsonSchema.cs b/PlexCleaner/MediaInfoToolJsonSchema.cs index cd7eb780..87ea29c9 100644 --- a/PlexCleaner/MediaInfoToolJsonSchema.cs +++ b/PlexCleaner/MediaInfoToolJsonSchema.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/PlexCleaner/MediaInfoToolXmlSchema.cs b/PlexCleaner/MediaInfoToolXmlSchema.cs index 65c6bea5..34f1df2b 100644 --- a/PlexCleaner/MediaInfoToolXmlSchema.cs +++ b/PlexCleaner/MediaInfoToolXmlSchema.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Xml.Serialization; // https://github.com/MediaArea/MediaAreaXml/blob/master/mediainfo.xsd diff --git a/PlexCleaner/MediaInfoXmlParser.cs b/PlexCleaner/MediaInfoXmlParser.cs index 28da558c..6b64c2b6 100644 --- a/PlexCleaner/MediaInfoXmlParser.cs +++ b/PlexCleaner/MediaInfoXmlParser.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Text; using System.Text.Json; using System.Xml; diff --git a/PlexCleaner/MediaProps.cs b/PlexCleaner/MediaProps.cs index d2efc004..5c691e4e 100644 --- a/PlexCleaner/MediaProps.cs +++ b/PlexCleaner/MediaProps.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/MediaTool.cs b/PlexCleaner/MediaTool.cs index 94b0a1ce..0a25410c 100644 --- a/PlexCleaner/MediaTool.cs +++ b/PlexCleaner/MediaTool.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.IO; using System.Runtime.InteropServices; using System.Text; -using System.Threading; using CliWrap; using CliWrap.Buffered; using Serilog; diff --git a/PlexCleaner/MediaToolInfo.cs b/PlexCleaner/MediaToolInfo.cs index 0dfbe21c..9dfe4abc 100644 --- a/PlexCleaner/MediaToolInfo.cs +++ b/PlexCleaner/MediaToolInfo.cs @@ -1,4 +1,3 @@ -using System; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/MkvMergeBuilder.cs b/PlexCleaner/MkvMergeBuilder.cs index 322667ed..a532f5ef 100644 --- a/PlexCleaner/MkvMergeBuilder.cs +++ b/PlexCleaner/MkvMergeBuilder.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.Linq; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/MkvMergeTool.cs b/PlexCleaner/MkvMergeTool.cs index 01e835d9..1b2021fd 100644 --- a/PlexCleaner/MkvMergeTool.cs +++ b/PlexCleaner/MkvMergeTool.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.IO; using System.Text.RegularExpressions; using CliWrap; using CliWrap.Buffered; diff --git a/PlexCleaner/MkvPropEditBuilder.cs b/PlexCleaner/MkvPropEditBuilder.cs index 4efa4660..1205ed60 100644 --- a/PlexCleaner/MkvPropEditBuilder.cs +++ b/PlexCleaner/MkvPropEditBuilder.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/MkvPropEditTool.cs b/PlexCleaner/MkvPropEditTool.cs index f3613eac..612ea560 100644 --- a/PlexCleaner/MkvPropEditTool.cs +++ b/PlexCleaner/MkvPropEditTool.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.Linq; using CliWrap; using CliWrap.Buffered; diff --git a/PlexCleaner/MkvToolJsonSchema.cs b/PlexCleaner/MkvToolJsonSchema.cs index 67ca1803..d880cb90 100644 --- a/PlexCleaner/MkvToolJsonSchema.cs +++ b/PlexCleaner/MkvToolJsonSchema.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/PlexCleaner/Monitor.cs b/PlexCleaner/Monitor.cs index 96500cd1..49d12609 100644 --- a/PlexCleaner/Monitor.cs +++ b/PlexCleaner/Monitor.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/PlexCleaner.csproj b/PlexCleaner/PlexCleaner.csproj index 81d8a14d..d6dfe983 100644 --- a/PlexCleaner/PlexCleaner.csproj +++ b/PlexCleaner/PlexCleaner.csproj @@ -1,7 +1,6 @@ - + True - latest PlexCleaner.ico PlexCleaner 1.1.1.0 @@ -11,13 +10,9 @@ Utility to optimize media files for Direct Play in Plex, Emby, Jellyfin, etc. Linux true - true - true - true 1.1.1.0 true en - enable Exe ptr727.PlexCleaner MIT @@ -30,7 +25,6 @@ PlexCleaner PlexCleaner.Program snupkg - net10.0 1.1.1.0-prerelease @@ -44,17 +38,17 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/PlexCleaner/Process.cs b/PlexCleaner/Process.cs index ffe5ac58..bde7d927 100644 --- a/PlexCleaner/Process.cs +++ b/PlexCleaner/Process.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/ProcessDriver.cs b/PlexCleaner/ProcessDriver.cs index 01702b81..a38253b7 100644 --- a/PlexCleaner/ProcessDriver.cs +++ b/PlexCleaner/ProcessDriver.cs @@ -1,10 +1,5 @@ -using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/ProcessFile.cs b/PlexCleaner/ProcessFile.cs index 5ca65245..a619ef04 100644 --- a/PlexCleaner/ProcessFile.cs +++ b/PlexCleaner/ProcessFile.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/ProcessOptions.cs b/PlexCleaner/ProcessOptions.cs index 3ff07c9b..a141c9ee 100644 --- a/PlexCleaner/ProcessOptions.cs +++ b/PlexCleaner/ProcessOptions.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using Serilog; diff --git a/PlexCleaner/ProcessResultJsonSchema.cs b/PlexCleaner/ProcessResultJsonSchema.cs index 57591306..b7a58c4d 100644 --- a/PlexCleaner/ProcessResultJsonSchema.cs +++ b/PlexCleaner/ProcessResultJsonSchema.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; using System.ComponentModel; -using System.IO; using System.Runtime.InteropServices; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/PlexCleaner/Program.cs b/PlexCleaner/Program.cs index 6e9145bf..9b34f094 100644 --- a/PlexCleaner/Program.cs +++ b/PlexCleaner/Program.cs @@ -1,14 +1,7 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; -using System.Linq; -using System.Net.Http; using System.Net.Http.Headers; using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; using InsaneGenius.Utilities; using Serilog; using Serilog.Debugging; diff --git a/PlexCleaner/SelectMediaProps.cs b/PlexCleaner/SelectMediaProps.cs index 545ca87c..b3d56b94 100644 --- a/PlexCleaner/SelectMediaProps.cs +++ b/PlexCleaner/SelectMediaProps.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace PlexCleaner; diff --git a/PlexCleaner/SevenZipBuilder.cs b/PlexCleaner/SevenZipBuilder.cs index 6e273a3c..98d69e89 100644 --- a/PlexCleaner/SevenZipBuilder.cs +++ b/PlexCleaner/SevenZipBuilder.cs @@ -1,4 +1,3 @@ -using System; using CliWrap; using CliWrap.Builders; diff --git a/PlexCleaner/SevenZipTool.cs b/PlexCleaner/SevenZipTool.cs index 2ef2653e..76c08146 100644 --- a/PlexCleaner/SevenZipTool.cs +++ b/PlexCleaner/SevenZipTool.cs @@ -1,6 +1,4 @@ -using System; using System.Diagnostics; -using System.IO; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using CliWrap; diff --git a/PlexCleaner/SidecarFile.cs b/PlexCleaner/SidecarFile.cs index 78df1aa2..44b0f28f 100644 --- a/PlexCleaner/SidecarFile.cs +++ b/PlexCleaner/SidecarFile.cs @@ -1,7 +1,5 @@ -using System; using System.Buffers; using System.Diagnostics; -using System.IO; using System.Security.Cryptography; using InsaneGenius.Utilities; using Serilog; diff --git a/PlexCleaner/SidecarFileJsonSchema.cs b/PlexCleaner/SidecarFileJsonSchema.cs index 1855edb5..22aaae7b 100644 --- a/PlexCleaner/SidecarFileJsonSchema.cs +++ b/PlexCleaner/SidecarFileJsonSchema.cs @@ -1,7 +1,5 @@ // See ConfigFileJsonSchema.cs for schema update steps -using System; -using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using InsaneGenius.Utilities; diff --git a/PlexCleaner/SubtitleProps.cs b/PlexCleaner/SubtitleProps.cs index bdcd61e0..9f0bbbe5 100644 --- a/PlexCleaner/SubtitleProps.cs +++ b/PlexCleaner/SubtitleProps.cs @@ -1,4 +1,3 @@ -using System; using System.Globalization; using Serilog; diff --git a/PlexCleaner/TagMapSet.cs b/PlexCleaner/TagMapSet.cs index e966b57e..f300a643 100644 --- a/PlexCleaner/TagMapSet.cs +++ b/PlexCleaner/TagMapSet.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/ToolInfoJsonSchema.cs b/PlexCleaner/ToolInfoJsonSchema.cs index fbfaf694..902420a4 100644 --- a/PlexCleaner/ToolInfoJsonSchema.cs +++ b/PlexCleaner/ToolInfoJsonSchema.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using Serilog; diff --git a/PlexCleaner/Tools.cs b/PlexCleaner/Tools.cs index 7ef087b8..4e6f7202 100644 --- a/PlexCleaner/Tools.cs +++ b/PlexCleaner/Tools.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Net.Http; using System.Runtime.InteropServices; -using System.Threading.Tasks; using InsaneGenius.Utilities; using Serilog; diff --git a/PlexCleaner/TrackProps.cs b/PlexCleaner/TrackProps.cs index 6839fe8b..544a8be9 100644 --- a/PlexCleaner/TrackProps.cs +++ b/PlexCleaner/TrackProps.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.Linq; using Serilog; namespace PlexCleaner; diff --git a/PlexCleaner/VerifyOptions.cs b/PlexCleaner/VerifyOptions.cs index 0b898a8b..128d5582 100644 --- a/PlexCleaner/VerifyOptions.cs +++ b/PlexCleaner/VerifyOptions.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; diff --git a/PlexCleaner/VideoProps.cs b/PlexCleaner/VideoProps.cs index 3eddde5a..47bc98d4 100644 --- a/PlexCleaner/VideoProps.cs +++ b/PlexCleaner/VideoProps.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Serilog; // TODO: Find a better way to create profile levels diff --git a/PlexCleanerTests/CommandLineTests.cs b/PlexCleanerTests/CommandLineTests.cs index 4a215a0e..ea13e91b 100644 --- a/PlexCleanerTests/CommandLineTests.cs +++ b/PlexCleanerTests/CommandLineTests.cs @@ -4,10 +4,8 @@ namespace PlexCleanerTests; -public class CommandLineTests(PlexCleanerFixture fixture) +public class CommandLineTests { - private readonly PlexCleanerFixture _fixture = fixture; - [Theory] [InlineData( "removeclosedcaptions", diff --git a/PlexCleanerTests/FfMpegIdetInfoSerializer.cs b/PlexCleanerTests/FfMpegIdetInfoSerializer.cs index 2fa8da30..a2d32b05 100644 --- a/PlexCleanerTests/FfMpegIdetInfoSerializer.cs +++ b/PlexCleanerTests/FfMpegIdetInfoSerializer.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using PlexCleaner; @@ -10,8 +9,8 @@ public class FfMpegIdetInfoSerializer : IXunitSerializer { public bool IsSerializable( Type type, - object value, - [NotNullWhen(false)] out string failureReason + object? value, + [NotNullWhen(false)] out string? failureReason ) { if (type == typeof(FfMpegIdetInfo) && value is FfMpegIdetInfo) diff --git a/PlexCleanerTests/FfMpegIdetParsingTests.cs b/PlexCleanerTests/FfMpegIdetParsingTests.cs index a0418d19..7bc35c64 100644 --- a/PlexCleanerTests/FfMpegIdetParsingTests.cs +++ b/PlexCleanerTests/FfMpegIdetParsingTests.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; using AwesomeAssertions; @@ -11,10 +10,8 @@ namespace PlexCleanerTests; -public class FfMpegIdetParsingTests(PlexCleanerFixture fixture) +public class FfMpegIdetParsingTests { - private readonly PlexCleanerFixture _fixture = fixture; - public static TheoryData Data => new() { diff --git a/PlexCleanerTests/FileNameEscapingTests.cs b/PlexCleanerTests/FileNameEscapingTests.cs index d93daf09..26c3eee3 100644 --- a/PlexCleanerTests/FileNameEscapingTests.cs +++ b/PlexCleanerTests/FileNameEscapingTests.cs @@ -3,10 +3,8 @@ namespace PlexCleanerTests; -public class FileNameEscapingTests(PlexCleanerFixture fixture) +public class FileNameEscapingTests { - private readonly PlexCleanerFixture _fixture = fixture; - [Theory] [InlineData(@"\", @"/")] [InlineData(@":", @"\\:")] diff --git a/PlexCleanerTests/PlexCleanerFixture.cs b/PlexCleanerTests/PlexCleanerFixture.cs index 21de8476..ac55a57f 100644 --- a/PlexCleanerTests/PlexCleanerFixture.cs +++ b/PlexCleanerTests/PlexCleanerFixture.cs @@ -1,7 +1,5 @@ -using System; using System.Diagnostics; using System.Globalization; -using System.IO; using System.Reflection; using InsaneGenius.Utilities; using PlexCleaner; @@ -18,7 +16,7 @@ namespace PlexCleanerTests; // One instance for all tests in the assembly -public class PlexCleanerFixture : IDisposable +public sealed class PlexCleanerFixture : IDisposable { internal static string GetSamplesAbsoluteDirectory() { @@ -26,9 +24,9 @@ internal static string GetSamplesAbsoluteDirectory() const string samplesDirectory = "../../../../Samples/PlexCleaner"; // Get absolute path - Assembly entryAssembly = Assembly.GetEntryAssembly(); + Assembly entryAssembly = Assembly.GetEntryAssembly()!; Debug.Assert(entryAssembly != null); - string assemblyDirectory = Path.GetDirectoryName(entryAssembly.Location); + string assemblyDirectory = Path.GetDirectoryName(entryAssembly.Location)!; Debug.Assert(!string.IsNullOrEmpty(assemblyDirectory)); return Path.GetFullPath(Path.Combine(assemblyDirectory, samplesDirectory)); } @@ -74,6 +72,16 @@ public string GetSampleFilePath(string fileName) => } // One instance per test and copy of samples in temp directory +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1063:Implement IDisposable Correctly", + Justification = "Test fixture does not need the full IDisposable pattern" +)] +[System.Diagnostics.CodeAnalysis.SuppressMessage( + "Design", + "CA1031:Do not catch general exception types", + Justification = "Test fixture cleanup must not throw" +)] public class SamplesFixture : IDisposable { public SamplesFixture() @@ -118,7 +126,7 @@ string sourceFilePath in Directory.EnumerateFiles( { string relativePath = Path.GetRelativePath(sourceSamplesDirectory, sourceFilePath); string destFilePath = Path.Combine(GetSamplesDirectory, relativePath); - string destDirPath = Path.GetDirectoryName(destFilePath); + string destDirPath = Path.GetDirectoryName(destFilePath)!; if (!string.IsNullOrEmpty(destDirPath) && !Directory.Exists(destDirPath)) { diff --git a/PlexCleanerTests/PlexCleanerTests.csproj b/PlexCleanerTests/PlexCleanerTests.csproj index ad3743c0..a1a3cfcc 100644 --- a/PlexCleanerTests/PlexCleanerTests.csproj +++ b/PlexCleanerTests/PlexCleanerTests.csproj @@ -1,16 +1,17 @@ - net10.0 + true - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/PlexCleanerTests/VersionParsingTests.cs b/PlexCleanerTests/VersionParsingTests.cs index 7400cceb..4dcbc482 100644 --- a/PlexCleanerTests/VersionParsingTests.cs +++ b/PlexCleanerTests/VersionParsingTests.cs @@ -4,10 +4,8 @@ namespace PlexCleanerTests; -public class VersionParsingTests(PlexCleanerFixture fixture) +public class VersionParsingTests { - private readonly PlexCleanerFixture _fixture = fixture; - [Theory] [InlineData( "ffmpeg version 4.3.1-2020-11-19-full_build-www.gyan.dev Copyright (c) 2000-2020 the FFmpeg developers", diff --git a/PlexCleanerTests/WildcardTests.cs b/PlexCleanerTests/WildcardTests.cs index 5adc0256..86f5becb 100644 --- a/PlexCleanerTests/WildcardTests.cs +++ b/PlexCleanerTests/WildcardTests.cs @@ -3,10 +3,8 @@ namespace PlexCleanerTests; -public class WildcardTests(PlexCleanerFixture fixture) +public class WildcardTests { - private readonly PlexCleanerFixture _fixture = fixture; - [Theory] [InlineData("*.fuse_hidden*", "Foo.fuse_hidden1234", true)] [InlineData("*.fuse_hidden*", "fuse_hidden.foo", false)] diff --git a/README.md b/README.md index 0d65b4ad..4d13a6d5 100644 --- a/README.md +++ b/README.md @@ -24,20 +24,12 @@ Utility to optimize media files for Direct Play in Plex, Emby, Jellyfin, etc. ### Release Notes -**Version: 3.15**: +**Version: 3.16**: **Summary:** -- Updated from .NET 9 to .NET 10. -- Refactored code to support Nullable types and Native AOT. -- Changed MediaInfo output from XML to JSON for AOT compatibility. -- Documentation structure update. - -> **⚠️ Docker Breaking Changes:** -> -> - Only `ubuntu:rolling` images are published (Alpine and Debian discontinued). -> - Only `linux/amd64` and `linux/arm64` architectures supported (`linux/arm/v7` discontinued). -> - Update compose files: Use `docker.io/ptr727/plexcleaner:latest` (Based on `ubuntu:rolling`). +- Structural changes only, no functional changes. +- Consolidated project structure, build configuration, CI/CD workflows, and Docker configuration across projects. See [Release History](./HISTORY.md) for complete release notes and older versions. @@ -998,7 +990,7 @@ Licensed under the [MIT License][license-link]\ [docker-develop-version-shield]: https://img.shields.io/docker/v/ptr727/plexcleaner/develop?label=Docker%20Develop&logo=docker&color=orange [docker-latest-version-shield]: https://img.shields.io/docker/v/ptr727/plexcleaner/latest?label=Docker%20Latest&logo=docker [docker-link]: https://hub.docker.com/r/ptr727/plexcleaner -[docker-status-shield]: https://img.shields.io/github/actions/workflow/status/ptr727/PlexCleaner/BuildDockerPush.yml?logo=github&label=Docker%20Build +[docker-status-shield]: https://img.shields.io/github/actions/workflow/status/ptr727/PlexCleaner/publish-periodic-docker-release.yml?logo=github&label=Docker%20Build [github-link]: https://github.com/ptr727/PlexCleaner [plexcleaner-hub-link]: https://hub.docker.com/r/ptr727/plexcleaner [issues-link]: https://github.com/ptr727/PlexCleaner/issues @@ -1008,7 +1000,7 @@ Licensed under the [MIT License][license-link]\ [license-link]: ./LICENSE [license-shield]: https://img.shields.io/github/license/ptr727/PlexCleaner?label=License [pre-release-version-shield]: https://img.shields.io/github/v/release/ptr727/PlexCleaner?include_prereleases&label=GitHub%20Pre-Release&logo=github -[release-status-shield]: https://img.shields.io/github/actions/workflow/status/ptr727/PlexCleaner/BuildGitHubRelease.yml?logo=github&label=Releases%20Build +[release-status-shield]: https://img.shields.io/github/actions/workflow/status/ptr727/PlexCleaner/publish-release.yml?logo=github&label=Releases%20Build [release-version-shield]: https://img.shields.io/github/v/release/ptr727/PlexCleaner?logo=github&label=GitHub%20Release [releases-link]: https://github.com/ptr727/PlexCleaner/releases [ubuntu-hub-link]: https://hub.docker.com/_/ubuntu diff --git a/Sandbox/Program.cs b/Sandbox/Program.cs index 7f81e2b5..31946031 100644 --- a/Sandbox/Program.cs +++ b/Sandbox/Program.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Text.Json; using System.Text.Json.Serialization; -using System.Threading.Tasks; using InsaneGenius.Utilities; using PlexCleaner; using Serilog; @@ -56,11 +52,11 @@ public static async Task Main(string[] args) LogOptions.Logger = Log.Logger; // Get settings - Dictionary settings = null; + Dictionary? settings = null; if (GetSettingsFilePath(JsonConfigFile) is { } settingsPath) { await using FileStream jsonStream = File.OpenRead(settingsPath); - settings = JsonSerializer.Deserialize( + settings = await JsonSerializer.DeserializeAsync( jsonStream, ConfigJsonContext.Default.DictionaryStringJsonElement ); @@ -68,7 +64,7 @@ public static async Task Main(string[] args) } // Derive from Program and implement Sandbox() - TestSomething program = new(settings); + TestSomething program = new(settings ?? []); int ret = await program.Sandbox(args); // Done @@ -93,7 +89,7 @@ public static void SetRuntimeOptions() : 1; } - public static string GetSettingsFilePath(string fileName) + public static string? GetSettingsFilePath(string fileName) { // Load settings file from current working directory string settingsPath = Path.GetFullPath(fileName); diff --git a/Sandbox/Sandbox.csproj b/Sandbox/Sandbox.csproj index e661ff8b..e103c99e 100644 --- a/Sandbox/Sandbox.csproj +++ b/Sandbox/Sandbox.csproj @@ -1,11 +1,10 @@ Exe - net10.0 false - + diff --git a/Sandbox/TestSomething.cs b/Sandbox/TestSomething.cs index 736e70b0..a9da6f72 100644 --- a/Sandbox/TestSomething.cs +++ b/Sandbox/TestSomething.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; using System.Text.Json; -using System.Threading.Tasks; namespace Sandbox; diff --git a/version.json b/version.json index 286b9f36..a79bea72 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "3.15", + "version": "3.16", "publicReleaseRefSpec": [ "^refs/heads/main$" ],