diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..c674463f
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,127 @@
+name: Build & Test
+
+on:
+ push:
+ paths-ignore:
+ - 'wiki/**'
+ - '**.md'
+ pull_request:
+ paths-ignore:
+ - 'wiki/**'
+ - '**.md'
+
+env:
+ dotnetVersion: 5.0.x
+
+defaults:
+ run:
+ working-directory: src
+
+jobs:
+ test:
+ name: Test
+ runs-on: windows-latest
+ steps:
+ - name: Checkout Source Code
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0 # avoid shallow clone for NBGV
+
+ - name: Setup .NET Core SDK ${{ env.dotnetVersion }}
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: ${{ env.dotnetVersion }}
+
+ - name: Test
+ run: dotnet test --configuration Release --logger GitHubActions
+
+ build:
+ name: Build
+ needs: test
+ strategy:
+ fail-fast: true
+ matrix:
+ runtime: [win-x64, linux-x64, osx-x64]
+ # Must run on Windows so that version info gets properly set in host EXE. See:
+ # https://github.com/dotnet/runtime/issues/3828
+ runs-on: windows-latest
+ steps:
+ - name: Checkout Source Code
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0 # avoid shallow clone for NBGV
+
+ - uses: dotnet/nbgv@master
+ id: nbgv
+
+ - name: Setup .NET Core SDK ${{ env.dotnetVersion }}
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: ${{ env.dotnetVersion }}
+
+ - name: Publish
+ run: >
+ dotnet publish Trash
+ --configuration Release
+ --output publish
+ --runtime ${{ matrix.runtime }}
+ --self-contained true
+ -p:PublishSingleFile=true
+ -p:PublishTrimmed=true
+ -p:IncludeNativeLibrariesForSelfExtract=true
+
+ - name: Zip Binary
+ shell: pwsh
+ run: Compress-Archive publish/trash* trash-${{ matrix.runtime }}.zip
+
+ - name: Upload Artifacts
+ uses: actions/upload-artifact@v2
+ with:
+ name: trash
+ path: src/trash-*.zip
+
+ release:
+ name: Release
+ needs: build
+ runs-on: ubuntu-latest
+ if: startsWith(github.ref, 'refs/tags/v')
+ # github.event.create.ref_type == 'tag'
+ # startsWith(github.event.push.ref, 'refs/heads/release/')
+ # github.event.pull_request.merged == true &&
+ # startsWith(github.event.pull_request.head.ref, 'release/')
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0 # avoid shallow clone for NBGV
+ # token: ${{ secrets.GITHUB_TOKEN }} # Allows git push
+
+ - name: Set up NBGV
+ uses: dotnet/nbgv@master
+ id: nbgv
+
+ - name: Verify tag matches version.json
+ if: endsWith(github.ref, steps.nbgv.outputs.SimpleVersion) != true
+ run: |
+ echo "The tag ${{ github.ref }} does not match version.json: ${{ steps.nbgv.outputs.SimpleVersion }}"
+ exit 1
+
+ - name: Download Artifacts
+ uses: actions/download-artifact@v2
+ with:
+ name: trash
+
+ - name: Extract Changelog
+ id: changelog
+ uses: ffurrer2/extract-release-notes@v1
+
+ - name: Create Release
+ uses: softprops/action-gh-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.PAT }}
+ with:
+ files: trash-*.zip
+ body: ${{ steps.changelog.outputs.release_notes }}
+ tag_name: ${{ github.event.create.ref }}
+ draft: false
+ prerelease: ${{ steps.nbgv.outputs.PrereleaseVersion != '' }}
diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml
new file mode 100644
index 00000000..4a63d7c4
--- /dev/null
+++ b/.github/workflows/draft-new-release.yml
@@ -0,0 +1,78 @@
+name: Draft New Release
+
+on:
+ workflow_dispatch:
+
+jobs:
+ draft_new_release:
+ name: Draft a new release
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 0 # avoid shallow clone for NBGV
+ token: ${{ secrets.PAT }} # Allows git push
+
+ - name: Set up NBGV
+ uses: dotnet/nbgv@master
+ id: nbgv
+
+ - run: echo "VERSION=${{ steps.nbgv.outputs.SimpleVersion }}${{ steps.nbgv.outputs.PrereleaseVersion }}" >> $GITHUB_ENV
+
+ - name: Initialize mandatory git config
+ run: |
+ git config user.name "GitHub Actions"
+ git config user.email noreply@github.com
+
+ # TODO: Support specifying a SHA1 to branch from in the workflow run?
+ - name: Create Release Branch
+ run: |
+ nbgv prepare-release
+ git checkout release/${{ steps.nbgv.outputs.SimpleVersion }}
+
+ - name: Update changelog
+ uses: thomaseizinger/keep-a-changelog-new-release@1.1.0
+ with:
+ version: ${{ env.VERSION }}
+
+ - name: Commit Changelog
+ run: git commit -m 'Finalize changelog for version ${{ env.VERSION }}' -- CHANGELOG.md
+
+ - name: Push master and release branch
+ run: git push origin master +release/${{ steps.nbgv.outputs.SimpleVersion }}
+
+ - name: Create Pull Request
+ uses: peter-evans/create-pull-request@v3
+ id: cpr
+ with:
+ token: ${{ secrets.PAT }}
+ delete-branch: true
+ base: master
+
+ - name: Enable Pull Request Automerge
+ uses: peter-evans/enable-pull-request-automerge@v1
+ with:
+ token: ${{ secrets.PAT }}
+ pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
+ merge-method: merge
+ title: "Preparation for Release: ${{ env.VERSION }}"
+ body: |
+ This pull request represents changes to be made in preparation of the next release,
+ ${{ env.VERSION }}.
+
+ Once the build and release tasks in this PR are completed, the release will be created
+ and this PR will be automatically merged.
+
+ - name: Auto Approve Pull Request
+ uses: actions/github-script@v3
+ if: steps.cpr.outputs.pull-request-operation == 'created'
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ await github.pulls.createReview({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: ${{ steps.cpr.outputs.pull-request-number }},
+ event: 'APPROVE'
+ })
diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml
new file mode 100644
index 00000000..e7d81399
--- /dev/null
+++ b/.github/workflows/wiki.yml
@@ -0,0 +1,25 @@
+name: Publish Wiki
+
+on:
+ push:
+ paths:
+ - 'wiki/**'
+ branches:
+ - master
+
+jobs:
+ wiki:
+ name: Publish Wiki
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Source Code
+ uses: actions/checkout@v2
+
+ - name: Upload Documentation to Wiki
+ uses: Andrew-Chen-Wang/github-wiki-action@v2
+ env:
+ WIKI_DIR: wiki/
+ GH_TOKEN: ${{ secrets.PAT }}
+ GH_MAIL: ${{ secrets.EMAIL }}
+ GH_NAME: ${{ github.repository_owner }}
+ EXCLUDED_FILES: "*.json"
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..c5e66d1b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,466 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/windows,rider,csharp
+# Edit at https://www.toptal.com/developers/gitignore?templates=windows,rider,csharp
+
+### Csharp ###
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*[.json, .xml, .info]
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+### Rider ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.toptal.com/developers/gitignore/api/windows,rider,csharp
diff --git a/.idea/.idea.TrashUpdater/.idea/.gitignore b/.idea/.idea.TrashUpdater/.idea/.gitignore
new file mode 100644
index 00000000..ea439e0d
--- /dev/null
+++ b/.idea/.idea.TrashUpdater/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/.idea.TrashUpdater.iml
+/modules.xml
+/contentModel.xml
+/projectSettingsUpdater.xml
diff --git a/.idea/.idea.TrashUpdater/.idea/encodings.xml b/.idea/.idea.TrashUpdater/.idea/encodings.xml
new file mode 100644
index 00000000..df87cf95
--- /dev/null
+++ b/.idea/.idea.TrashUpdater/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.TrashUpdater/.idea/indexLayout.xml b/.idea/.idea.TrashUpdater/.idea/indexLayout.xml
new file mode 100644
index 00000000..7b08163c
--- /dev/null
+++ b/.idea/.idea.TrashUpdater/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.TrashUpdater/.idea/vcs.xml b/.idea/.idea.TrashUpdater/.idea/vcs.xml
new file mode 100644
index 00000000..94a25f7f
--- /dev/null
+++ b/.idea/.idea.TrashUpdater/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 00000000..12f87bc2
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..94a25f7f
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.markdownlint.json b/.markdownlint.json
new file mode 100644
index 00000000..60b5550b
--- /dev/null
+++ b/.markdownlint.json
@@ -0,0 +1,11 @@
+{
+ "default": true,
+ "line-length": {
+ "line_length": 100,
+ "tables": false,
+ "code_blocks": false
+ },
+ "no-inline-html": {
+ "allowed_elements": ["br"]
+ }
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..be0cafdd
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,34 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [1.0.0] - 2021-04-14
+
+See the [Python Migration Guide][py-mig] for details on how to update your YAML configuration.
+
+[py-mig]: https://github.com/rcdailey/trash-updater/wiki/Python-Migration-Guide
+
+### Added
+
+- Full rewrite of the application in C# .NET Core 5
+- More than one configuration (YAML) file can be specified using the `--config` option.
+- Multiple Sonarr and Radarr instances can be specified in a single YAML config.
+
+### Removed
+
+- Nearly all command line options removed in favor of YAML equivalents.
+- Completely removed old python project & source code
+
+## [0.1.0]
+
+First (and final) release of the Python version of the application.
+
+
+[unreleased]: https://github.com/rcdailey/trash-updater/compare/v1.0.0...HEAD
+[1.0.0]: https://github.com/rcdailey/trash-updater/compare/v0.1.0...v1.0.0
+[0.1.0]: https://github.com/rcdailey/trash-updater/releases/tag/v0.1.0
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..42c87b9e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Robert Dailey
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Publish.ps1 b/Publish.ps1
new file mode 100644
index 00000000..ce8bf66e
--- /dev/null
+++ b/Publish.ps1
@@ -0,0 +1,15 @@
+[CmdletBinding()]
+param (
+ [Parameter()]
+ [string]
+ $runtime
+)
+
+dotnet publish Trash `
+ --output publish `
+ --runtime $runtime `
+ --configuration Release `
+ --self-contained true `
+ -p:PublishSingleFile=true `
+ -p:PublishTrimmed=true `
+ -p:IncludeNativeLibrariesForSelfExtract=true
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..d15c0ea7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,109 @@
+# TRaSH Guide Updater
+
+Automatically mirror TRaSH guides to your Sonarr/Radarr instance.
+
+> **NOTICE**: This program is a work-in-progress!
+
+## Features
+
+Features list will continue to grow. See the limitations & roadmap section for more details!
+
+### Sonarr
+
+Release Profiles
+
+- "Preferred", "Must Not Contain", and "Must Contain" terms from guides are reflected in
+ corresponding release profile fields in Sonarr.
+- "Include Preferred when Renaming" is properly checked/unchecked depending on explicit mention of
+ this in the guides.
+- Profiles get created if they do not exist, or updated if they already exist. Profiles get a unique
+ name based on the guide and this name is used to find them in subsequent runs.
+- Tags can be added to any updated or created profiles.
+- Ability to convert preferred with negative scores to "Must not contain" terms.
+
+Quality Definitions
+
+- Anime and Series (Non-Anime) quality definitions from the guide.
+- "Hybrid" type supported that is a mixture of both.
+
+### Radarr
+
+Quality Definitions
+
+- Movie quality definition from the guide
+
+## Installation
+
+Simply download the latest release for your platform:
+
+- [Windows (64-bit)](https://github.com/rcdailey/trash-updater/releases/latest/download/trash-win-x64.zip)
+- [Linux (64-bit)](https://github.com/rcdailey/trash-updater/releases/latest/download/trash-linux-x64.zip)
+- [macOS (64-bit)](https://github.com/rcdailey/trash-updater/releases/latest/download/trash-osx-x64.zip)
+
+The above links are from the latest release on the [releases page][rp]. Feel free to visit there for
+release notes and older releases.
+
+> **Note**: For Sonarr updates to work, you must be running version `3.0.4.1098` or greater.
+
+[rp]: https://github.com/rcdailey/trash-updater/releases
+
+### Special Note about Linux
+
+When you extract the ZIP archive on Linux, it will *not* have the executable permission set. Here is
+a quick one-liner you can use in a terminal to download the latest release, extract it, and set it
+as executable. Run this from the directory where you want `trash` to be installed.
+
+```bash
+ wget -O trash.zip https://github.com/rcdailey/trash-updater/releases/latest/download/trash-linux-x64.zip \
+ && unzip trash.zip && rm trash.zip && chmod +x trash
+```
+
+## Getting Started
+
+> **TL;DR**: Run `trash [sonarr|radarr] --help` for help with available command line options. Visit
+> [the wiki](https://github.com/rcdailey/trash-updater/wiki) for in-depth documentation about the
+> command line, configuration, and other topics.
+
+The `trash` executable provides one subcommand per distinct service. This means, for example, you
+can run `trash sonarr` and `trash radarr`. When you run these subcommands, the relevant service
+configuration is read from the YAML files.
+
+That's all you need to do on the command line to get the program to parse guides and push settings
+to the respective service. Most of the documentation will be for the YAML configuration, which is
+what drives the behavior of the program.
+
+### Read the Documentation
+
+Main documentation is located in the wiki. Links provided below for some main topics.
+
+- [Command Line Reference](../wiki/Command-Line-Reference)
+- [Configuration Reference](../wiki/Configuration-Reference)
+
+## Important Notices
+
+The script may stop working at any time due to guide updates. I will do my best to fix them in a
+timely manner. Reporting such issues ASAP would be appreciated and will help identify issues more
+quickly.
+
+Please be aware that this application relies on a deterministic and consistent structure of the
+TRaSH Guide markdown files. I have [documented guidelines][dg] for the TRaSH Guides that should help
+to reduce the risk of the guide breaking the program's parsing logic, however it requires that guide
+contributors follow them.
+
+[dg]: ../wiki/TRaSH-Guide-Structural-Guidelines
+
+### Limitations
+
+This application is a work in progress. At the moment, it only supports the following features
+and/or has the following limitations:
+
+- Radarr custom formats are not supported yet (coming soon).
+- Multiple scores on the same line are not supported. Only the first is used.
+
+### Roadmap
+
+In addition to the above limitations, the following items are planned for the future.
+
+- Better and more polished error handling (it's pretty minimal right now)
+- Implement some sort of guide versioning (e.g. to avoid updating a release profile if the guide did
+ not change).
diff --git a/azure-pipelines/build.yml b/azure-pipelines/build.yml
new file mode 100644
index 00000000..e8cbc84c
--- /dev/null
+++ b/azure-pipelines/build.yml
@@ -0,0 +1,54 @@
+pool:
+ vmImage: windows-latest
+
+variables:
+ configuration: Release
+
+strategy:
+ matrix:
+ windows:
+ runtime: win-x64
+ linux:
+ runtime: linux-x64
+ macos:
+ runtime: osx-x64
+
+steps:
+ -
+ checkout: self
+ -
+ task: UseDotNet@2
+ displayName: Setup .NET Core
+ inputs:
+ version: 5.0.x
+ -
+ pwsh: |
+ dotnet tool install --tool-path . nbgv
+ ./nbgv cloud -a
+ displayName: Set build number
+ -
+ task: DotNetCoreCLI@2
+ displayName:
+ inputs:
+ command: build
+ arguments: --configuration $(configuration)
+ -
+ task: DotNetCoreCLI@2
+ displayName:
+ inputs:
+ command: test
+ arguments: --configuration $(configuration)
+ -
+ task: DotNetCoreCLI@2
+ displayName:
+ inputs:
+ command: publish
+ projects: Trash
+ arguments: >
+ --runtime $(runtime)
+ --configuration $(configuration)
+ --self-contained true
+ -p:PublishSingleFile=true
+ -p:PublishTrimmed=true
+ -p:IncludeNativeLibrariesForSelfExtract=true
+
\ No newline at end of file
diff --git a/src/.idea/.idea.TrashUpdater/.idea/.gitignore b/src/.idea/.idea.TrashUpdater/.idea/.gitignore
new file mode 100644
index 00000000..b58e4759
--- /dev/null
+++ b/src/.idea/.idea.TrashUpdater/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/modules.xml
+/projectSettingsUpdater.xml
+/.idea.TrashUpdater.iml
+/contentModel.xml
diff --git a/src/.idea/.idea.TrashUpdater/.idea/.name b/src/.idea/.idea.TrashUpdater/.idea/.name
new file mode 100644
index 00000000..953afe3d
--- /dev/null
+++ b/src/.idea/.idea.TrashUpdater/.idea/.name
@@ -0,0 +1 @@
+TrashUpdater
\ No newline at end of file
diff --git a/src/.idea/.idea.TrashUpdater/.idea/encodings.xml b/src/.idea/.idea.TrashUpdater/.idea/encodings.xml
new file mode 100644
index 00000000..df87cf95
--- /dev/null
+++ b/src/.idea/.idea.TrashUpdater/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/.idea.TrashUpdater/.idea/indexLayout.xml b/src/.idea/.idea.TrashUpdater/.idea/indexLayout.xml
new file mode 100644
index 00000000..7b08163c
--- /dev/null
+++ b/src/.idea/.idea.TrashUpdater/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/.idea.TrashUpdater/.idea/vcs.xml b/src/.idea/.idea.TrashUpdater/.idea/vcs.xml
new file mode 100644
index 00000000..6c0b8635
--- /dev/null
+++ b/src/.idea/.idea.TrashUpdater/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644
index 00000000..c7dd259f
--- /dev/null
+++ b/src/Directory.Build.props
@@ -0,0 +1,31 @@
+
+
+ net5.0
+ enable
+ 9999
+
+ embedded
+
+
+ $(MSBuildThisFileDirectory)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
new file mode 100644
index 00000000..ff50ca22
--- /dev/null
+++ b/src/Directory.Build.targets
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ true
+
+
+
+
+
+
+
diff --git a/src/TestLibrary.Tests/Data/DataFile.txt b/src/TestLibrary.Tests/Data/DataFile.txt
new file mode 100644
index 00000000..833889bd
--- /dev/null
+++ b/src/TestLibrary.Tests/Data/DataFile.txt
@@ -0,0 +1 @@
+DataFile
diff --git a/src/TestLibrary.Tests/DataFileWontBeFound.txt b/src/TestLibrary.Tests/DataFileWontBeFound.txt
new file mode 100644
index 00000000..9a67e972
--- /dev/null
+++ b/src/TestLibrary.Tests/DataFileWontBeFound.txt
@@ -0,0 +1 @@
+DataFileWontBeFound
diff --git a/src/TestLibrary.Tests/OtherData/AnotherDataFile.txt b/src/TestLibrary.Tests/OtherData/AnotherDataFile.txt
new file mode 100644
index 00000000..a523bba2
--- /dev/null
+++ b/src/TestLibrary.Tests/OtherData/AnotherDataFile.txt
@@ -0,0 +1 @@
+AnotherDataFile
diff --git a/src/TestLibrary.Tests/TestDataTest.cs b/src/TestLibrary.Tests/TestDataTest.cs
new file mode 100644
index 00000000..353abb3b
--- /dev/null
+++ b/src/TestLibrary.Tests/TestDataTest.cs
@@ -0,0 +1,53 @@
+using System;
+using FluentAssertions;
+using NUnit.Framework;
+
+namespace TestLibrary.Tests
+{
+ internal class TestFixtureMissingAttribute
+ {
+ }
+
+ [TestFixture]
+ public class TestDataTest
+ {
+ [Test]
+ public void Construction_ClassMissingAttribute_Throw()
+ {
+ // ReSharper disable once ObjectCreationAsStatement
+ Action act = () => new TestData();
+
+ act.Should()
+ .Throw()
+ .WithMessage("*does not have the [TestFixture] attribute");
+ }
+
+ [Test]
+ public void GetResourceData_CustomDir_ReturnResourceData()
+ {
+ TestData testData = new();
+ testData.DataSubdirectoryName = "OtherData";
+ var data = testData.GetResourceData("AnotherDataFile.txt");
+ data.Trim().Should().Be("AnotherDataFile");
+ }
+
+ [Test]
+ public void GetResourceData_DefaultDir_ReturnResourceData()
+ {
+ TestData testData = new();
+ var data = testData.GetResourceData("DataFile.txt");
+ data.Trim().Should().Be("DataFile");
+ }
+
+ [Test]
+ public void GetResourceData_NonexistentFile_Throw()
+ {
+ TestData testData = new();
+ Action act = () => testData.GetResourceData("DataFileWontBeFound.txt");
+
+ act.Should()
+ .Throw()
+ .WithMessage("Embedded resource not found*");
+ }
+ }
+}
diff --git a/src/TestLibrary.Tests/TestLibrary.Tests.csproj b/src/TestLibrary.Tests/TestLibrary.Tests.csproj
new file mode 100644
index 00000000..474578da
--- /dev/null
+++ b/src/TestLibrary.Tests/TestLibrary.Tests.csproj
@@ -0,0 +1,14 @@
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/TestLibrary/StreamBuilder.cs b/src/TestLibrary/StreamBuilder.cs
new file mode 100644
index 00000000..d0bc7338
--- /dev/null
+++ b/src/TestLibrary/StreamBuilder.cs
@@ -0,0 +1,14 @@
+using System.IO;
+using System.Text;
+
+namespace TestLibrary
+{
+ public static class StreamBuilder
+ {
+ public static StreamReader FromString(string data)
+ {
+ var stream = new MemoryStream(Encoding.UTF8.GetBytes(data));
+ return new StreamReader(stream);
+ }
+ }
+}
diff --git a/src/TestLibrary/TestData.cs b/src/TestLibrary/TestData.cs
new file mode 100644
index 00000000..91f86fbd
--- /dev/null
+++ b/src/TestLibrary/TestData.cs
@@ -0,0 +1,41 @@
+using System;
+using System.IO;
+using System.Reflection;
+using NUnit.Framework;
+
+namespace TestLibrary
+{
+ public class TestData
+ {
+ private readonly Assembly? _assembly;
+ private readonly string? _namespace;
+
+ public TestData()
+ {
+ var attributes = typeof(TTestFixtureClass).GetCustomAttributes(typeof(TestFixtureAttribute), true);
+ if (attributes.Length == 0)
+ {
+ throw new ArgumentException(
+ $"{typeof(TTestFixtureClass).Name} does not have the [TestFixture] attribute");
+ }
+
+ _namespace = typeof(TTestFixtureClass).Namespace;
+ _assembly = Assembly.GetAssembly(typeof(TTestFixtureClass));
+ }
+
+ public string DataSubdirectoryName { get; set; } = "Data";
+
+ public string GetResourceData(string name)
+ {
+ var resourceName = $"{_namespace}.{DataSubdirectoryName}.{name}";
+ using var stream = _assembly?.GetManifestResourceStream(resourceName);
+ if (stream == null)
+ {
+ throw new ArgumentException($"Embedded resource not found: {resourceName}");
+ }
+
+ using var reader = new StreamReader(stream);
+ return reader.ReadToEnd();
+ }
+ }
+}
diff --git a/src/TestLibrary/TestLibrary.csproj b/src/TestLibrary/TestLibrary.csproj
new file mode 100644
index 00000000..d7877722
--- /dev/null
+++ b/src/TestLibrary/TestLibrary.csproj
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/Trash.Tests/Config/ConfigurationLoaderTest.cs b/src/Trash.Tests/Config/ConfigurationLoaderTest.cs
new file mode 100644
index 00000000..cf705d05
--- /dev/null
+++ b/src/Trash.Tests/Config/ConfigurationLoaderTest.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Abstractions;
+using System.Linq;
+using System.Text;
+using FluentAssertions;
+using NSubstitute;
+using NUnit.Framework;
+using TestLibrary;
+using Trash.Config;
+using Trash.Extensions;
+using Trash.Sonarr;
+using Trash.Sonarr.ReleaseProfile;
+using YamlDotNet.Serialization.ObjectFactories;
+
+namespace Trash.Tests.Config
+{
+ [TestFixture]
+ public class ConfigurationLoaderTest
+ {
+ private TextReader GetResourceData(string file)
+ {
+ var testData = new TestData();
+ if (testData == null)
+ {
+ throw new InvalidOperationException("TestData object has not been created yet");
+ }
+
+ return new StringReader(testData.GetResourceData(file));
+ }
+
+ [Test]
+ public void Load_UsingStream_CorrectParsing()
+ {
+ var configLoader = new ConfigurationLoader(
+ Substitute.For>(),
+ Substitute.For(),
+ new DefaultObjectFactory());
+
+ var configs = configLoader.LoadFromStream(GetResourceData("Load_UsingStream_CorrectParsing.yml"), "sonarr");
+
+ configs.Should()
+ .BeEquivalentTo(new List
+ {
+ new()
+ {
+ ApiKey = "95283e6b156c42f3af8a9b16173f876b",
+ BaseUrl = "http://localhost:8989",
+ ReleaseProfiles = new List
+ {
+ new()
+ {
+ Type = ReleaseProfileType.Anime,
+ StrictNegativeScores = true,
+ Tags = new List {"anime"}
+ },
+ new()
+ {
+ Type = ReleaseProfileType.Series,
+ StrictNegativeScores = false,
+ Tags = new List
+ {
+ "tv",
+ "series"
+ }
+ }
+ }
+ }
+ });
+ }
+
+ [Test]
+ public void LoadMany_CorrectNumberOfIterations()
+ {
+ StreamReader MockYaml(params object[] args)
+ {
+ var str = new StringBuilder("sonarr:");
+ const string templateYaml = "\n - base_url: {0}";
+ str.Append(args.Aggregate("", (current, p) => current + templateYaml.FormatWith(p)));
+ return StreamBuilder.FromString(str.ToString());
+ }
+
+ var fs = Substitute.For();
+ fs.File.OpenText(Arg.Any())
+ .Returns(MockYaml(1, 2), MockYaml(3));
+
+ var provider = Substitute.For>();
+ // var objectFactory = Substitute.For();
+ // objectFactory.Create(Arg.Any())
+ // .Returns(t => Substitute.For(new[] {(Type)t[0]}, Array.Empty