Build Sonarr on Net6

Co-Authored-By: ta264 <ta264@users.noreply.github.com>
pull/5116/head
Qstick 3 years ago committed by Mark McDowall
parent f79ae77a3a
commit 878d1561aa

@ -2,13 +2,267 @@
# editorconfig.org
root = true
[*.{cs}]
# NOTE: Requires **VS2019 16.3** or later
# Stylecop.ruleset
# Description: Rules for Sonarr
# Code files
[*.cs]
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:refactoring
dotnet_style_qualification_for_property = false:refactoring
dotnet_style_qualification_for_method = false:refactoring
dotnet_style_qualification_for_event = false:refactoring
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
dotnet_naming_style.instance_field_style.capitalization = camel_case
dotnet_naming_style.instance_field_style.required_prefix = _
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Stylecop Rules
dotnet_diagnostic.SA0001.severity = none
dotnet_diagnostic.SA1005.severity = none
dotnet_diagnostic.SA1025.severity = none
dotnet_diagnostic.SA1101.severity = none
dotnet_diagnostic.SA1116.severity = none
dotnet_diagnostic.SA1118.severity = none
dotnet_diagnostic.SA1122.severity = none
dotnet_diagnostic.SA1201.severity = suggestion
dotnet_diagnostic.SA1202.severity = suggestion
dotnet_diagnostic.SA1204.severity = suggestion
dotnet_diagnostic.SA1300.severity = none
dotnet_diagnostic.SA1303.severity = none
dotnet_diagnostic.SA1304.severity = none
dotnet_diagnostic.SA1306.severity = none
dotnet_diagnostic.SA1309.severity = none
dotnet_diagnostic.SA1310.severity = none
dotnet_diagnostic.SA1401.severity = none
dotnet_diagnostic.SA1402.severity = none
dotnet_diagnostic.SA1404.severity = suggestion
dotnet_diagnostic.SA1405.severity = suggestion
dotnet_diagnostic.SA1406.severity = suggestion
dotnet_diagnostic.SA1410.severity = suggestion
dotnet_diagnostic.SA1411.severity = suggestion
dotnet_diagnostic.SA1413.severity = none
dotnet_diagnostic.SA1516.severity = none
dotnet_diagnostic.SA1600.severity = none
dotnet_diagnostic.SA1601.severity = none
dotnet_diagnostic.SA1602.severity = none
dotnet_diagnostic.SA1604.severity = none
dotnet_diagnostic.SA1605.severity = none
dotnet_diagnostic.SA1606.severity = none
dotnet_diagnostic.SA1607.severity = none
dotnet_diagnostic.SA1608.severity = none
dotnet_diagnostic.SA1610.severity = none
dotnet_diagnostic.SA1611.severity = none
dotnet_diagnostic.SA1612.severity = none
dotnet_diagnostic.SA1613.severity = none
dotnet_diagnostic.SA1614.severity = none
dotnet_diagnostic.SA1615.severity = none
dotnet_diagnostic.SA1616.severity = none
dotnet_diagnostic.SA1617.severity = none
dotnet_diagnostic.SA1618.severity = none
dotnet_diagnostic.SA1619.severity = none
dotnet_diagnostic.SA1620.severity = none
dotnet_diagnostic.SA1621.severity = none
dotnet_diagnostic.SA1622.severity = none
dotnet_diagnostic.SA1623.severity = none
dotnet_diagnostic.SA1624.severity = none
dotnet_diagnostic.SA1625.severity = none
dotnet_diagnostic.SA1626.severity = none
dotnet_diagnostic.SA1627.severity = none
dotnet_diagnostic.SA1629.severity = none
dotnet_diagnostic.SA1633.severity = none
dotnet_diagnostic.SA1634.severity = none
dotnet_diagnostic.SA1635.severity = none
dotnet_diagnostic.SA1636.severity = none
dotnet_diagnostic.SA1637.severity = none
dotnet_diagnostic.SA1638.severity = none
dotnet_diagnostic.SA1640.severity = none
dotnet_diagnostic.SA1641.severity = none
dotnet_diagnostic.SA1642.severity = none
dotnet_diagnostic.SA1643.severity = none
dotnet_diagnostic.SA1648.severity = none
dotnet_diagnostic.SA1649.severity = none
dotnet_diagnostic.SA1651.severity = none
dotnet_diagnostic.SX1309.severity = warning
# Microsoft Analyzers that fail and need to be sorted thru
dotnet_diagnostic.ASP0000.severity = suggestion
dotnet_diagnostic.CA1000.severity = suggestion
dotnet_diagnostic.CA1001.severity = suggestion
dotnet_diagnostic.CA1002.severity = suggestion
dotnet_diagnostic.CA1003.severity = suggestion
dotnet_diagnostic.CA1008.severity = suggestion
dotnet_diagnostic.CA1010.severity = suggestion
dotnet_diagnostic.CA1012.severity = suggestion
dotnet_diagnostic.CA1014.severity = suggestion
dotnet_diagnostic.CA1016.severity = suggestion
dotnet_diagnostic.CA1017.severity = suggestion
dotnet_diagnostic.CA1018.severity = suggestion
dotnet_diagnostic.CA1019.severity = suggestion
dotnet_diagnostic.CA1021.severity = suggestion
dotnet_diagnostic.CA1024.severity = suggestion
dotnet_diagnostic.CA1027.severity = suggestion
dotnet_diagnostic.CA1028.severity = suggestion
dotnet_diagnostic.CA1030.severity = suggestion
dotnet_diagnostic.CA1031.severity = suggestion
dotnet_diagnostic.CA1032.severity = suggestion
dotnet_diagnostic.CA1033.severity = suggestion
dotnet_diagnostic.CA1034.severity = suggestion
dotnet_diagnostic.CA1036.severity = suggestion
dotnet_diagnostic.CA1040.severity = suggestion
dotnet_diagnostic.CA1041.severity = suggestion
dotnet_diagnostic.CA1043.severity = suggestion
dotnet_diagnostic.CA1044.severity = suggestion
dotnet_diagnostic.CA1050.severity = suggestion
dotnet_diagnostic.CA1051.severity = suggestion
dotnet_diagnostic.CA1052.severity = suggestion
dotnet_diagnostic.CA1054.severity = suggestion
dotnet_diagnostic.CA1055.severity = suggestion
dotnet_diagnostic.CA1056.severity = suggestion
dotnet_diagnostic.CA1058.severity = suggestion
dotnet_diagnostic.CA1060.severity = suggestion
dotnet_diagnostic.CA1061.severity = suggestion
dotnet_diagnostic.CA1062.severity = suggestion
dotnet_diagnostic.CA1063.severity = suggestion
dotnet_diagnostic.CA1064.severity = suggestion
dotnet_diagnostic.CA1065.severity = suggestion
dotnet_diagnostic.CA1066.severity = suggestion
dotnet_diagnostic.CA1067.severity = suggestion
dotnet_diagnostic.CA1068.severity = suggestion
dotnet_diagnostic.CA1069.severity = suggestion
dotnet_diagnostic.CA1200.severity = suggestion
dotnet_diagnostic.CA1303.severity = suggestion
dotnet_diagnostic.CA1304.severity = suggestion
dotnet_diagnostic.CA1305.severity = suggestion
dotnet_diagnostic.CA1307.severity = suggestion
dotnet_diagnostic.CA1308.severity = suggestion
dotnet_diagnostic.CA1309.severity = suggestion
dotnet_diagnostic.CA1310.severity = suggestion
dotnet_diagnostic.CA1401.severity = suggestion
dotnet_diagnostic.CA1416.severity = suggestion
dotnet_diagnostic.CA1507.severity = suggestion
dotnet_diagnostic.CA1508.severity = suggestion
dotnet_diagnostic.CA1707.severity = suggestion
dotnet_diagnostic.CA1708.severity = suggestion
dotnet_diagnostic.CA1710.severity = suggestion
dotnet_diagnostic.CA1711.severity = suggestion
dotnet_diagnostic.CA1712.severity = suggestion
dotnet_diagnostic.CA1714.severity = suggestion
dotnet_diagnostic.CA1715.severity = suggestion
dotnet_diagnostic.CA1716.severity = suggestion
dotnet_diagnostic.CA1717.severity = suggestion
dotnet_diagnostic.CA1720.severity = suggestion
dotnet_diagnostic.CA1721.severity = suggestion
dotnet_diagnostic.CA1724.severity = suggestion
dotnet_diagnostic.CA1725.severity = suggestion
dotnet_diagnostic.CA1801.severity = suggestion
dotnet_diagnostic.CA1802.severity = suggestion
dotnet_diagnostic.CA1805.severity = suggestion
dotnet_diagnostic.CA1806.severity = suggestion
dotnet_diagnostic.CA1810.severity = suggestion
dotnet_diagnostic.CA1812.severity = suggestion
dotnet_diagnostic.CA1813.severity = suggestion
dotnet_diagnostic.CA1814.severity = suggestion
dotnet_diagnostic.CA1815.severity = suggestion
dotnet_diagnostic.CA1816.severity = suggestion
dotnet_diagnostic.CA1819.severity = suggestion
dotnet_diagnostic.CA1822.severity = suggestion
dotnet_diagnostic.CA1823.severity = suggestion
dotnet_diagnostic.CA1824.severity = suggestion
dotnet_diagnostic.CA2000.severity = suggestion
dotnet_diagnostic.CA2002.severity = suggestion
dotnet_diagnostic.CA2007.severity = suggestion
dotnet_diagnostic.CA2008.severity = suggestion
dotnet_diagnostic.CA2009.severity = suggestion
dotnet_diagnostic.CA2010.severity = suggestion
dotnet_diagnostic.CA2011.severity = suggestion
dotnet_diagnostic.CA2012.severity = suggestion
dotnet_diagnostic.CA2013.severity = suggestion
dotnet_diagnostic.CA2100.severity = suggestion
dotnet_diagnostic.CA2101.severity = suggestion
dotnet_diagnostic.CA2119.severity = suggestion
dotnet_diagnostic.CA2153.severity = suggestion
dotnet_diagnostic.CA2200.severity = suggestion
dotnet_diagnostic.CA2201.severity = suggestion
dotnet_diagnostic.CA2207.severity = suggestion
dotnet_diagnostic.CA2208.severity = suggestion
dotnet_diagnostic.CA2211.severity = suggestion
dotnet_diagnostic.CA2213.severity = suggestion
dotnet_diagnostic.CA2214.severity = suggestion
dotnet_diagnostic.CA2215.severity = suggestion
dotnet_diagnostic.CA2216.severity = suggestion
dotnet_diagnostic.CA2219.severity = suggestion
dotnet_diagnostic.CA2225.severity = suggestion
dotnet_diagnostic.CA2226.severity = suggestion
dotnet_diagnostic.CA2227.severity = suggestion
dotnet_diagnostic.CA2229.severity = suggestion
dotnet_diagnostic.CA2231.severity = suggestion
dotnet_diagnostic.CA2234.severity = suggestion
dotnet_diagnostic.CA2235.severity = suggestion
dotnet_diagnostic.CA2237.severity = suggestion
dotnet_diagnostic.CA2241.severity = suggestion
dotnet_diagnostic.CA2242.severity = suggestion
dotnet_diagnostic.CA2243.severity = suggestion
dotnet_diagnostic.CA2244.severity = suggestion
dotnet_diagnostic.CA2245.severity = suggestion
dotnet_diagnostic.CA2246.severity = suggestion
dotnet_diagnostic.CA3061.severity = suggestion
dotnet_diagnostic.CA3075.severity = suggestion
dotnet_diagnostic.CA3076.severity = suggestion
dotnet_diagnostic.CA3077.severity = suggestion
dotnet_diagnostic.CA3147.severity = suggestion
dotnet_diagnostic.CA5350.severity = suggestion
dotnet_diagnostic.CA5351.severity = suggestion
dotnet_diagnostic.CA5359.severity = suggestion
dotnet_diagnostic.CA5360.severity = suggestion
dotnet_diagnostic.CA5363.severity = suggestion
dotnet_diagnostic.CA5364.severity = suggestion
dotnet_diagnostic.CA5365.severity = suggestion
dotnet_diagnostic.CA5366.severity = suggestion
dotnet_diagnostic.CA5368.severity = suggestion
dotnet_diagnostic.CA5369.severity = suggestion
dotnet_diagnostic.CA5370.severity = suggestion
dotnet_diagnostic.CA5371.severity = suggestion
dotnet_diagnostic.CA5372.severity = suggestion
dotnet_diagnostic.CA5373.severity = suggestion
dotnet_diagnostic.CA5374.severity = suggestion
dotnet_diagnostic.CA5379.severity = suggestion
dotnet_diagnostic.CA5384.severity = suggestion
dotnet_diagnostic.CA5385.severity = suggestion
dotnet_diagnostic.CA5392.severity = suggestion
dotnet_diagnostic.CA5394.severity = suggestion
dotnet_diagnostic.CA5397.severity = suggestion
dotnet_diagnostic.SYSLIB0006.severity = none
dotnet_diagnostic.SYSLIB0014.severity = none
[*.{js,html,js,hbs,less,css}]
charset = utf-8
trim_trailing_whitespace = true

19
.gitignore vendored

@ -117,12 +117,15 @@ src/UI/.idea/*
*log.txt
node_modules/
_output*
_artifacts
_rawPackage/
_dotTrace*
_tests*
_publish*
_temp*
*.Result.xml
coverage*.xml
coverage*.json
setup/Output/
*.~is
@ -133,12 +136,28 @@ bin
obj
output/*
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# Packages
Sonarr_*/
Sonarr_*.zip
Sonarr_*.gz
gecko.zip
geckodriver.exe
#OS X metadata files
._*
.DS_Store
_start
_temp_*/**/*
# Windows thumbnail cache files
Thumbs.db
src/.idea/
/distribution/windows/setup/output/*

@ -1,122 +1,49 @@
#! /bin/bash
msBuildVersion='15.0'
outputFolder='./_output'
outputFolderWindows='./_output_windows'
outputFolderLinux='./_output_linux'
outputFolderMacOS='./_output_macos'
outputFolderMacOSApp='./_output_macos_app'
testPackageFolder='./_tests'
testPackageFolderWindows='./_tests_windows'
testPackageFolderLinux='./_tests_linux'
sourceFolder='./src'
slnFile=$sourceFolder/Sonarr.sln
updateSubFolder=Sonarr.Update
nuget='tools/nuget/nuget.exe';
vswhere='tools/vswhere/vswhere.exe';
. ./version.sh
CheckExitCode()
{
"$@"
local status=$?
if [ $status -ne 0 ]; then
echo "error with $1" >&2
exit 1
fi
return $status
}
#! /usr/bin/env bash
set -e
outputFolder='_output'
testPackageFolder='_tests'
artifactsFolder="_artifacts";
ProgressStart()
{
echo "##teamcity[blockOpened name='$1']"
echo "##teamcity[progressStart '$1']"
echo "Start '$1'"
}
ProgressEnd()
{
echo "##teamcity[progressFinish '$1']"
echo "##teamcity[blockClosed name='$1']"
echo "Finish '$1'"
}
UpdateVersionNumber()
{
if [ "$BUILD_NUMBER" != "" ]; then
if [ "$SONARRVERSION" != "" ]; then
echo "Updating Version Info"
verMajorMinorRevision=`echo "$buildVersion" | cut -d. -f1,2,3`
verBuild=`echo "${BUILD_NUMBER}" | cut -d. -f4`
BUILD_NUMBER=$verMajorMinorRevision.$verBuild
echo "##teamcity[buildNumber '$BUILD_NUMBER']"
sed -i "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$BUILD_NUMBER<\/AssemblyVersion>/g" ./src/Directory.Build.props
sed -i "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BRANCH:-dev}<\/AssemblyConfiguration>/g" ./src/Directory.Build.props
fi
}
CreateReleaseInfo()
{
if [ "$BUILD_NUMBER" != "" ]; then
echo "Create Release Info"
echo -e "# Do Not Edit\nReleaseVersion=$BUILD_NUMBER\nBranch=${BRANCH:-dev}" > $outputFolder/release_info
sed -i'' -e "s/<AssemblyVersion>[0-9.*]\+<\/AssemblyVersion>/<AssemblyVersion>$SONARRVERSION<\/AssemblyVersion>/g" src/Directory.Build.props
sed -i'' -e "s/<AssemblyConfiguration>[\$()A-Za-z-]\+<\/AssemblyConfiguration>/<AssemblyConfiguration>${BUILD_SOURCEBRANCHNAME}<\/AssemblyConfiguration>/g" src/Directory.Build.props
sed -i'' -e "s/<string>10.0.0.0<\/string>/<string>$SONARRVERSION<\/string>/g" distribution/osx/Sonarr.app/Contents/Info.plist
fi
}
CleanFolder()
EnableBsdSupport()
{
local path=$1
local keepConfigFiles=$2
#todo enable sdk with
#SDK_PATH=$(dotnet --list-sdks | grep -P '5\.\d\.\d+' | head -1 | sed 's/\(5\.[0-9]*\.[0-9]*\).*\[\(.*\)\]/\2\/\1/g')
# BUNDLED_VERSIONS="${SDK_PATH}/Microsoft.NETCoreSdk.BundledVersions.props"
find $path -name "*.transform" -exec rm "{}" \;
if [ $keepConfigFiles != true ] ; then
find $path -name "*.dll.config" -exec rm "{}" \;
if grep -qv freebsd-x64 src/Directory.Build.props; then
sed -i'' -e "s^<RuntimeIdentifiers>\(.*\)</RuntimeIdentifiers>^<RuntimeIdentifiers>\1;freebsd-x64</RuntimeIdentifiers>^g" src/Directory.Build.props
fi
echo "Removing FluentValidation.Resources files"
find $path -name "FluentValidation.resources.dll" -exec rm "{}" \;
find $path -name "App.config" -exec rm "{}" \;
echo "Removing vshost files"
find $path -name "*.vshost.exe" -exec rm "{}" \;
echo "Removing dylib files"
find $path -name "*.dylib" -exec rm "{}" \;
echo "Removing Empty folders"
find $path -depth -empty -type d -exec rm -r "{}" \;
}
BuildWithMSBuild()
{
msBuildPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -find MSBuild\\\\\*\*\\\\Bin\\\\MSBuild.exe`
msBuildPath=${msBuildPath/C:\\/\/c\/}
msBuildPath=${msBuildPath//\\/\/}
msBuildDir=$(dirname "$msBuildPath")
echo $msBuildDir
export PATH=$msBuildDir:$PATH
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Clean //m
$nuget restore $slnFile
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
}
BuildWithXbuild()
{
export MONO_IOMAP=case
CheckExitCode msbuild /t:Clean $slnFile
mono $nuget restore $slnFile
CheckExitCode msbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
}
LintUI()
{
ProgressStart 'ESLint'
CheckExitCode yarn lint
yarn lint
ProgressEnd 'ESLint'
ProgressStart 'Stylelint'
CheckExitCode yarn stylelint
yarn stylelint
ProgressEnd 'Stylelint'
}
@ -127,331 +54,323 @@ Build()
rm -rf $outputFolder
rm -rf $testPackageFolder
if [ $runtime = "dotnet" ] ; then
BuildWithMSBuild
slnFile=src/Sonarr.sln
if [ $os = "windows" ]; then
platform=Windows
else
BuildWithXbuild
platform=Posix
fi
CleanFolder $outputFolder false
dotnet clean $slnFile -c Debug
dotnet clean $slnFile -c Release
echo "Removing Mono.Posix.dll"
rm $outputFolder/Mono.Posix.dll
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform=$platform -t:PublishAllRids
else
dotnet msbuild -restore $slnFile -p:Configuration=Release -p:Platform=$platform -p:RuntimeIdentifiers=$RID -t:PublishAllRids
fi
ProgressEnd 'Build'
}
RunWebpack()
YarnInstall()
{
ProgressStart 'yarn install'
yarn install
yarn install --frozen-lockfile --network-timeout 120000
ProgressEnd 'yarn install'
LintUI
ProgressStart 'Running webpack'
CheckExitCode yarn run build --env production
ProgressEnd 'Running webpack'
}
CreateMdbs()
RunWebpack()
{
local path=$1
if [ $runtime = "dotnet" ] ; then
local pdbFiles=( $(find $path -name "*.pdb") )
for filename in "${pdbFiles[@]}"
do
if [ -e ${filename%.pdb}.dll ] ; then
tools/pdb2mdb/pdb2mdb.exe ${filename%.pdb}.dll
fi
if [ -e ${filename%.pdb}.exe ] ; then
tools/pdb2mdb/pdb2mdb.exe ${filename%.pdb}.exe
fi
done
fi
ProgressStart 'Running webpack'
yarn run build --env production
ProgressEnd 'Running webpack'
}
PatchMono()
PackageFiles()
{
local path=$1
# Below we deal with some mono incompatibilities with windows-only dotnet core/standard libs
# See: https://github.com/mono/mono/blob/master/tools/nuget-hash-extractor/download.sh
# That list defines assemblies that are prohibited from being loaded from the appdir, instead loading from mono GAC.
# We have debian dependencies to get these installed or facades from mono 5.10+
for assembly in System.IO.Compression System.Runtime.InteropServices.RuntimeInformation System.Net.Http System.Globalization.Extensions System.Text.Encoding.CodePages System.Threading.Overlapped
do
if [ -e $path/$assembly.dll ]; then
if [ -e $sourceFolder/Libraries/Mono/$assembly.dll ]; then
echo "Copy Mono-specific facade $assembly.dll (uses win32 interop)"
cp $sourceFolder/Libraries/Mono/$assembly.dll $path/$assembly.dll
else
echo "Remove $assembly.dll (uses win32 interop)"
rm $path/$assembly.dll
fi
fi
done
# Copy more stable version of Vectors for mono <5.12
if [ -e $path/System.Numerics.Vectors.dll ]; then
packageDir="$HOME/.nuget/packages/system.numerics.vectors/4.5.0"
if [ ! -d "$HOME/.nuget/packages/system.numerics.vectors/4.5.0" ]; then
# May reside in the NuGetFallback folder, which is harder to find
# Download somewhere to get the real cache populated
if [ $runtime = "dotnet" ] ; then
$nuget install System.Numerics.Vectors -Version 4.5.0 -Output ./_temp/System.Numerics.Vectors
else
mono $nuget install System.Numerics.Vectors -Version 4.5.0 -Output ./_temp/System.Numerics.Vectors
fi
rm -rf ./_temp/System.Numerics.Vectors
fi
# Copy the netstandard2.0 version rather than net46
cp "$packageDir/lib/netstandard2.0/System.Numerics.Vectors.dll" $path/
fi
local folder="$1"
local framework="$2"
local runtime="$3"
rm -rf $folder
mkdir -p $folder
cp -r $outputFolder/$framework/$runtime/publish/* $folder
cp -r $outputFolder/Sonarr.Update/$framework/$runtime/publish $folder/Sonarr.Update
cp -r $outputFolder/UI $folder
echo "Adding LICENSE"
cp LICENSE.md $folder
}
PackageMono()
PackageLinux()
{
ProgressStart 'Creating Mono Package'
local framework="$1"
local runtime="$2"
rm -rf $outputFolderLinux
ProgressStart "Creating $runtime Package for $framework"
echo "Copying Binaries"
cp -r $outputFolder $outputFolderLinux
local folder=$artifactsFolder/$runtime/$framework/Sonarr
echo "Creating MDBs"
CreateMdbs $outputFolderLinux
echo "Removing PDBs"
find $outputFolderLinux -name "*.pdb" -exec rm "{}" \;
PackageFiles "$folder" "$framework" "$runtime"
echo "Removing Service helpers"
rm -f $outputFolderLinux/ServiceUninstall.*
rm -f $outputFolderLinux/ServiceInstall.*
echo "Removing native windows binaries Sqlite, MediaInfo"
rm -f $outputFolderLinux/sqlite3.*
rm -f $outputFolderLinux/MediaInfo.*
PatchMono $outputFolderLinux
echo "Adding Sonarr.Core.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $outputFolderLinux
# Remove Http binding redirect by renaming it
# We don't need this anymore once our minimum mono version is 5.10
sed -i "s/System.Net.Http/System.Net.Http.Mono/g" $outputFolderLinux/Sonarr.Console.exe.config
echo "Renaming Sonarr.Console.exe to Sonarr.exe"
rm $outputFolderLinux/Sonarr.exe*
for file in $outputFolderLinux/Sonarr.Console.exe*; do
mv "$file" "${file//.Console/}"
done
rm -f $folder/ServiceUninstall.*
rm -f $folder/ServiceInstall.*
echo "Removing Sonarr.Windows"
rm $outputFolderLinux/Sonarr.Windows.*
rm $folder/Sonarr.Windows.*
echo "Adding Sonarr.Mono to UpdatePackage"
cp $outputFolderLinux/Sonarr.Mono.* $outputFolderLinux/$updateSubFolder/
cp $folder/Sonarr.Mono.* $folder/Sonarr.Update
if [ "$framework" = "net6.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Sonarr.Update
cp $folder/libMonoPosixHelper.* $folder/Sonarr.Update
fi
ProgressEnd 'Creating Mono Package'
ProgressEnd "Creating $runtime Package for $framework"
}
PackageMacOS()
{
ProgressStart 'Creating MacOS Package'
rm -rf $outputFolderMacOS
mkdir $outputFolderMacOS
local framework="$1"
echo "Copying Binaries"
cp -r $outputFolderLinux/* $outputFolderMacOS
ProgressStart "Creating MacOS Package for $framework"
echo "Adding Sonarr Launcher"
cp ./distribution/osx/Launcher/dist/Launcher $outputFolderMacOS/
mv $outputFolderMacOS/Sonarr.exe $outputFolderMacOS/Sonarr.exe.bak
mv $outputFolderMacOS/Launcher $outputFolderMacOS/Sonarr
mv $outputFolderMacOS/Sonarr.exe.bak $outputFolderMacOS/Sonarr.exe
chmod +x $outputFolderMacOS/Sonarr
local folder=$artifactsFolder/macos/$framework/Sonarr
echo "Adding Sonarr.Update Launcher"
cp ./distribution/osx/Launcher/dist/Launcher $outputFolderMacOS/Sonarr.Update/
mv $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe.bak
mv $outputFolderMacOS/Sonarr.Update/Launcher $outputFolderMacOS/Sonarr.Update/Sonarr.Update
mv $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe.bak $outputFolderMacOS/Sonarr.Update/Sonarr.Update.exe
chmod +x $outputFolderMacOS/Sonarr.Update/Sonarr.Update
PackageFiles "$folder" "$framework" "osx-x64"
echo "Adding sqlite dylibs"
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOS
echo "Removing Service helpers"
rm -f $folder/ServiceUninstall.*
rm -f $folder/ServiceInstall.*
echo "Adding MediaInfo dylib"
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOS
echo "Removing Sonarr.Windows"
rm $folder/Sonarr.Windows.*
echo "Adding Sonarr.Mono to UpdatePackage"
cp $folder/Sonarr.Mono.* $folder/Sonarr.Update
if [ "$framework" = "net6.0" ]; then
cp $folder/Mono.Posix.NETStandard.* $folder/Sonarr.Update
cp $folder/libMonoPosixHelper.* $folder/Sonarr.Update
fi
ProgressEnd 'Creating MacOS Package'
}
PackageMacOSApp()
{
ProgressStart 'Creating macOS App Package'
local framework="$1"
ProgressStart "Creating macOS App Package for $framework"
rm -rf $outputFolderMacOSApp
mkdir $outputFolderMacOSApp
cp -r ./distribution/osx/Sonarr.app $outputFolderMacOSApp
mkdir -p $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
local folder=$artifactsFolder/macos-app/$framework
echo "Adding Sonarr Launcher"
cp ./distribution/osx/Launcher/dist/Launcher $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/
mv $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Launcher $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr
chmod +x $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr
rm -rf $folder
mkdir -p $folder
cp -r distribution/osx/Sonarr.app $folder
mkdir -p $folder/Sonarr.app/Contents/MacOS
echo "Copying Binaries"
mkdir -p $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin
cp -r $outputFolderLinux/* $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin/
echo "Adding sqlite dylibs"
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin/
echo "Adding MediaInfo dylib"
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin/
cp -r $artifactsFolder/macos/$framework/Sonarr/* $folder/Sonarr.app/Contents/MacOS
echo "Removing Update Folder"
rm -r $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/bin/Sonarr.Update
echo "# Do Not Edit\nPackageVersion=${BUILD_NUMBER}\nPackageAuthor=[Team Sonarr](https://sonarr.tv)\nReleaseVersion=${BUILD_NUMBER}\nUpdateMethod=$PackageUpdater\nBranch=${Branch:-master}" > $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/package_info
rm -r $folder/Sonarr.app/Contents/MacOS/Sonarr.Update
ProgressEnd 'Creating macOS App Package'
}
PackageTestsMono()
{
ProgressStart 'Creating Mono Test Package'
rm -rf $testPackageFolderLinux
echo "Copying Binaries"
cp -r $testPackageFolder $testPackageFolderLinux
if [ $runtime = "dotnet" ] ; then
$nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
else
mono $nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderLinux
fi
echo "Creating MDBs"
CreateMdbs $testPackageFolderLinux
echo "Removing PDBs"
find $testPackageFolderLinux -name "*.pdb" -exec rm "{}" \;
PatchMono $testPackageFolderLinux
echo "Adding Sonarr.Core.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolderLinux
# Remove Http binding redirect by renaming it
# We don't need this anymore once our minimum mono version is 5.10
sed -i "s/System.Net.Http/System.Net.Http.Mono/g" $testPackageFolderLinux/Sonarr.Common.Test.dll.config
cp ./test.sh $testPackageFolderLinux/
dos2unix $testPackageFolderLinux/test.sh
echo "Removing Sonarr.Windows"
rm $testPackageFolderLinux/Sonarr.Windows.*
rm -f $testPackageFolderLinux/*.log.config
CleanFolder $testPackageFolderLinux true
ProgressEnd 'Creating Linux Test Package'
}
PackageTestsWindows()
{
ProgressStart 'Creating Windows Test Package'
rm -rf $testPackageFolderWindows
echo "Copying Binaries"
cp -r $testPackageFolder $testPackageFolderWindows
if [ $runtime = "dotnet" ] ; then
$nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderWindows
else
mono $nuget install NUnit.ConsoleRunner -Version 3.10.0 -Output $testPackageFolderWindows
fi
cp ./test.sh $testPackageFolderWindows
echo "Removing Sonarr.Mono"
rm -f $testPackageFolderWindows/Sonarr.Mono.*
rm -f $testPackageFolderWindows/*.log.config
CleanFolder $testPackageFolderWindows true
ProgressEnd 'Creating Windows Test Package'
}
PackageWindows()
{
ProgressStart 'Creating Windows Package'
local framework="$1"
local runtime="$2"
ProgressStart "Creating Windows Package for $framework"
rm -rf $outputFolderWindows
local folder=$artifactsFolder/$runtime/$framework/Sonarr
echo "Copying Binaries"
cp -r $outputFolder $outputFolderWindows
PackageFiles "$folder" "$framework" "$runtime"
cp -r $outputFolder/$framework-windows/$runtime/publish/* $folder
echo "Removing Sonarr.Mono"
rm -f $outputFolderWindows/Sonarr.Mono.*
rm -f $folder/Sonarr.Mono.*
rm -f $folder/Mono.Posix.NETStandard.*
rm -f $folder/libMonoPosixHelper.*
echo "Adding Sonarr.Windows to UpdatePackage"
cp $outputFolderWindows/Sonarr.Windows.* $outputFolderWindows/$updateSubFolder/
cp $folder/Sonarr.Windows.* $folder/Sonarr.Update
ProgressEnd 'Creating Windows Package'
}
PublishArtifacts()
Package()
{
local framework="$1"
local runtime="$2"
local SPLIT
IFS='-' read -ra SPLIT <<< "$runtime"
case "${SPLIT[0]}" in
linux|freebsd*)
PackageLinux "$framework" "$runtime"
;;
win)
PackageWindows "$framework" "$runtime"
;;
osx)
PackageMacOS "$framework"
PackageMacOSApp "$framework"
;;
esac
}
PackageTests()
{
ProgressStart 'Publishing Artifacts'
local framework="$1"
local runtime="$2"
# Tests
echo "##teamcity[publishArtifacts '$testPackageFolderWindows/** => tests.windows.zip']"
echo "##teamcity[publishArtifacts '$testPackageFolderLinux/** => tests.linux.zip']"
cp test.sh "$testPackageFolder/$framework/$runtime/publish"
# Releases
echo "##teamcity[publishArtifacts '$outputFolderWindows/** => Sonarr.$BRANCH.$BUILD_NUMBER.windows.zip!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderLinux/** => Sonarr.$BRANCH.$BUILD_NUMBER.linux.tar.gz!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderMacOS/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.tar.gz!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderMacOSApp/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.zip']"
# Debian Package
echo "##teamcity[publishArtifacts 'distribution/** => distribution.zip']"
ProgressEnd 'Publishing Artifacts'
rm -f $testPackageFolder/$framework/$runtime/*.log.config
ProgressEnd 'Creating Test Package'
}
# Use mono or .net depending on OS
case "$(uname -s)" in
CYGWIN*|MINGW32*|MINGW64*|MSYS*)
# on windows, use dotnet
runtime="dotnet"
os="windows"
;;
*)
# otherwise use mono
runtime="mono"
os="posix"
;;
esac
UpdateVersionNumber
Build
CreateReleaseInfo
RunWebpack
PackageMono
PackageMacOS
PackageMacOSApp
PackageTestsMono
PackageTestsWindows
PackageWindows
PublishArtifacts
POSITIONAL=()
if [ $# -eq 0 ]; then
echo "No arguments provided, building everything"
BACKEND=YES
FRONTEND=YES
PACKAGES=YES
LINT=YES
ENABLE_BSD=NO
fi
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
--backend)
BACKEND=YES
shift # past argument
;;
--enable-bsd)
ENABLE_BSD=YES
shift # past argument
;;
-r|--runtime)
RID="$2"
shift # past argument
shift # past value
;;
-f|--framework)
FRAMEWORK="$2"
shift # past argument
shift # past value
;;
--frontend)
FRONTEND=YES
shift # past argument
;;
--packages)
PACKAGES=YES
shift # past argument
;;
--lint)
LINT=YES
shift # past argument
;;
--all)
BACKEND=YES
FRONTEND=YES
PACKAGES=YES
LINT=YES
shift # past argument
;;
*) # unknown option
POSITIONAL+=("$1") # save it in an array for later
shift # past argument
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters
if [ "$BACKEND" = "YES" ];
then
UpdateVersionNumber
if [ "$ENABLE_BSD" = "YES" ];
then
EnableBsdSupport
fi
Build
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
PackageTests "net6.0" "win-x64"
PackageTests "net6.0" "win-x86"
PackageTests "net6.0" "linux-x64"
PackageTests "net6.0" "linux-musl-x64"
PackageTests "net6.0" "osx-x64"
if [ "$ENABLE_BSD" = "YES" ];
then
PackageTests "net6.0" "freebsd-x64"
fi
else
PackageTests "$FRAMEWORK" "$RID"
fi
fi
if [ "$FRONTEND" = "YES" ];
then
YarnInstall
RunWebpack
fi
if [ "$LINT" = "YES" ];
then
if [ -z "$FRONTEND" ];
then
YarnInstall
fi
LintUI
fi
if [ "$PACKAGES" = "YES" ];
then
UpdateVersionNumber
if [[ -z "$RID" || -z "$FRAMEWORK" ]];
then
Package "net6.0" "win-x64"
Package "net6.0" "win-x86"
Package "net6.0" "linux-x64"
Package "net6.0" "linux-musl-x64"
Package "net6.0" "linux-arm64"
Package "net6.0" "linux-musl-arm64"
Package "net6.0" "linux-arm"
Package "net6.0" "osx-x64"
if [ "$ENABLE_BSD" = "YES" ];
then
Package "net6.0" "freebsd-x64"
fi
else
Package "$FRAMEWORK" "$RID"
fi
fi

@ -1,459 +0,0 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS

@ -1,28 +0,0 @@
Code reused from duplicati, licensed under LGPL 2.1
Modified for Sonarr by Taloth Saldono
see here for the original source: https://github.com/duplicati/duplicati/tree/679981d29f8a6e445d3c1e6d41e72a673ffaa653/Installer/OSX
License
-------
Sonarr as a whole is licensed under GPL 3.0 as specified in the git repository root.
But to preserve the original intent of the duplicati project, the modified versions of the sources in this folder are dual licensed under LGPL 2.1 and GPL 3.0.
Note: This exception can be freely removed in any copy of Sonarr sources as per LGPL/GPL licensing terms.
A copy of the LGPL 2.1 license is included in the LICENSE.LGPL.md file.
Purpose
-------
The Launcher is a bootstrap/shim application that checks if the appropriate version of mono is installed and subsequently use it to execute Sonarr.
By using a separate application, instead of a shell script, this allows the user to assign certain operating system permissions to Sonarr specifically.
Compiling the Launcher
----------------------
You need an OSX installation with xcode
Then run compile.sh in a terminal
The generated dist/Launcher can be renamed to Sonarr and Sonarr.Update to serve as shims to run Sonarr.exe and Sonarr.Update.exe respectively.

Binary file not shown.

@ -1,32 +0,0 @@
#import "run-with-mono.h"
#import "PFMoveApplication.h"
int const MONO_VERSION_MAJOR = 5;
int const MONO_VERSION_MINOR = 20;
int main() {
@autoreleasepool {
// Use our own executable name so the same compiled binary to be used for forks
NSString * const FileName = NSProcessInfo.processInfo.arguments[0].lastPathComponent;
// Sonarr.Update.exe
NSString * const ASSEMBLY = [NSString stringWithFormat:@"%@.exe", FileName];
// Sonarr Update
NSString * const APP_NAME = [FileName stringByReplacingOccurrencesOfString:@"." withString:@" "];
// -sonarrupdate
NSString * const PROCESS_NAME = [NSString stringWithFormat:@"-%@", [FileName stringByReplacingOccurrencesOfString:@"." withString:@""].lowercaseString];
@try
{
PFMoveToApplicationsFolderIfNecessary();
}
@catch (NSException * ex)
{
NSLog(@"Translocation/Quarantine check failed, starting normally. Reason: %@", ex.reason);
}
return [RunWithMono runAssemblyWithMono:APP_NAME procnamesuffix:PROCESS_NAME assembly:ASSEMBLY major:MONO_VERSION_MAJOR minor:MONO_VERSION_MINOR];
}
}

@ -1,32 +0,0 @@
//
// PFMoveApplication.h, version 1.24
// LetsMove
//
// Created by Andy Kim at Potion Factory LLC on 9/17/09
//
// The contents of this file are dedicated to the public domain.
#ifdef __cplusplus
extern "C" {
#endif
#import <Foundation/Foundation.h>
/**
Moves the running application to ~/Applications or /Applications if the former does not exist.
After the move, it relaunches app from the new location.
DOES NOT work for sandboxed applications.
Call from \c NSApplication's delegate method \c -applicationWillFinishLaunching: method. */
void PFMoveToApplicationsFolderIfNecessary(void);
/**
Check whether an app move is currently in progress.
Returns YES if LetsMove is currently in-progress trying to move the app to the Applications folder, or NO otherwise.
This can be used to work around a crash with apps that terminate after last window is closed.
See https://github.com/potionfactory/LetsMove/issues/64 for details. */
BOOL PFMoveIsInProgress(void);
#ifdef __cplusplus
}
#endif

@ -1,565 +0,0 @@
//
// PFMoveApplication.m, version 1.24
// LetsMove
//
// Created by Andy Kim at Potion Factory LLC on 9/17/09
//
// The contents of this file are dedicated to the public domain.
#import "PFMoveApplication.h"
#import <AppKit/AppKit.h>
#import <Security/Security.h>
#import <dlfcn.h>
#import <sys/mount.h>
@interface LetsMove : NSObject
@end
@implementation LetsMove
+ (NSBundle *)bundle {
return [NSBundle bundleForClass:self];
}
@end
// Strings
// These are macros to be able to use custom i18n tools
#define _I10NS(nsstr) NSLocalizedStringFromTableInBundle(nsstr, @"MoveApplication", [LetsMove bundle], nil)
#define kStrMoveApplicationCouldNotMove _I10NS(@"Could not move to Applications folder")
#define kStrMoveApplicationQuestionTitle _I10NS(@"Move to Applications folder?")
#define kStrMoveApplicationQuestionTitleHome _I10NS(@"Move to Applications folder in your Home folder?")
#define kStrMoveApplicationQuestionMessage _I10NS(@"I can move myself to the Applications folder if you'd like.")
#define kStrMoveApplicationButtonMove _I10NS(@"Move to Applications Folder")
#define kStrMoveApplicationButtonDoNotMove _I10NS(@"Do Not Move")
#define kStrMoveApplicationQuestionInfoWillRequirePasswd _I10NS(@"Note that this will require an administrator password.")
#define kStrMoveApplicationQuestionInfoInDownloadsFolder _I10NS(@"This will keep your Downloads folder uncluttered.")
// Needs to be defined for compiling under 10.5 SDK
#ifndef NSAppKitVersionNumber10_5
#define NSAppKitVersionNumber10_5 949
#endif
// By default, we use a small control/font for the suppression button.
// If you prefer to use the system default (to match your other alerts),
// set this to 0.
#define PFUseSmallAlertSuppressCheckbox 1
static NSString *AlertSuppressKey = @"moveToApplicationsFolderAlertSuppress";
static BOOL MoveInProgress = NO;
// Helper functions
static NSString *PreferredInstallLocation(BOOL *isUserDirectory);
static BOOL IsInApplicationsFolder(NSString *path);
static BOOL IsInDownloadsFolder(NSString *path);
static BOOL IsApplicationAtPathRunning(NSString *path);
static BOOL IsApplicationAtPathNested(NSString *path);
static NSString *ContainingDiskImageDevice(NSString *path);
static BOOL Trash(NSString *path);
static BOOL DeleteOrTrash(NSString *path);
static BOOL AuthorizedInstall(NSString *srcPath, NSString *dstPath, BOOL *canceled);
static BOOL CopyBundle(NSString *srcPath, NSString *dstPath);
static NSString *ShellQuotedString(NSString *string);
static void Relaunch(NSString *destinationPath);
// Main worker function
void PFMoveToApplicationsFolderIfNecessary(void) {
// Make sure to do our work on the main thread.
// Apparently Electron apps need this for things to work properly.
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{
PFMoveToApplicationsFolderIfNecessary();
});
return;
}
// Skip if user suppressed the alert before
if ([[NSUserDefaults standardUserDefaults] boolForKey:AlertSuppressKey]) return;
// Path of the bundle
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
// Check if the bundle is embedded in another application
BOOL isNestedApplication = IsApplicationAtPathNested(bundlePath);
// Skip if the application is already in some Applications folder,
// unless it's inside another app's bundle.
if (IsInApplicationsFolder(bundlePath) && !isNestedApplication) return;
// OK, looks like we'll need to do a move - set the status variable appropriately
MoveInProgress = YES;
// File Manager
NSFileManager *fm = [NSFileManager defaultManager];
// Are we on a disk image?
NSString *diskImageDevice = ContainingDiskImageDevice(bundlePath);
// Since we are good to go, get the preferred installation directory.
BOOL installToUserApplications = NO;
NSString *applicationsDirectory = PreferredInstallLocation(&installToUserApplications);
NSString *bundleName = [bundlePath lastPathComponent];
NSString *destinationPath = [applicationsDirectory stringByAppendingPathComponent:bundleName];
// Check if we need admin password to write to the Applications directory
BOOL needAuthorization = ([fm isWritableFileAtPath:applicationsDirectory] == NO);
// Check if the destination bundle is already there but not writable
needAuthorization |= ([fm fileExistsAtPath:destinationPath] && ![fm isWritableFileAtPath:destinationPath]);
// Setup the alert
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
{
NSString *informativeText = nil;
[alert setMessageText:(installToUserApplications ? kStrMoveApplicationQuestionTitleHome : kStrMoveApplicationQuestionTitle)];
informativeText = kStrMoveApplicationQuestionMessage;
if (needAuthorization) {
informativeText = [informativeText stringByAppendingString:@" "];
informativeText = [informativeText stringByAppendingString:kStrMoveApplicationQuestionInfoWillRequirePasswd];
}
else if (IsInDownloadsFolder(bundlePath)) {
// Don't mention this stuff if we need authentication. The informative text is long enough as it is in that case.
informativeText = [informativeText stringByAppendingString:@" "];
informativeText = [informativeText stringByAppendingString:kStrMoveApplicationQuestionInfoInDownloadsFolder];
}
[alert setInformativeText:informativeText];
// Add accept button
[alert addButtonWithTitle:kStrMoveApplicationButtonMove];
// Add deny button
NSButton *cancelButton = [alert addButtonWithTitle:kStrMoveApplicationButtonDoNotMove];
[cancelButton setKeyEquivalent:[NSString stringWithFormat:@"%C", 0x1b]]; // Escape key
// Setup suppression button
[alert setShowsSuppressionButton:YES];
if (PFUseSmallAlertSuppressCheckbox) {
NSCell *cell = [[alert suppressionButton] cell];
[cell setControlSize:NSSmallControlSize];
[cell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
}
}
// Activate app -- work-around for focus issues related to "scary file from internet" OS dialog.
if (![NSApp isActive]) {
[NSApp activateIgnoringOtherApps:YES];
}
if ([alert runModal] == NSAlertFirstButtonReturn) {
NSLog(@"INFO -- Moving myself to the Applications folder");
// Move
if (needAuthorization) {
BOOL authorizationCanceled;
if (!AuthorizedInstall(bundlePath, destinationPath, &authorizationCanceled)) {
if (authorizationCanceled) {
NSLog(@"INFO -- Not moving because user canceled authorization");
MoveInProgress = NO;
return;
}
else {
NSLog(@"ERROR -- Could not copy myself to /Applications with authorization");
goto fail;
}
}
}
else {
// If a copy already exists in the Applications folder, put it in the Trash
if ([fm fileExistsAtPath:destinationPath]) {
// But first, make sure that it's not running
if (IsApplicationAtPathRunning(destinationPath)) {
// Give the running app focus and terminate myself
NSLog(@"INFO -- Switching to an already running version");
[[NSTask launchedTaskWithLaunchPath:@"/usr/bin/open" arguments:[NSArray arrayWithObject:destinationPath]] waitUntilExit];
MoveInProgress = NO;
exit(0);
}
else {
if (!Trash([applicationsDirectory stringByAppendingPathComponent:bundleName]))
goto fail;
}
}
if (!CopyBundle(bundlePath, destinationPath)) {
NSLog(@"ERROR -- Could not copy myself to %@", destinationPath);
goto fail;
}
}
// Trash the original app. It's okay if this fails.
// NOTE: This final delete does not work if the source bundle is in a network mounted volume.
// Calling rm or file manager's delete method doesn't work either. It's unlikely to happen
// but it'd be great if someone could fix this.
if (!isNestedApplication && diskImageDevice == nil && !DeleteOrTrash(bundlePath)) {
NSLog(@"WARNING -- Could not delete application after moving it to Applications folder");
}
// Relaunch.
Relaunch(destinationPath);
// Launched from within a disk image? -- unmount (if no files are open after 5 seconds,
// otherwise leave it mounted).
if (diskImageDevice && !isNestedApplication) {
NSString *script = [NSString stringWithFormat:@"(/bin/sleep 5 && /usr/bin/hdiutil detach %@) &", ShellQuotedString(diskImageDevice)];
[NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]];
}
MoveInProgress = NO;
exit(0);
}
// Save the alert suppress preference if checked
else if ([[alert suppressionButton] state] == NSOnState) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:AlertSuppressKey];
}
MoveInProgress = NO;
return;
fail:
{
// Show failure message
alert = [[[NSAlert alloc] init] autorelease];
[alert setMessageText:kStrMoveApplicationCouldNotMove];
[alert runModal];
MoveInProgress = NO;
}
}
BOOL PFMoveIsInProgress() {
return MoveInProgress;
}
#pragma mark -
#pragma mark Helper Functions
static NSString *PreferredInstallLocation(BOOL *isUserDirectory) {
// Return the preferred install location.
// Assume that if the user has a ~/Applications folder, they'd prefer their
// applications to go there.
NSFileManager *fm = [NSFileManager defaultManager];
/*
NSArray *userApplicationsDirs = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES);
if ([userApplicationsDirs count] > 0) {
NSString *userApplicationsDir = [userApplicationsDirs objectAtIndex:0];
BOOL isDirectory;
if ([fm fileExistsAtPath:userApplicationsDir isDirectory:&isDirectory] && isDirectory) {
// User Applications directory exists. Get the directory contents.
NSArray *contents = [fm contentsOfDirectoryAtPath:userApplicationsDir error:NULL];
// Check if there is at least one ".app" inside the directory.
for (NSString *contentsPath in contents) {
if ([[contentsPath pathExtension] isEqualToString:@"app"]) {
if (isUserDirectory) *isUserDirectory = YES;
return [userApplicationsDir stringByResolvingSymlinksInPath];
}
}
}
}
*/
// No user Applications directory in use. Return the machine local Applications directory
if (isUserDirectory) *isUserDirectory = NO;
return [[NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSLocalDomainMask, YES) lastObject] stringByResolvingSymlinksInPath];
}
static BOOL IsInApplicationsFolder(NSString *path) {
// Check all the normal Application directories
NSArray *applicationDirs = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSAllDomainsMask, YES);
for (NSString *appDir in applicationDirs) {
if ([path hasPrefix:appDir]) return YES;
}
// Also, handle the case that the user has some other Application directory (perhaps on a separate data partition).
if ([[path pathComponents] containsObject:@"Applications"]) return YES;
return NO;
}
static BOOL IsInDownloadsFolder(NSString *path) {
NSArray *downloadDirs = NSSearchPathForDirectoriesInDomains(NSDownloadsDirectory, NSAllDomainsMask, YES);
for (NSString *downloadsDirPath in downloadDirs) {
if ([path hasPrefix:downloadsDirPath]) return YES;
}
return NO;
}
static BOOL IsApplicationAtPathRunning(NSString *bundlePath) {
bundlePath = [bundlePath stringByStandardizingPath];
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
// Use the new API on 10.6 or higher to determine if the app is already running
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5) {
for (NSRunningApplication *runningApplication in [[NSWorkspace sharedWorkspace] runningApplications]) {
NSString *runningAppBundlePath = [[[runningApplication bundleURL] path] stringByStandardizingPath];
if ([runningAppBundlePath isEqualToString:bundlePath]) {
return YES;
}
}
return NO;
}
#endif
// Use the shell to determine if the app is already running on systems 10.5 or lower
NSString *script = [NSString stringWithFormat:@"/bin/ps ax -o comm | /usr/bin/grep %@/ | /usr/bin/grep -v grep >/dev/null", ShellQuotedString(bundlePath)];
NSTask *task = [NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]];
[task waitUntilExit];
// If the task terminated with status 0, it means that the final grep produced 1 or more lines of output.
// Which means that the app is already running
return [task terminationStatus] == 0;
}
static BOOL IsApplicationAtPathNested(NSString *path) {
NSString *containingPath = [path stringByDeletingLastPathComponent];
NSArray *components = [containingPath pathComponents];
for (NSString *component in components) {
if ([[component pathExtension] isEqualToString:@"app"]) {
return YES;
}
}
return NO;
}
static NSString *ContainingDiskImageDevice(NSString *path) {
NSString *containingPath = [path stringByDeletingLastPathComponent];
struct statfs fs;
if (statfs([containingPath fileSystemRepresentation], &fs) || (fs.f_flags & MNT_ROOTFS))
return nil;
NSString *device = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:fs.f_mntfromname length:strlen(fs.f_mntfromname)];
NSTask *hdiutil = [[[NSTask alloc] init] autorelease];
[hdiutil setLaunchPath:@"/usr/bin/hdiutil"];
[hdiutil setArguments:[NSArray arrayWithObjects:@"info", @"-plist", nil]];
[hdiutil setStandardOutput:[NSPipe pipe]];
[hdiutil launch];
[hdiutil waitUntilExit];
NSData *data = [[[hdiutil standardOutput] fileHandleForReading] readDataToEndOfFile];
NSDictionary *info = nil;
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5) {
info = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL error:NULL];
}
else {
#endif
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_10
info = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:NULL errorDescription:NULL];
#endif
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5
}
#endif
if (![info isKindOfClass:[NSDictionary class]]) return nil;
NSArray *images = (NSArray *)[info objectForKey:@"images"];
if (![images isKindOfClass:[NSArray class]]) return nil;
for (NSDictionary *image in images) {
if (![image isKindOfClass:[NSDictionary class]]) return nil;
id systemEntities = [image objectForKey:@"system-entities"];
if (![systemEntities isKindOfClass:[NSArray class]]) return nil;
for (NSDictionary *systemEntity in systemEntities) {
if (![systemEntity isKindOfClass:[NSDictionary class]]) return nil;
NSString *devEntry = [systemEntity objectForKey:@"dev-entry"];
if (![devEntry isKindOfClass:[NSString class]]) return nil;
if ([devEntry isEqualToString:device])
return device;
}
}
return nil;
}
static BOOL Trash(NSString *path) {
BOOL result = NO;
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8
if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_8) {
result = [[NSFileManager defaultManager] trashItemAtURL:[NSURL fileURLWithPath:path] resultingItemURL:NULL error:NULL];
}
#endif
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11
if (!result) {
result = [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation
source:[path stringByDeletingLastPathComponent]
destination:@""
files:[NSArray arrayWithObject:[path lastPathComponent]]
tag:NULL];
}
#endif
// As a last resort try trashing with AppleScript.
// This allows us to trash the app in macOS Sierra even when the app is running inside
// an app translocation image.
if (!result) {
NSAppleScript *appleScript = [[[NSAppleScript alloc] initWithSource:
[NSString stringWithFormat:@"\
set theFile to POSIX file \"%@\" \n\
tell application \"Finder\" \n\
move theFile to trash \n\
end tell", path]] autorelease];
NSDictionary *errorDict = nil;
NSAppleEventDescriptor *scriptResult = [appleScript executeAndReturnError:&errorDict];
if (scriptResult == nil) {
NSLog(@"Trash AppleScript error: %@", errorDict);
}
result = (scriptResult != nil);
}
if (!result) {
NSLog(@"ERROR -- Could not trash '%@'", path);
}
return result;
}
static BOOL DeleteOrTrash(NSString *path) {
NSError *error;
if ([[NSFileManager defaultManager] removeItemAtPath:path error:&error]) {
return YES;
}
else {
// Don't log warning if on Sierra and running inside App Translocation path
if ([path rangeOfString:@"/AppTranslocation/"].location == NSNotFound)
NSLog(@"WARNING -- Could not delete '%@': %@", path, [error localizedDescription]);
return Trash(path);
}
}
static BOOL AuthorizedInstall(NSString *srcPath, NSString *dstPath, BOOL *canceled) {
if (canceled) *canceled = NO;
// Make sure that the destination path is an app bundle. We're essentially running 'sudo rm -rf'
// so we really don't want to fuck this up.
if (![[dstPath pathExtension] isEqualToString:@"app"]) return NO;
// Do some more checks
if ([[dstPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) return NO;
if ([[srcPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) return NO;
int pid, status;
AuthorizationRef myAuthorizationRef;
// Get the authorization
OSStatus err = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &myAuthorizationRef);
if (err != errAuthorizationSuccess) return NO;
AuthorizationItem myItems = {kAuthorizationRightExecute, 0, NULL, 0};
AuthorizationRights myRights = {1, &myItems};
AuthorizationFlags myFlags = (AuthorizationFlags)(kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize);
err = AuthorizationCopyRights(myAuthorizationRef, &myRights, NULL, myFlags, NULL);
if (err != errAuthorizationSuccess) {
if (err == errAuthorizationCanceled && canceled)
*canceled = YES;
goto fail;
}
static OSStatus (*security_AuthorizationExecuteWithPrivileges)(AuthorizationRef authorization, const char *pathToTool,
AuthorizationFlags options, char * const *arguments,
FILE **communicationsPipe) = NULL;
if (!security_AuthorizationExecuteWithPrivileges) {
// On 10.7, AuthorizationExecuteWithPrivileges is deprecated. We want to still use it since there's no
// good alternative (without requiring code signing). We'll look up the function through dyld and fail
// if it is no longer accessible. If Apple removes the function entirely this will fail gracefully. If
// they keep the function and throw some sort of exception, this won't fail gracefully, but that's a
// risk we'll have to take for now.
security_AuthorizationExecuteWithPrivileges = (OSStatus (*)(AuthorizationRef, const char*,
AuthorizationFlags, char* const*,
FILE **)) dlsym(RTLD_DEFAULT, "AuthorizationExecuteWithPrivileges");
}
if (!security_AuthorizationExecuteWithPrivileges) goto fail;
// Delete the destination
{
char *args[] = {"-rf", (char *)[dstPath fileSystemRepresentation], NULL};
err = security_AuthorizationExecuteWithPrivileges(myAuthorizationRef, "/bin/rm", kAuthorizationFlagDefaults, args, NULL);
if (err != errAuthorizationSuccess) goto fail;
// Wait until it's done
pid = wait(&status);
if (pid == -1 || !WIFEXITED(status)) goto fail; // We don't care about exit status as the destination most likely does not exist
}
// Copy
{
char *args[] = {"-pR", (char *)[srcPath fileSystemRepresentation], (char *)[dstPath fileSystemRepresentation], NULL};
err = security_AuthorizationExecuteWithPrivileges(myAuthorizationRef, "/bin/cp", kAuthorizationFlagDefaults, args, NULL);
if (err != errAuthorizationSuccess) goto fail;
// Wait until it's done
pid = wait(&status);
if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status)) goto fail;
}
AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
return YES;
fail:
AuthorizationFree(myAuthorizationRef, kAuthorizationFlagDefaults);
return NO;
}
static BOOL CopyBundle(NSString *srcPath, NSString *dstPath) {
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error = nil;
if ([fm copyItemAtPath:srcPath toPath:dstPath error:&error]) {
return YES;
}
else {
NSLog(@"ERROR -- Could not copy '%@' to '%@' (%@)", srcPath, dstPath, error);
return NO;
}
}
static NSString *ShellQuotedString(NSString *string) {
return [NSString stringWithFormat:@"'%@'", [string stringByReplacingOccurrencesOfString:@"'" withString:@"'\\''"]];
}
static void Relaunch(NSString *destinationPath) {
// The shell script waits until the original app process terminates.
// This is done so that the relaunched app opens as the front-most app.
int pid = [[NSProcessInfo processInfo] processIdentifier];
// Command run just before running open /final/path
NSString *preOpenCmd = @"";
NSString *quotedDestinationPath = ShellQuotedString(destinationPath);
// OS X >=10.5:
// Before we launch the new app, clear xattr:com.apple.quarantine to avoid
// duplicate "scary file from the internet" dialog.
if (floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_5) {
// Add the -r flag on 10.6
preOpenCmd = [NSString stringWithFormat:@"/usr/bin/xattr -d -r com.apple.quarantine %@", quotedDestinationPath];
}
else {
preOpenCmd = [NSString stringWithFormat:@"/usr/bin/xattr -d com.apple.quarantine %@", quotedDestinationPath];
}
NSString *script = [NSString stringWithFormat:@"(while /bin/kill -0 %d >&/dev/null; do /bin/sleep 0.1; done; %@; /usr/bin/open %@) &", pid, preOpenCmd, quotedDestinationPath];
[NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:[NSArray arrayWithObjects:@"-c", script, nil]];
}

@ -1,16 +0,0 @@
#!/bin/bash
# -fobjc-arc: enables ARC
# -fmodules: enables modules so you can import with `@import AppKit;`
# -mmacosx-version-min=10.6: support older OS X versions, this might increase the binary size
if [ ! -d "../dist" ]; then mkdir ../dist; fi
clang PFMoveApplication.m -fno-objc-arc -fmodules -mmacosx-version-min=10.6 -c -o PFMoveApplication.o
clang run-with-mono.m Launcher.m PFMoveApplication.o -fobjc-arc -fmodules -mmacosx-version-min=10.6 -o ../dist/Launcher
rm PFMoveApplication.o
if [ "$1" == "install" ] && [ "$2" != "" ]; then
echo "Installing to $2"
cp ../dist/Launcher $2
chmod +x $2
fi

@ -1,11 +0,0 @@
@import Foundation;
@import AppKit;
@interface RunWithMono : NSObject {
}
+ (void) openDownloadLink:(NSButton*)button;
+ (bool) showDownloadMonoDialog:(NSString *)appName major:(int)major minor:(int)minor;
+ (int) runAssemblyWithMono:(NSString *)appName procnamesuffix:(NSString *)procnamesuffix assembly:(NSString *)assembly major:(int) major minor:(int) minor;
@end

@ -1,258 +0,0 @@
#import "run-with-mono.h"
@import Foundation;
@import AppKit;
NSString * const VERSION_TITLE = @"Cannot launch %@";
NSString * const VERSION_MSG = @"%@ requires the Mono Framework version %d.%d or later.";
NSString * const DOWNLOAD_URL = @"http://www.mono-project.com/download/stable/#download-mac";
// Helper method to see if the user has requested debug output
bool D() {
NSString* v = [[[NSProcessInfo processInfo]environment]objectForKey:@"DEBUG"];
if (v == nil || v.length == 0 || [v isEqual:@"0"] || [v isEqual:@"false"] || [v isEqual:@"f"])
return false;
return true;
}
// Wrapper method to invoke commandline operations and return the string output
NSString *runCommand(NSString *program, NSArray<NSString *> *arguments) {
NSPipe *pipe = [NSPipe pipe];
NSFileHandle *file = pipe.fileHandleForReading;
NSTask *task = [[NSTask alloc] init];
task.launchPath = program;
task.arguments = arguments;
task.standardOutput = pipe;
[task launch];
NSData *data = [file readDataToEndOfFile];
[file closeFile];
[task waitUntilExit];
NSString *cmdOutput = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
if (cmdOutput == nil || cmdOutput.length == 0)
return nil;
return [cmdOutput stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
// Checks if the Mono version is greater than or equal to the desired version
bool isValidMono(NSString *mono, int major, int minor) {
NSFileManager *fileManager = [NSFileManager defaultManager];
if (mono == nil)
return false;
if (![fileManager fileExistsAtPath:mono] || ![fileManager isExecutableFileAtPath:mono])
return false;
NSString *versionInfo = runCommand(mono, @[@"--version"]);
NSRange rg = [versionInfo rangeOfString:@"Mono JIT compiler version \\d+\\.\\d+" options:NSRegularExpressionSearch];
if (rg.location != NSNotFound) {
versionInfo = [versionInfo substringWithRange:rg];
if (D()) NSLog(@"Matched version: %@", versionInfo);
rg = [versionInfo rangeOfString:@"\\d+\\.\\d+" options:NSRegularExpressionSearch];
if (rg.location != NSNotFound) {
versionInfo = [versionInfo substringWithRange:rg];
if (D()) NSLog(@"Matched version: %@", versionInfo);
NSArray<NSString *> *versionComponents = [versionInfo componentsSeparatedByString:@"."];
if ([versionComponents[0] intValue] < major)
return false;
if ([versionComponents[0] intValue] == major && [versionComponents[1] intValue] < minor)
return false;
return true;
}
}
return false;
}
// Attempts to locate a mono with a valid version
NSString *findMono(int major, int minor) {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *currentMono = runCommand(@"/usr/bin/which", @[@"mono"]);
if (D()) NSLog(@"which mono: %@", currentMono);
if (isValidMono(currentMono, major, minor)) {
if (D()) NSLog(@"Found mono with: %@", currentMono);
return currentMono;
}
NSArray *probepaths = @[@"/usr/local/bin/mono", @"/Library/Frameworks/Mono.framework/Versions/Current/bin/mono", @"/opt/local/bin/mono"];
for(NSString* probepath in probepaths) {
if (D()) NSLog(@"Trying mono with: %@", probepath);
if (isValidMono(probepath, major, minor)) {
if (D()) NSLog(@"Found mono with: %@", probepath);
return probepath;
}
}
if (D()) NSLog(@"Failed to find Mono, returning: %@", nil);
return nil;
}
// Check Bundle for quarantine
void checkBundle() {
NSString * const bundlePath = [[NSBundle mainBundle] bundlePath];
NSString * const attributes = runCommand(@"/usr/bin/xattr", @[@"-l", bundlePath]);
if (D()) NSLog(@"Attributes: %@", attributes);
if ([attributes containsString:@"com.apple.quarantine:"]) {
runCommand(@"/usr/bin/xattr", @[@"-dr", @"com.apple.quarantine", bundlePath]);
NSLog(@"Removed quarantine attribute from bundle");
}
}
@implementation RunWithMono
+ (void) openDownloadLink:(NSButton*)button {
if (D()) NSLog(@"Clicked Download");
runCommand(@"/usr/bin/open", @[DOWNLOAD_URL]);
}
// Shows the download dialog, prompting to download Mono
+ (bool) showDownloadMonoDialog:(NSString *)appName major:(int)major minor:(int)minor {
NSAlert *alert = [[NSAlert alloc] init];
[alert setInformativeText:[NSString stringWithFormat:VERSION_MSG, appName, major, minor]];
[alert setMessageText:[NSString stringWithFormat:VERSION_TITLE, appName]];
[alert addButtonWithTitle:@"Cancel"];
[alert addButtonWithTitle:@"Retry"];
[alert addButtonWithTitle:@"Download"];
NSButton *downloadButton = [[alert buttons] objectAtIndex:2];
[downloadButton setTarget:self];
[downloadButton setAction:@selector(openDownloadLink:)];
NSModalResponse btn = [alert runModal];
if (btn == NSAlertFirstButtonReturn) {
if (D()) NSLog(@"Clicked Cancel");
return true;
}
else if (btn == NSAlertSecondButtonReturn) {
if (D()) NSLog(@"Clicked Retry");
return false;
}
return true;
}
// Top-level method, finds Mono with an appropriate version and launches the assembly
+ (int) runAssemblyWithMono: (NSString *)appName procnamesuffix:(NSString *)procnamesuffix assembly:(NSString *)assembly major:(int) major minor:(int) minor {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *assemblyPath;
bool found = false;
NSString *localPath = NSProcessInfo.processInfo.arguments[0].stringByDeletingLastPathComponent;
NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
NSArray *paths = @[
localPath,
[NSString pathWithComponents:@[localPath, @"bin"]],
resourcePath,
[NSString pathWithComponents:@[resourcePath, @"bin"]]
];
for (NSString* entryFolder in paths) {
if (D()) NSLog(@"Checking folder: %@", entryFolder);
assemblyPath = [NSString pathWithComponents:@[entryFolder, assembly]];
if ([fileManager fileExistsAtPath:assemblyPath]) {
found = true;
break;
}
}
if (!found) {
NSLog(@"Assembly file not found");
return 1;
}
if (D()) NSLog(@"assemblyPath: %@", assemblyPath);
checkBundle();
NSString *currentMono = findMono(major, minor);
while (currentMono == nil) {
NSLog(@"No valid mono found!");
bool close = [self showDownloadMonoDialog:appName major:major minor:minor];
if (close)
return 1;
currentMono = findMono(major, minor);
}
// Setup dylib fallback loading
NSMutableArray * dylibPath = [NSMutableArray arrayWithObject:assemblyPath.stringByDeletingLastPathComponent];
// Update the PATH to use the specified mono version
if ([currentMono hasPrefix:@"/"])
{
NSString * curMonoBinDir = currentMono.stringByDeletingLastPathComponent;
NSString * curMonoDir = curMonoBinDir.stringByDeletingLastPathComponent;
NSString * curMonoLibDir = [NSString pathWithComponents:@[curMonoDir, @"lib"]];
NSString * curEnvPath = [NSString stringWithUTF8String:getenv("PATH")];
NSString * newEnvPath = [NSString stringWithFormat:@"%@:%@", curMonoBinDir, curEnvPath];
setenv("PATH", newEnvPath.UTF8String, 1);
[dylibPath addObject:curMonoLibDir];
NSLog(@"Added %@ to PATH", curMonoBinDir);
}
// Setup libsqlite?
/* if [[ -f '/opt/local/lib/libsqlite3.0.dylib' ]]; then
export DYLD_FALLBACK_LIBRARY_PATH="/opt/local/lib:$DYLD_FALLBACK_LIBRARY_PATH"
fi
*/
[dylibPath addObjectsFromArray:@[@"$HOME/lib", @"/usr/local/lib", @"/lib", @"/usr/lib"]];
setenv("DYLD_FALLBACK_LIBRARY_PATH", [dylibPath componentsJoinedByString:@":"].UTF8String, 1);
if (D()) NSLog(@"Running %@ --debug %@", currentMono, assemblyPath);
// Copy commandline arguments
NSMutableArray* arguments = [[NSMutableArray alloc] init];
// Disabled suffix for now coz it's confusing and not preserved on in-app restart
[arguments addObject:currentMono];
//[arguments addObject:[currentMono stringByAppendingString:procnamesuffix]];
[arguments addObject:@"--debug"];
[arguments addObjectsFromArray:[[NSProcessInfo processInfo] arguments]];
// replace the executable-path with the assembly path
[arguments replaceObjectAtIndex:2 withObject:assemblyPath];
// Try switch to mono using execv
char * cPath = strdup([currentMono UTF8String]);
char ** cArgs;
char ** pArgNext = cArgs = malloc(sizeof(*cArgs) * ([arguments count] + 1));
for (NSString *s in arguments) {
*pArgNext++ = strdup([s UTF8String]);
}
*pArgNext = NULL;
int ret = execv(cPath, cArgs);
if (ret != 0)
NSLog(@"Failed execv with errno @d", errno);
// execv failed, cleanup
pArgNext = cArgs;
for (NSString *s in arguments) {
free(*pArgNext++);
}
free(cArgs);
free(cPath);
return -1;
}
@end

@ -1,62 +0,0 @@
#!/bin/sh
#get the bundle's MacOS directory full path
DIR=$(cd "$(dirname "$0")"; pwd)
#change these values to match your app
EXE_PATH="$DIR/Sonarr.exe"
APPNAME="Sonarr"
#set up environment
if [[ -x '/opt/local/bin/mono' ]]; then
# Macports and mono-supplied installer path
export PATH="/opt/local/bin:$PATH"
elif [[ -x '/usr/local/bin/mono' ]]; then
# Homebrew-supplied path to mono
export PATH="/usr/local/bin:$PATH"
fi
export DYLD_FALLBACK_LIBRARY_PATH="$DIR"
if [ -e /Library/Frameworks/Mono.framework ]; then
MONO_FRAMEWORK_PATH=/Library/Frameworks/Mono.framework/Versions/Current
export PATH="$MONO_FRAMEWORK_PATH/bin:$PATH"
export DYLD_FALLBACK_LIBRARY_PATH="$DYLD_FALLBACK_LIBRARY_PATH:$MONO_FRAMEWORK_PATH/lib"
fi
if [[ -f '/opt/local/lib/libsqlite3.0.dylib' ]]; then
export DYLD_FALLBACK_LIBRARY_PATH="/opt/local/lib:$DYLD_FALLBACK_LIBRARY_PATH"
fi
export DYLD_FALLBACK_LIBRARY_PATH="$DYLD_FALLBACK_LIBRARY_PATH:$HOME/lib:/usr/local/lib:/lib:/usr/lib"
#mono version check
REQUIRED_MAJOR=4
REQUIRED_MINOR=6
VERSION_TITLE="Cannot launch $APPNAME"
VERSION_MSG="$APPNAME requires Mono Runtime Environment(MRE) $REQUIRED_MAJOR.$REQUIRED_MINOR or later."
DOWNLOAD_URL="http://www.mono-project.com/download/#download-mac"
MONO_VERSION="$(mono --version | grep 'Mono JIT compiler version ' | cut -f5 -d\ )"
# if [[ -o DEBUG ]]; then osascript -e "display dialog \"MONO_VERSION: $MONO_VERSION\""; fi
MONO_VERSION_MAJOR="$(echo $MONO_VERSION | cut -f1 -d.)"
MONO_VERSION_MINOR="$(echo $MONO_VERSION | cut -f2 -d.)"
if [ -z "$MONO_VERSION" ] \
|| [ $MONO_VERSION_MAJOR -lt $REQUIRED_MAJOR ] \
|| [ $MONO_VERSION_MAJOR -eq $REQUIRED_MAJOR -a $MONO_VERSION_MINOR -lt $REQUIRED_MINOR ]
then
osascript \
-e "set question to display dialog \"$VERSION_MSG\" with title \"$VERSION_TITLE\" buttons {\"Cancel\", \"Download...\"} default button 2" \
-e "if button returned of question is equal to \"Download...\" then open location \"$DOWNLOAD_URL\""
echo "$VERSION_TITLE"
echo "$VERSION_MSG"
exit 1
fi
MONO_EXEC="exec mono --debug"
#run app using mono
$MONO_EXEC "$EXE_PATH"

@ -7,8 +7,9 @@
#define ForumsURL "https://forums.sonarr.tv/"
#define AppExeName "Sonarr.exe"
#define BuildNumber "3.0"
#define BuildNumber GetEnv('BUILD_NUMBER')
#define BranchName GetEnv('BRANCH')
#define Framework GetEnv('FRAMEWORK')
#define Runtime GetEnv('RUNTIME')
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
@ -16,7 +17,7 @@
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{56C1065D-3523-4025-B76D-6F73F67F7F71}
AppName={#AppName}
AppVersion=3.0
AppVersion={#BuildNumber}
AppPublisher={#AppPublisher}
AppPublisherURL={#AppURL}
AppSupportURL={#ForumsURL}
@ -26,7 +27,7 @@ DefaultDirName={commonappdata}\Sonarr\bin
DisableDirPage=yes
DefaultGroupName={#AppName}
DisableProgramGroupPage=yes
OutputBaseFilename=Sonarr.{#BranchName}.{#BuildNumber}.windows
OutputBaseFilename=Sonarr.{#BranchName}.{#BuildNumber}.windows.{#Framework}
SolidCompression=yes
AppCopyright=Creative Commons 3.0 License
AllowUNCPath=False
@ -49,8 +50,8 @@ Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
[Files]
Source: "..\..\..\_output_windows\Sonarr.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\..\_output_windows\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Sonarr\Sonarr.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\..\..\_artifacts\{#Runtime}\{#Framework}\Sonarr\*"; Excludes: "Sonarr.Update"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
@ -80,26 +81,3 @@ begin
Exec('net', 'stop nzbdrone', '', 0, ewWaitUntilTerminated, ResultCode)
Exec('sc', 'delete nzbdrone', '', 0, ewWaitUntilTerminated, ResultCode)
end;
function Framework472IsNotInstalled(): Boolean;
var
bSuccess: Boolean;
regVersion: Cardinal;
begin
Result := True;
bSuccess := RegQueryDWordValue(HKLM, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', regVersion);
if (True = bSuccess) and (regVersion >= 461808) then begin
Result := False;
end;
end;
function InitializeSetup(): Boolean;
begin
if Framework472IsNotInstalled() then begin
MsgBox('Sonarr requires Microsoft .NET Framework 4.7.2 or higher.'#13#13
'Please use Windows Update to install this version'#13
'or download it from https://dotnet.microsoft.com/download/dotnet-framework.', mbInformation, MB_OK);
result := false;
end else
result := true;
end;

@ -18,7 +18,8 @@ class About extends Component {
version,
packageVersion,
packageAuthor,
isMonoRuntime,
isNetCore,
isDocker,
runtimeVersion,
appData,
startupPath,
@ -45,14 +46,18 @@ class About extends Component {
}
{
isMonoRuntime ?
isNetCore &&
<DescriptionListItem
title="Mono Version"
data={runtimeVersion}
/> :
title=".Net Version"
data={`Yes (${runtimeVersion})`}
/>
}
{
isDocker &&
<DescriptionListItem
title=".net Version"
data={runtimeVersion}
title="Docker"
data={'Yes'}
/>
}
@ -92,8 +97,9 @@ About.propTypes = {
version: PropTypes.string.isRequired,
packageVersion: PropTypes.string,
packageAuthor: PropTypes.string,
isMonoRuntime: PropTypes.bool.isRequired,
isNetCore: PropTypes.bool.isRequired,
runtimeVersion: PropTypes.string.isRequired,
isDocker: PropTypes.bool.isRequired,
appData: PropTypes.string.isRequired,
startupPath: PropTypes.string.isRequired,
mode: PropTypes.string.isRequired,

@ -29,7 +29,7 @@
"@fortawesome/free-regular-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/react-fontawesome": "0.1.14",
"@microsoft/signalr": "5.0.5",
"@microsoft/signalr": "6.0.3",
"@sentry/browser": "6.3.1",
"@sentry/integrations": "6.3.1",
"classnames": "2.3.1",

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Dictionary>
<Words>
<Recognized>
<Word>Ack</Word>
<Word>Minifier</Word>
<Word>Jsonp</Word>
<Word>Linktionary</Word>
<Word>Scaleout</Word>
<Word>Redis</Word>
<Word>Owin</Word>
<Word>Stringify</Word>
<Word>Unminify</Word>
<Word>Unminified</Word>
<Word>Stateful</Word>
<Word>SignalR</Word>
<Word>Hubservable</Word>
<Word>Sse</Word>
<Word>GitHub</Word>
</Recognized>
</Words>
</Dictionary>

@ -1,20 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Reflection;
using System.Resources;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyCompany("Microsoft Open Technologies, Inc.")]
[assembly: AssemblyCopyright("© Microsoft Open Technologies, Inc. All rights reserved.")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyConfiguration("")]
[assembly: ComVisible(false)]
[assembly: CLSCompliant(false)]
[assembly: NeutralResourcesLanguage("en-US")]

@ -1,5 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System.Reflection;
[assembly: AssemblyVersion("10.0.0.*")]

@ -1,18 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
//
// To add a suppression to this file, right-click the message in the
// Code Analysis results, point to "Suppress Message", and click
// "In Suppression File".
// You do not need to add suppressions to this file manually.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Strong naming is done on the CI.")]
[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "param", Scope = "resource", Target = "Microsoft.AspNet.SignalR.Resources.resources")]
[assembly: SuppressMessage("Microsoft.Usage", "CA2243:AttributeStringLiteralsShouldParseCorrectly", Justification = "We use semver")]
[assembly: SuppressMessage("Microsoft.Design", "CA1065:DoNotRaiseExceptionsInUnexpectedLocations", Scope = "member", Target = "Microsoft.AspNet.SignalR.Messaging.ScaleoutTaskQueue.#.cctor()", Justification = "The task is cached")]

@ -1,227 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="New Rule Set" Description=" " ToolsVersion="11.0">
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
<Rule Id="CA1000" Action="Error" />
<Rule Id="CA1001" Action="Error" />
<Rule Id="CA1002" Action="Error" />
<Rule Id="CA1003" Action="Error" />
<Rule Id="CA1004" Action="Error" />
<Rule Id="CA1005" Action="Error" />
<Rule Id="CA1007" Action="Error" />
<Rule Id="CA1008" Action="Error" />
<Rule Id="CA1010" Action="Error" />
<Rule Id="CA1011" Action="Error" />
<Rule Id="CA1012" Action="Error" />
<Rule Id="CA1013" Action="Error" />
<Rule Id="CA1014" Action="Error" />
<Rule Id="CA1016" Action="Error" />
<Rule Id="CA1017" Action="Error" />
<Rule Id="CA1018" Action="Error" />
<Rule Id="CA1019" Action="Error" />
<Rule Id="CA1021" Action="Error" />
<Rule Id="CA1023" Action="Error" />
<Rule Id="CA1024" Action="Error" />
<Rule Id="CA1025" Action="Error" />
<Rule Id="CA1026" Action="Error" />
<Rule Id="CA1027" Action="Error" />
<Rule Id="CA1028" Action="Error" />
<Rule Id="CA1030" Action="Error" />
<Rule Id="CA1031" Action="Error" />
<Rule Id="CA1032" Action="Error" />
<Rule Id="CA1034" Action="Error" />
<Rule Id="CA1035" Action="Error" />
<Rule Id="CA1036" Action="Error" />
<Rule Id="CA1038" Action="Error" />
<Rule Id="CA1039" Action="Error" />
<Rule Id="CA1040" Action="Error" />
<Rule Id="CA1041" Action="Error" />
<Rule Id="CA1043" Action="Error" />
<Rule Id="CA1044" Action="Error" />
<Rule Id="CA1045" Action="Error" />
<Rule Id="CA1046" Action="Error" />
<Rule Id="CA1047" Action="Error" />
<Rule Id="CA1048" Action="Error" />
<Rule Id="CA1049" Action="Error" />
<Rule Id="CA1050" Action="Error" />
<Rule Id="CA1051" Action="Error" />
<Rule Id="CA1052" Action="Error" />
<Rule Id="CA1053" Action="Error" />
<Rule Id="CA1057" Action="Error" />
<Rule Id="CA1058" Action="Error" />
<Rule Id="CA1059" Action="Error" />
<Rule Id="CA1060" Action="Error" />
<Rule Id="CA1061" Action="Error" />
<Rule Id="CA1062" Action="Error" />
<Rule Id="CA1063" Action="Error" />
<Rule Id="CA1064" Action="Error" />
<Rule Id="CA1065" Action="Error" />
<Rule Id="CA1300" Action="Error" />
<Rule Id="CA1301" Action="Error" />
<Rule Id="CA1302" Action="Error" />
<Rule Id="CA1303" Action="Error" />
<Rule Id="CA1304" Action="Error" />
<Rule Id="CA1305" Action="Error" />
<Rule Id="CA1306" Action="Error" />
<Rule Id="CA1307" Action="Error" />
<Rule Id="CA1308" Action="Error" />
<Rule Id="CA1309" Action="Error" />
<Rule Id="CA1400" Action="Error" />
<Rule Id="CA1401" Action="Error" />
<Rule Id="CA1402" Action="Error" />
<Rule Id="CA1403" Action="Error" />
<Rule Id="CA1404" Action="Error" />
<Rule Id="CA1405" Action="Error" />
<Rule Id="CA1406" Action="Error" />
<Rule Id="CA1407" Action="Error" />
<Rule Id="CA1408" Action="Error" />
<Rule Id="CA1409" Action="Error" />
<Rule Id="CA1410" Action="Error" />
<Rule Id="CA1411" Action="Error" />
<Rule Id="CA1412" Action="Error" />
<Rule Id="CA1413" Action="Error" />
<Rule Id="CA1414" Action="Error" />
<Rule Id="CA1415" Action="Error" />
<Rule Id="CA1500" Action="Error" />
<Rule Id="CA1501" Action="Error" />
<Rule Id="CA1502" Action="Error" />
<Rule Id="CA1504" Action="Error" />
<Rule Id="CA1505" Action="Error" />
<Rule Id="CA1506" Action="Error" />
<Rule Id="CA1600" Action="Error" />
<Rule Id="CA1601" Action="Error" />
<Rule Id="CA1700" Action="Error" />
<Rule Id="CA1701" Action="Error" />
<Rule Id="CA1702" Action="Error" />
<Rule Id="CA1703" Action="Error" />
<Rule Id="CA1704" Action="Error" />
<Rule Id="CA1707" Action="Error" />
<Rule Id="CA1708" Action="Error" />
<Rule Id="CA1709" Action="Error" />
<Rule Id="CA1712" Action="Error" />
<Rule Id="CA1713" Action="Error" />
<Rule Id="CA1714" Action="Error" />
<Rule Id="CA1715" Action="Error" />
<Rule Id="CA1716" Action="Error" />
<Rule Id="CA1717" Action="Error" />
<Rule Id="CA1719" Action="Error" />
<Rule Id="CA1720" Action="Error" />
<Rule Id="CA1721" Action="Error" />
<Rule Id="CA1722" Action="Error" />
<Rule Id="CA1724" Action="Error" />
<Rule Id="CA1725" Action="Error" />
<Rule Id="CA1726" Action="Error" />
<Rule Id="CA1800" Action="Error" />
<Rule Id="CA1801" Action="Error" />
<Rule Id="CA1802" Action="Error" />
<Rule Id="CA1804" Action="Error" />
<Rule Id="CA1806" Action="Error" />
<Rule Id="CA1809" Action="Error" />
<Rule Id="CA1810" Action="Error" />
<Rule Id="CA1811" Action="Error" />
<Rule Id="CA1812" Action="Error" />
<Rule Id="CA1813" Action="Error" />
<Rule Id="CA1814" Action="Error" />
<Rule Id="CA1815" Action="Error" />
<Rule Id="CA1819" Action="Error" />
<Rule Id="CA1820" Action="Error" />
<Rule Id="CA1821" Action="Error" />
<Rule Id="CA1822" Action="Error" />
<Rule Id="CA1823" Action="Error" />
<Rule Id="CA1824" Action="Error" />
<Rule Id="CA1900" Action="Error" />
<Rule Id="CA1901" Action="Error" />
<Rule Id="CA1903" Action="Error" />
<Rule Id="CA2000" Action="Error" />
<Rule Id="CA2001" Action="Error" />
<Rule Id="CA2002" Action="Error" />
<Rule Id="CA2003" Action="Error" />
<Rule Id="CA2004" Action="Error" />
<Rule Id="CA2006" Action="Error" />
<Rule Id="CA2100" Action="Error" />
<Rule Id="CA2101" Action="Error" />
<Rule Id="CA2102" Action="Error" />
<Rule Id="CA2103" Action="Error" />
<Rule Id="CA2104" Action="Error" />
<Rule Id="CA2105" Action="Error" />
<Rule Id="CA2106" Action="Error" />
<Rule Id="CA2107" Action="Error" />
<Rule Id="CA2108" Action="Error" />
<Rule Id="CA2109" Action="Error" />
<Rule Id="CA2111" Action="Error" />
<Rule Id="CA2112" Action="Error" />
<Rule Id="CA2114" Action="Error" />
<Rule Id="CA2115" Action="Error" />
<Rule Id="CA2116" Action="Error" />
<Rule Id="CA2117" Action="Error" />
<Rule Id="CA2118" Action="Error" />
<Rule Id="CA2119" Action="Error" />
<Rule Id="CA2120" Action="Error" />
<Rule Id="CA2121" Action="Error" />
<Rule Id="CA2122" Action="Error" />
<Rule Id="CA2123" Action="Error" />
<Rule Id="CA2124" Action="Error" />
<Rule Id="CA2126" Action="Error" />
<Rule Id="CA2130" Action="Error" />
<Rule Id="CA2131" Action="Error" />
<Rule Id="CA2132" Action="Error" />
<Rule Id="CA2133" Action="Error" />
<Rule Id="CA2134" Action="Error" />
<Rule Id="CA2135" Action="Error" />
<Rule Id="CA2136" Action="Error" />
<Rule Id="CA2137" Action="Error" />
<Rule Id="CA2138" Action="Error" />
<Rule Id="CA2139" Action="Error" />
<Rule Id="CA2140" Action="Error" />
<Rule Id="CA2141" Action="Error" />
<Rule Id="CA2142" Action="Error" />
<Rule Id="CA2143" Action="Error" />
<Rule Id="CA2144" Action="Error" />
<Rule Id="CA2145" Action="Error" />
<Rule Id="CA2146" Action="Error" />
<Rule Id="CA2147" Action="Error" />
<Rule Id="CA2149" Action="Error" />
<Rule Id="CA2151" Action="Error" />
<Rule Id="CA2200" Action="Error" />
<Rule Id="CA2201" Action="Error" />
<Rule Id="CA2202" Action="Error" />
<Rule Id="CA2204" Action="Error" />
<Rule Id="CA2205" Action="Error" />
<Rule Id="CA2207" Action="Error" />
<Rule Id="CA2208" Action="Error" />
<Rule Id="CA2210" Action="Error" />
<Rule Id="CA2211" Action="Error" />
<Rule Id="CA2212" Action="Error" />
<Rule Id="CA2213" Action="Error" />
<Rule Id="CA2214" Action="Error" />
<Rule Id="CA2215" Action="Error" />
<Rule Id="CA2216" Action="Error" />
<Rule Id="CA2217" Action="Error" />
<Rule Id="CA2218" Action="Error" />
<Rule Id="CA2219" Action="Error" />
<Rule Id="CA2220" Action="Error" />
<Rule Id="CA2221" Action="Error" />
<Rule Id="CA2222" Action="Error" />
<Rule Id="CA2223" Action="Error" />
<Rule Id="CA2224" Action="Error" />
<Rule Id="CA2225" Action="Error" />
<Rule Id="CA2226" Action="Error" />
<Rule Id="CA2227" Action="Error" />
<Rule Id="CA2228" Action="Error" />
<Rule Id="CA2229" Action="Error" />
<Rule Id="CA2230" Action="Error" />
<Rule Id="CA2231" Action="Error" />
<Rule Id="CA2232" Action="Error" />
<Rule Id="CA2233" Action="Error" />
<Rule Id="CA2234" Action="Error" />
<Rule Id="CA2235" Action="Error" />
<Rule Id="CA2236" Action="Error" />
<Rule Id="CA2237" Action="Error" />
<Rule Id="CA2238" Action="Error" />
<Rule Id="CA2239" Action="Error" />
<Rule Id="CA2240" Action="Error" />
<Rule Id="CA2241" Action="Error" />
<Rule Id="CA2242" Action="Error" />
<Rule Id="CA2243" Action="Error" />
<Rule Id="CA5122" Action="Error" />
</Rules>
</RuleSet>

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(ArtifactsDir)' != ''">
<OutputPath Condition="$(UseBinPath) == ''">$(ArtifactsDir)\$(MSBuildProjectName)</OutputPath>
<OutputPath Condition="$(UseBinPath) == 'true'">$(ArtifactsDir)\$(MSBuildProjectName)\bin</OutputPath>
</PropertyGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)Microsoft.AspNet.SignalR.ruleset</CodeAnalysisRuleSet>
<RunCodeAnalysis Condition="'$(RunCodeAnalysis)' == ''">false</RunCodeAnalysis>
<NoWarn>1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition="'$(RunCodeAnalysis)' == 'true'">
<DefineConstants>$(DefineConstants);CODE_ANALYSIS</DefineConstants>
<VisualStudioVersion>11.0</VisualStudioVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(OS)' != 'Windows_NT'">
<DefineConstants>$(DefineConstants);MONO</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(KeyFile)' != '' And '$(DisableSigning)' != 'true'">
<DefineConstants>$(DefineConstants);SIGNED</DefineConstants>
<SignAssembly>true</SignAssembly>
<DelaySign>true</DelaySign>
<AssemblyOriginatorKeyFile>$(KeyFile)</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup Condition="Exists('$(MSBuildThisFileDirectory)GlobalSuppressions.cs')">
<Compile Include="$(MSBuildThisFileDirectory)GlobalSuppressions.cs">
<Link>GlobalSuppressions.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="$(MSBuildThisFileDirectory)CodeAnalysisDictionary.xml" />
</ItemGroup>
</Project>

@ -1,6 +1,13 @@
<Project>
<!-- Common to all Sonarr Projects -->
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<PlatformTarget>AnyCPU</PlatformTarget>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<RuntimeIdentifiers>win-x64;win-x86;osx-x64;linux-x64;linux-musl-x64;linux-arm;linux-arm64;linux-musl-arm64</RuntimeIdentifiers>
<SonarrRootDir>$(MSBuildThisFileDirectory)..\</SonarrRootDir>
<!-- Specifies the type of output -->
@ -17,6 +24,10 @@
<SonarrProject Condition="$(MSBuildProjectName.StartsWith('Sonarr'))">true</SonarrProject>
<SonarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceInstall'))">true</SonarrProject>
<SonarrProject Condition="$(MSBuildProjectName.StartsWith('ServiceUninstall'))">true</SonarrProject>
<!-- A test project gets the test sdk packages automatically added -->
<TestProject>false</TestProject>
<TestProject Condition="$(MSBuildProjectName.EndsWith('.Test'))">true</TestProject>
</PropertyGroup>
<PropertyGroup>
@ -32,12 +43,11 @@
<OutputPath Condition="'$(SonarrOutputType)'=='Update'">$(SonarrRootDir)_output\Sonarr.Update\</OutputPath>
<!-- Paths relative to project file for better readability -->
<EnableBaseIntermediateOutputPathMismatchWarning>false</EnableBaseIntermediateOutputPathMismatchWarning>
<BaseIntermediateOutputPath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(BaseIntermediateOutputPath)'))</BaseIntermediateOutputPath>
<IntermediateOutputPath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(IntermediateOutputPath)'))</IntermediateOutputPath>
<OutputPath>$([MSBuild]::MakeRelative('$(MSBuildProjectDirectory)', '$(OutputPath)'))</OutputPath>
<!-- below net4.7.1 the new portable pdb format has no line numbers, pdb to mdb probably doesn't like it either -->
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
@ -45,17 +55,19 @@
<PropertyGroup Condition="'$(SonarrOutputType)'=='Test'">
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<SelfContained>false</SelfContained>
</PropertyGroup>
<!-- Set the Product and Version info for our own projects -->
<PropertyGroup Condition="'$(SonarrProject)'=='true'">
<Product>Sonarr</Product>
<Company>sonarr.tv</Company>
<Copyright>Copyright 2010-$([System.DateTime]::Now.ToString('yyyy')) sonarr.tv (GNU General Public v3)</Copyright>
<Copyright>Copyright 2014-$([System.DateTime]::Now.ToString('yyyy')) sonarr.tv (GNU General Public v3)</Copyright>
<!-- Should be replaced by CI -->
<AssemblyVersion>10.0.0.*</AssemblyVersion>
<AssemblyConfiguration>$(Configuration)-dev</AssemblyConfiguration>
<NeutralLanguage>en</NeutralLanguage>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
@ -71,10 +83,64 @@
</ItemGroup>
<PropertyGroup>
<!-- We don't want separate framework directories till we go dotnet core -->
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<!-- For now keep the NzbDrone namespace -->
<RootNamespace Condition="'$(SonarrProject)'=='true'">$(MSBuildProjectName.Replace('Sonarr','NzbDrone'))</RootNamespace>
</PropertyGroup>
</Project>
<!-- Standard testing packages -->
<ItemGroup Condition="'$(TestProject)'=='true'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NunitXml.TestLogger" Version="2.1.62" />
</ItemGroup>
<ItemGroup Condition="'$(TestProject)'=='true' and '$(TargetFramework)'=='net6.0'">
<PackageReference Include="coverlet.collector" Version="3.0.4-preview.27.ge7cb7c3b40" />
</ItemGroup>
<PropertyGroup Condition="'$(SonarrProject)'=='true' and '$(EnableAnalyzers)'=='false'">
<!-- FXCop Built into Net5 SDK now as NETAnalyzers, Enabled by default on net5 projects -->
<EnableNETAnalyzers>false</EnableNETAnalyzers>
</PropertyGroup>
<!--
Set runtime identifier to local system type if not specified
-->
<Choose>
<When Condition="'$(OS)' == 'Windows_NT'">
<PropertyGroup>
<IsWindows>true</IsWindows>
</PropertyGroup>
</When>
<When Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::OSX)))' == 'true'">
<PropertyGroup>
<IsOSX>true</IsOSX>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<IsLinux>true</IsLinux>
</PropertyGroup>
</Otherwise>
</Choose>
<PropertyGroup Condition="'$(IsWindows)' == 'true' and
'$(RuntimeIdentifier)' == ''">
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)' == 'true' and
'$(RuntimeIdentifier)' == ''">
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
</PropertyGroup>
<PropertyGroup Condition="'$(IsOSX)' == 'true' and
'$(RuntimeIdentifier)' == ''">
<_UsingDefaultRuntimeIdentifier>true</_UsingDefaultRuntimeIdentifier>
<RuntimeIdentifier>osx-x64</RuntimeIdentifier>
</PropertyGroup>
</Project>

@ -0,0 +1,4 @@
<Project>
<Import Project="Targets/PublishAllRids.targets" />
<Import Project="Targets/CopyRuntimes.targets" />
</Project>

Binary file not shown.

@ -1,3 +0,0 @@
Copied from mono/4.5/Facades of the mono 5.4 release.
These are the mono version of the dotnet Core TypeForwardedTo assemblies.
Using these assemblies is no longer necessary once we reach mono 5.18 as minimum version

@ -38,15 +38,6 @@ namespace Marr.Data
private DbCommand _command;
/// <summary>
/// Initializes a DataMapper for the given provider type and connection string.
/// </summary>
/// <param name="providerName">Ex: </param>
/// <param name="connectionString">The database connection string.</param>
public DataMapper(string providerName, string connectionString)
: this(DbProviderFactories.GetFactory(providerName), connectionString)
{ }
/// <summary>
/// A database provider agnostic initialization.
/// </summary>

@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<Platforms>x86</Platforms>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<AssemblyVersion>3.17.0.0</AssemblyVersion>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>

@ -1,10 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<Platforms>x86</Platforms>
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NBuilder" Version="6.0.0" />
<PackageReference Include="NBuilder" Version="6.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Core\Sonarr.Core.csproj" />

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NLog;
@ -9,7 +10,7 @@ using NzbDrone.Automation.Test.PageModel;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Test.Common;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Remote;
namespace NzbDrone.Automation.Test
@ -34,7 +35,14 @@ namespace NzbDrone.Automation.Test
[OneTimeSetUp]
public void SmokeTestSetup()
{
driver = new FirefoxDriver();
var options = new ChromeOptions();
options.AddArguments("--headless");
var service = ChromeDriverService.CreateDefaultService();
// Timeout as windows automation tests seem to take alot longer to get going
driver = new ChromeDriver(service, options, new TimeSpan(0, 3, 0));
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger());
_runner.KillAll();
@ -56,6 +64,19 @@ namespace NzbDrone.Automation.Test
.Select(e => e.Text);
}
protected void TakeScreenshot(string name)
{
try
{
Screenshot image = ((ITakesScreenshot)driver).GetScreenshot();
image.SaveAsFile($"./{name}_test_screenshot.png", ScreenshotImageFormat.Png);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to save screenshot {name}, {ex.Message}");
}
}
[OneTimeTearDown]
public void SmokeTestTearDown()
{

@ -1,11 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<Platforms>x86</Platforms>
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Selenium.Firefox.WebDriver" Version="0.24.0" />
<PackageReference Include="Selenium.Support" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="88.0.4324.9600" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Sonarr.Test.Common.csproj" />

@ -439,7 +439,7 @@ namespace NzbDrone.Common.Test.DiskTests
[Test]
public void CopyFolder_should_not_copy_casesensitive_folder()
{
MonoOnly();
PosixOnly();
WithRealDiskProvider();
@ -540,9 +540,10 @@ namespace NzbDrone.Common.Test.DiskTests
}
[Test]
[Platform(Exclude = "MacOsX")]
public void MoveFolder_should_rename_casesensitive_folder()
{
MonoOnly();
PosixOnly();
WithRealDiskProvider();

@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Test.Common;
@ -19,7 +19,7 @@ namespace NzbDrone.Common.Test.EnsureTest
[TestCase(@"/var/user/file with, comma.mkv")]
public void EnsureLinuxPath(string path)
{
MonoOnly();
PosixOnly();
Ensure.That(path, () => path).IsValidPath();
}
}

@ -250,11 +250,6 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_follow_redirects_to_https()
{
if (typeof(TDispatcher) == typeof(ManagedHttpDispatcher) && PlatformInfo.IsMono)
{
Assert.Ignore("Will fail on tls1.2 via managed dispatcher, ignore.");
}
var request = new HttpRequestBuilder($"http://{_httpBinHost}/redirect-to")
.AddQueryParam("url", $"https://sonarr.tv/")
.Build();
@ -758,7 +753,7 @@ namespace NzbDrone.Common.Test.Http
{
try
{
string url = $"http://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeUriString(malformedCookie)}";
string url = $"http://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeDataString(malformedCookie)}";
var requestSet = new HttpRequest(url);
requestSet.AllowAutoRedirect = false;

@ -52,7 +52,7 @@ namespace NzbDrone.Common.Test
[TestCase(@"//CAPITAL//lower// ", @"/CAPITAL/lower")]
public void Clean_Path_Linux(string dirty, string clean)
{
MonoOnly();
PosixOnly();
var result = dirty.CleanFilePath();
result.Should().Be(clean);
@ -152,14 +152,14 @@ namespace NzbDrone.Common.Test
[TestCase(@"/test", "/")]
public void path_should_return_parent_mono(string path, string parentPath)
{
MonoOnly();
PosixOnly();
path.GetParentPath().Should().Be(parentPath);
}
[Test]
public void path_should_return_parent_for_oversized_path()
{
MonoOnly();
PosixOnly();
// This test will fail on Windows if long path support is not enabled: https://www.howtogeek.com/266621/how-to-make-windows-10-accept-file-paths-over-260-characters/
// It will also fail if the app isn't configured to use long path (such as resharper): https://blogs.msdn.microsoft.com/jeremykuhne/2016/07/30/net-4-6-2-and-long-paths-on-windows-10/
@ -223,7 +223,7 @@ namespace NzbDrone.Common.Test
public void get_actual_casing_should_return_actual_casing_for_local_dir_in_windows()
{
WindowsOnly();
var path = Directory.GetCurrentDirectory().Replace("c:\\","C:\\").Replace("system32", "System32");
var path = Directory.GetCurrentDirectory().Replace("c:\\", "C:\\").Replace("d:\\", "D:\\").Replace("system32", "System32");
path.ToUpper().GetActualCasing().Should().Be(path);
path.ToLower().GetActualCasing().Should().Be(path);
@ -232,7 +232,7 @@ namespace NzbDrone.Common.Test
[Test]
public void get_actual_casing_should_return_original_value_in_linux()
{
MonoOnly();
PosixOnly();
var path = Directory.GetCurrentDirectory();
path.GetActualCasing().Should().Be(path);
path.GetActualCasing().Should().Be(path);
@ -280,7 +280,7 @@ namespace NzbDrone.Common.Test
[Test]
public void GetUpdateClientExePath()
{
GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\sonarr_update\Sonarr.Update.exe".AsOsAgnostic());
GetIAppDirectoryInfo().GetUpdateClientExePath(PlatformType.DotNet).Should().BeEquivalentTo(@"C:\Temp\sonarr_update\Sonarr.Update.exe".AsOsAgnostic());
}
[Test]
@ -306,7 +306,7 @@ namespace NzbDrone.Common.Test
[Test]
public void GetAncestorFolders_should_return_all_ancestors_in_path_Linux()
{
MonoOnly();
PosixOnly();
var path = @"/Test/TV/Series Title";
var result = path.GetAncestorFolders();

@ -6,6 +6,7 @@ using System.Linq;
using System.Threading;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Model;
using NzbDrone.Common.Processes;
using NzbDrone.Test.Common;
@ -13,6 +14,7 @@ using NzbDrone.Test.Dummy;
namespace NzbDrone.Common.Test
{
[NonParallelizable]
[TestFixture]
public class ProcessProviderFixture : TestBase<ProcessProvider>
{
@ -65,19 +67,22 @@ namespace NzbDrone.Common.Test
}
[Test]
[Retry(3)]
public void Should_be_able_to_start_process()
{
var process = StartDummyProcess();
Thread.Sleep(500);
Subject.Exists(DummyApp.DUMMY_PROCCESS_NAME).Should()
.BeTrue("one running dummy process");
var check = Subject.GetProcessById(process.Id);
check.Should().NotBeNull();
process.Refresh();
process.HasExited.Should().BeFalse();
process.Kill();
process.WaitForExit();
Subject.Exists(DummyApp.DUMMY_PROCCESS_NAME).Should().BeFalse();
process.HasExited.Should().BeTrue();
}
[Test]
@ -131,6 +136,8 @@ namespace NzbDrone.Common.Test
[Test]
[Platform(Exclude = "MacOsX")]
[Retry(3)]
public void kill_all_should_kill_all_process_with_name()
{
var dummy1 = StartDummyProcess();
@ -146,8 +153,33 @@ namespace NzbDrone.Common.Test
private Process StartDummyProcess()
{
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, DummyApp.DUMMY_PROCCESS_NAME + ".exe");
return Subject.Start(path);
var processStarted = new ManualResetEventSlim();
string suffix;
if (OsInfo.IsWindows)
{
suffix = ".exe";
}
else
{
suffix = "";
}
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, DummyApp.DUMMY_PROCCESS_NAME + suffix);
var process = Subject.Start(path, onOutputDataReceived: (string data) =>
{
if (data.StartsWith("Dummy process. ID:"))
{
processStarted.Set();
}
});
if (!processStarted.Wait(5000))
{
Assert.Fail("Failed to start process within 2 sec");
}
return process;
}
[Test]

@ -1,17 +1,18 @@
using System;
using System.Security.Principal;
using System.ServiceProcess;
using System.Threading;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Processes;
using NzbDrone.Test.Common;
using NzbDrone.Test.Common.Categories;
namespace NzbDrone.Common.Test
{
[TestFixture]
[Timeout(15000)]
public class ServiceProviderTests : TestBase<ServiceProvider>
public class ServiceProviderFixture : TestBase<ServiceProvider>
{
private const string ALWAYS_INSTALLED_SERVICE = "SCardSvr"; //Smart Card
private const string TEMP_SERVICE_NAME = "NzbDrone_Nunit";
@ -20,6 +21,9 @@ namespace NzbDrone.Common.Test
public void Setup()
{
WindowsOnly();
Mocker.SetConstant<IProcessProvider>(Mocker.Resolve<ProcessProvider>());
CleanupService();
}
@ -71,6 +75,7 @@ namespace NzbDrone.Common.Test
Subject.Install(TEMP_SERVICE_NAME);
Subject.ServiceExist(TEMP_SERVICE_NAME).Should().BeTrue();
Subject.Uninstall(TEMP_SERVICE_NAME);
Thread.Sleep(2000);
Subject.ServiceExist(TEMP_SERVICE_NAME).Should().BeFalse();
ExceptionVerification.ExpectedWarns(1);

@ -1,14 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<Platforms>x86</Platforms>
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Host\Sonarr.Host.csproj" />
<ProjectReference Include="..\NzbDrone.Test.Common\Sonarr.Test.Common.csproj" />
<ProjectReference Include="..\NzbDrone.Test.Dummy\Sonarr.Test.Dummy.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.ServiceProcess" />
</ItemGroup>
</Project>

@ -10,6 +10,7 @@ using FluentAssertions;
namespace NzbDrone.Common.Test.TPLTests
{
[TestFixture]
[Platform(Exclude = "MacOsX")]
public class RateLimitServiceFixture : TestBase<RateLimitService>
{
private DateTime _epoch;

@ -85,7 +85,9 @@ namespace NzbDrone.Common
string fullZipToPath = Path.Combine(destination, entryFileName);
string directoryName = Path.GetDirectoryName(fullZipToPath);
if (directoryName.Length > 0)
{
Directory.CreateDirectory(directoryName);
}
// Unzip file in buffered chunks. This is just as fast as unpacking to a buffer the full size
// of the file, but does not waste memory.
@ -103,7 +105,7 @@ namespace NzbDrone.Common
Stream inStream = File.OpenRead(compressedFile);
Stream gzipStream = new GZipInputStream(inStream);
TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream);
TarArchive tarArchive = TarArchive.CreateInputTarArchive(gzipStream, null);
tarArchive.ExtractContents(destination);
tarArchive.Close();

@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Messaging;
using TinyIoC;
@ -21,15 +24,74 @@ namespace NzbDrone.Common.Composition
assemblies.Add(OsInfo.IsWindows ? "Sonarr.Windows" : "Sonarr.Mono");
assemblies.Add("Sonarr.Common");
foreach (var assembly in assemblies)
var startupPath = AppDomain.CurrentDomain.BaseDirectory;
foreach (var assemblyName in assemblies)
{
_loadedTypes.AddRange(Assembly.Load(assembly).GetExportedTypes());
_loadedTypes.AddRange(AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.Combine(startupPath, $"{assemblyName}.dll")).GetExportedTypes());
}
var toRegisterResolver = new List<string> { "System.Data.SQLite" };
toRegisterResolver.AddRange(assemblies.Intersect(new[] { "Sonarr.Core" }));
RegisterNativeResolver(toRegisterResolver);
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ContainerResolveEventHandler);
Container = new Container(new TinyIoCContainer(), _loadedTypes);
AutoRegisterInterfaces();
Container.Register(args);
}
}
private static Assembly ContainerResolveEventHandler(object sender, ResolveEventArgs args)
{
var resolver = new AssemblyDependencyResolver(args.RequestingAssembly.Location);
var assemblyPath = resolver.ResolveAssemblyToPath(new AssemblyName(args.Name));
if (assemblyPath == null)
{
return null;
}
return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
}
public static void RegisterNativeResolver(IEnumerable<string> assemblyNames)
{
// This ensures we look for sqlite3 using libsqlite3.so.0 on Linux and not libsqlite3.so which
// is less likely to exist.
foreach (var name in assemblyNames)
{
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"{name}.dll"));
try
{
NativeLibrary.SetDllImportResolver(assembly, LoadNativeLib);
}
catch (InvalidOperationException)
{
// This can only be set once per assembly
// Catch required for NzbDrone.Host tests
}
}
}
private static IntPtr LoadNativeLib(string libraryName, Assembly assembly, DllImportSearchPath? dllImportSearchPath)
{
var mappedName = libraryName;
if (OsInfo.IsLinux)
{
if (libraryName == "sqlite3")
{
mappedName = "libsqlite3.so.0";
}
else if (libraryName == "mediainfo")
{
mappedName = "libmediainfo.so.0";
}
}
return NativeLibrary.Load(mappedName, assembly, dllImportSearchPath);
}
private void AutoRegisterInterfaces()
{

@ -16,7 +16,7 @@
int bitBufferCount = 0;
int index = 0;
for (int i = 0; i < str.Length;i++ )
for (int i = 0; i < str.Length; i++)
{
bitBuffer = (bitBuffer << 5) | ValidChars.IndexOf(str[i]);
bitBufferCount += 5;

@ -1,7 +1,7 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.17626
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@ -9,6 +9,9 @@
//------------------------------------------------------------------------------
namespace NzbDrone.Common.EnsureThat.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
@ -16,7 +19,7 @@ namespace NzbDrone.Common.EnsureThat.Resources {
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class ExceptionMessages {

@ -47,7 +47,6 @@ namespace NzbDrone.Common.EnvironmentInfo
{
throw new SonarrStartupException("Cannot create AppFolder, Access to the path {0} is denied", _appFolderInfo.AppDataFolder);
}
if (OsInfo.IsWindows)
{
@ -82,8 +81,15 @@ namespace NzbDrone.Common.EnvironmentInfo
if (_startupContext.Args.ContainsKey(StartupContext.APPDATA))
{
if (_diskProvider.FileExists(_appFolderInfo.GetDatabase())) return;
if (!_diskProvider.FileExists(oldDbFile)) return;
if (_diskProvider.FileExists(_appFolderInfo.GetDatabase()))
{
return;
}
if (!_diskProvider.FileExists(oldDbFile))
{
return;
}
MoveSqliteDatabase(oldDbFile, _appFolderInfo.GetDatabase());
RemovePidFile();
@ -125,25 +131,33 @@ namespace NzbDrone.Common.EnvironmentInfo
private void InitializeMonoApplicationData()
{
if (OsInfo.IsWindows) return;
if (OsInfo.IsWindows)
{
return;
}
try
{
var configHome = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
if (configHome == "/.config" ||
configHome.EndsWith("/.config") && !_diskProvider.FolderExists(configHome.GetParentPath()) ||
// It seems that DoNotVerify is the mono behaviour even though .net docs specify a blank string
// should be returned if the data doesn't exist. For compatibility with .net core, explicitly
// set DoNotVerify (which makes sense given we're explicitly checking that the folder exists)
var configHome = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData, Environment.SpecialFolderOption.DoNotVerify);
if (configHome.IsNullOrWhiteSpace() ||
configHome == "/.config" ||
(configHome.EndsWith("/.config") && !_diskProvider.FolderExists(configHome.GetParentPath())) ||
!_diskProvider.FolderExists(configHome))
{
// Tell mono to use appData/.config as ApplicationData folder.
// Tell mono/netcore to use appData/.config as ApplicationData folder.
Environment.SetEnvironmentVariable("XDG_CONFIG_HOME", Path.Combine(_appFolderInfo.AppDataFolder, ".config"));
}
var dataHome = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (dataHome == "/.local/share" ||
dataHome.EndsWith("/.local/share") && !_diskProvider.FolderExists(dataHome.GetParentPath().GetParentPath()) ||
var dataHome = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.DoNotVerify);
if (dataHome.IsNullOrWhiteSpace() ||
dataHome == "/.local/share" ||
(dataHome.EndsWith("/.local/share") && !_diskProvider.FolderExists(dataHome.GetParentPath().GetParentPath())) ||
!_diskProvider.FolderExists(dataHome))
{
// Tell mono to use appData/.config/share as LocalApplicationData folder.
// Tell mono/netcore to use appData/.config/share as LocalApplicationData folder.
Environment.SetEnvironmentVariable("XDG_DATA_HOME", Path.Combine(_appFolderInfo.AppDataFolder, ".config/share"));
}
}

@ -16,7 +16,7 @@ namespace NzbDrone.Common.EnvironmentInfo
public class AppFolderInfo : IAppFolderInfo
{
private readonly Environment.SpecialFolder DATA_SPECIAL_FOLDER = Environment.SpecialFolder.CommonApplicationData;
private readonly Environment.SpecialFolder _dataSpecialFolder = Environment.SpecialFolder.CommonApplicationData;
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(AppFolderInfo));
@ -24,7 +24,7 @@ namespace NzbDrone.Common.EnvironmentInfo
{
if (OsInfo.IsNotWindows)
{
DATA_SPECIAL_FOLDER = Environment.SpecialFolder.ApplicationData;
_dataSpecialFolder = Environment.SpecialFolder.ApplicationData;
}
if (startupContext.Args.ContainsKey(StartupContext.APPDATA))
@ -34,8 +34,8 @@ namespace NzbDrone.Common.EnvironmentInfo
}
else
{
AppDataFolder = Path.Combine(Environment.GetFolderPath(DATA_SPECIAL_FOLDER, Environment.SpecialFolderOption.None), "Sonarr");
LegacyAppDataFolder = Path.Combine(Environment.GetFolderPath(DATA_SPECIAL_FOLDER, Environment.SpecialFolderOption.None), "NzbDrone");
AppDataFolder = Path.Combine(Environment.GetFolderPath(_dataSpecialFolder, Environment.SpecialFolderOption.DoNotVerify), "Sonarr");
LegacyAppDataFolder = Path.Combine(Environment.GetFolderPath(_dataSpecialFolder, Environment.SpecialFolderOption.DoNotVerify), "NzbDrone");
}
StartUpFolder = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName;

@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@ -30,7 +29,7 @@ namespace NzbDrone.Common.EnvironmentInfo
public static string AppName { get; } = "Sonarr";
public static Version Version { get; }
public static String Branch { get; }
public static string Branch { get; }
public static string Release { get; }
public static DateTime BuildDateTime
@ -54,4 +53,4 @@ namespace NzbDrone.Common.EnvironmentInfo
}
}
}
}
}

@ -6,4 +6,4 @@ namespace NzbDrone.Common.EnvironmentInfo
bool Enabled { get; }
OsVersionModel Read();
}
}
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using NLog;
@ -11,10 +12,13 @@ namespace NzbDrone.Common.EnvironmentInfo
public static Os Os { get; }
public static bool IsNotWindows => !IsWindows;
public static bool IsLinux => Os == Os.Linux;
public static bool IsLinux => Os == Os.Linux || Os == Os.LinuxMusl || Os == Os.Bsd;
public static bool IsOsx => Os == Os.Osx;
public static bool IsWindows => Os == Os.Windows;
// this needs to not be static so we can mock it
public bool IsDocker { get; }
public string Version { get; }
public string Name { get; }
public string FullName { get; }
@ -33,18 +37,7 @@ namespace NzbDrone.Common.EnvironmentInfo
case PlatformID.MacOSX:
case PlatformID.Unix:
{
// Sometimes Mac OS reports itself as Unix
if (Directory.Exists("/System/Library/CoreServices/") &&
(File.Exists("/System/Library/CoreServices/SystemVersion.plist") ||
File.Exists("/System/Library/CoreServices/ServerVersion.plist"))
)
{
Os = Os.Osx;
}
else
{
Os = Os.Linux;
}
Os = GetPosixFlavour();
break;
}
}
@ -82,6 +75,64 @@ namespace NzbDrone.Common.EnvironmentInfo
Name = Os.ToString();
FullName = Name;
}
if (IsLinux && File.Exists("/proc/1/cgroup") && File.ReadAllText("/proc/1/cgroup").Contains("/docker/"))
{
IsDocker = true;
}
}
private static Os GetPosixFlavour()
{
var output = RunAndCapture("uname", "-s");
if (output.StartsWith("Darwin"))
{
return Os.Osx;
}
else if (output.Contains("BSD"))
{
return Os.Bsd;
}
else
{
#if ISMUSL
return Os.LinuxMusl;
#else
return Os.Linux;
#endif
}
}
private static string RunAndCapture(string filename, string args)
{
var processStartInfo = new ProcessStartInfo
{
FileName = filename,
Arguments = args,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true
};
var output = string.Empty;
try
{
using (var p = Process.Start(processStartInfo))
{
// To avoid deadlocks, always read the output stream first and then wait.
output = p.StandardOutput.ReadToEnd();
p.WaitForExit(1000);
}
}
catch (Exception)
{
output = string.Empty;
}
return output;
}
}
@ -90,12 +141,15 @@ namespace NzbDrone.Common.EnvironmentInfo
string Version { get; }
string Name { get; }
string FullName { get; }
bool IsDocker { get; }
}
public enum Os
{
Windows,
Linux,
Osx
Osx,
LinuxMusl,
Bsd
}
}
}

@ -26,4 +26,4 @@ namespace NzbDrone.Common.EnvironmentInfo
public string FullName { get; }
public string Version { get; }
}
}
}

@ -1,7 +1,4 @@
using System;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Win32;
using System;
namespace NzbDrone.Common.EnvironmentInfo
{
@ -9,7 +6,8 @@ namespace NzbDrone.Common.EnvironmentInfo
public enum PlatformType
{
DotNet = 0,
Mono = 1
Mono = 1,
NetCore = 2
}
public interface IPlatformInfo
@ -19,28 +17,18 @@ namespace NzbDrone.Common.EnvironmentInfo
public class PlatformInfo : IPlatformInfo
{
private static readonly Regex MonoVersionRegex = new Regex(@"(?<=\W|^)(?<version>\d+\.\d+(\.\d+)?(\.\d+)?)(?=\W)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static PlatformType _platform;
private static Version _version;
static PlatformInfo()
{
if (Type.GetType("Mono.Runtime") != null)
{
_platform = PlatformType.Mono;
_version = GetMonoVersion();
}
else
{
_platform = PlatformType.DotNet;
_version = GetDotNetVersion();
}
_platform = PlatformType.NetCore;
_version = Environment.Version;
}
public static PlatformType Platform => _platform;
public static bool IsMono => Platform == PlatformType.Mono;
public static bool IsDotNet => Platform == PlatformType.DotNet;
public static bool IsNetCore => Platform == PlatformType.NetCore;
public static string PlatformName
{
@ -50,8 +38,10 @@ namespace NzbDrone.Common.EnvironmentInfo
{
return ".NET";
}
return "Mono";
else
{
return ".NET Core";
}
}
}
@ -61,98 +51,5 @@ namespace NzbDrone.Common.EnvironmentInfo
{
return _version;
}
private static Version GetMonoVersion()
{
try
{
var type = Type.GetType("Mono.Runtime");
if (type != null)
{
var displayNameMethod = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (displayNameMethod != null)
{
var displayName = displayNameMethod.Invoke(null, null).ToString();
var versionMatch = MonoVersionRegex.Match(displayName);
if (versionMatch.Success)
{
return new Version(versionMatch.Groups["version"].Value);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Couldnt get Mono version: " + ex.ToString());
}
return new Version();
}
private static Version GetDotNetVersion()
{
try
{
const string subkey = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\";
using (var ndpKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey(subkey))
{
if (ndpKey == null)
{
return new Version(4, 0);
}
var releaseKey = (int)ndpKey.GetValue("Release");
if (releaseKey >= 528040)
{
return new Version(4, 8, 0);
}
if (releaseKey >= 461808)
{
return new Version(4, 7, 2);
}
if (releaseKey >= 461308)
{
return new Version(4, 7, 1);
}
if (releaseKey >= 460798)
{
return new Version(4, 7);
}
if (releaseKey >= 394802)
{
return new Version(4, 6, 2);
}
if (releaseKey >= 394254)
{
return new Version(4, 6, 1);
}
if (releaseKey >= 393295)
{
return new Version(4, 6);
}
if (releaseKey >= 379893)
{
return new Version(4, 5, 2);
}
if (releaseKey >= 378675)
{
return new Version(4, 5, 1);
}
if (releaseKey >= 378389)
{
return new Version(4, 5);
}
}
}
catch (Exception ex)
{
Console.WriteLine("Couldnt get .NET framework version: " + ex.ToString());
}
return new Version(4, 0);
}
}
}

@ -23,13 +23,15 @@ namespace NzbDrone.Common.EnvironmentInfo
serviceProvider.ServiceExist(ServiceProvider.SERVICE_NAME) &&
serviceProvider.GetStatus(ServiceProvider.SERVICE_NAME) == ServiceControllerStatus.StartPending;
//Guarded to avoid issues when running in a non-managed process
var entry = Assembly.GetEntryAssembly();
// net6.0 will return Sonarr.dll for entry assembly, we need the actual
// executable name (Sonarr on linux). On mono this will return the location of
// the mono executable itself, which is not what we want.
var entry = Process.GetCurrentProcess().MainModule;
if (entry != null)
{
ExecutingApplication = entry.Location;
IsWindowsTray = OsInfo.IsWindows && entry.ManifestModule.Name == $"{ProcessProvider.SONARR_PROCESS_NAME}.exe";
ExecutingApplication = entry.FileName;
IsWindowsTray = OsInfo.IsWindows && entry.ModuleName == $"{ProcessProvider.SONARR_PROCESS_NAME}.exe";
}
}
@ -42,7 +44,7 @@ namespace NzbDrone.Common.EnvironmentInfo
// An official build running outside of the testing environment. (Analytics configurable)
IsProduction = !IsTesting && officialBuild;
// An unofficial build running outside of the testing environment. (Analytics enabled)
IsDevelopment = !IsTesting && !officialBuild && !InternalIsDebug();
}
@ -63,6 +65,11 @@ namespace NzbDrone.Common.EnvironmentInfo
{
get
{
if (OsInfo.IsNotWindows)
{
return false;
}
try
{
var principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
@ -124,10 +131,25 @@ namespace NzbDrone.Common.EnvironmentInfo
{
var lowerProcessName = Process.GetCurrentProcess().ProcessName.ToLower();
if (lowerProcessName.Contains("vshost")) return true;
if (lowerProcessName.Contains("nunit")) return true;
if (lowerProcessName.Contains("jetbrain")) return true;
if (lowerProcessName.Contains("resharper")) return true;
if (lowerProcessName.Contains("vshost"))
{
return true;
}
if (lowerProcessName.Contains("nunit"))
{
return true;
}
if (lowerProcessName.Contains("jetbrain"))
{
return true;
}
if (lowerProcessName.Contains("resharper"))
{
return true;
}
}
catch
{
@ -137,24 +159,50 @@ namespace NzbDrone.Common.EnvironmentInfo
try
{
var currentAssemblyLocation = typeof(RuntimeInfo).Assembly.Location;
if (currentAssemblyLocation.ToLower().Contains("_output")) return true;
if (currentAssemblyLocation.ToLower().Contains("_output"))
{
return true;
}
if (currentAssemblyLocation.ToLower().Contains("_tests"))
{
return true;
}
}
catch
{
}
var lowerCurrentDir = Directory.GetCurrentDirectory().ToLower();
if (lowerCurrentDir.Contains("teamcity")) return true;
if (lowerCurrentDir.Contains("buildagent")) return true;
if (lowerCurrentDir.Contains("_output")) return true;
if (lowerCurrentDir.Contains("vsts"))
{
return true;
}
if (lowerCurrentDir.Contains("buildagent"))
{
return true;
}
if (lowerCurrentDir.Contains("_output"))
{
return true;
}
if (lowerCurrentDir.Contains("_tests"))
{
return true;
}
return false;
}
private static bool InternalIsDebug()
{
if (BuildInfo.IsDebug || Debugger.IsAttached) return true;
if (BuildInfo.IsDebug || Debugger.IsAttached)
{
return true;
}
return false;
}
@ -162,7 +210,10 @@ namespace NzbDrone.Common.EnvironmentInfo
private static bool InternalIsOfficialBuild()
{
//Official builds will never have such a high revision
if (BuildInfo.Version.Major >= 10 || BuildInfo.Version.Revision > 10000) return false;
if (BuildInfo.Version.Major >= 10 || BuildInfo.Version.Revision > 10000)
{
return false;
}
return true;
}

@ -24,6 +24,7 @@ namespace NzbDrone.Common.EnvironmentInfo
public const string TERMINATE = "terminateexisting";
public const string RESTART = "restart";
public const string REGISTER_URL = "registerurl";
public const string NO_SINGLE_INSTANCE_CHECK = "nosingleinstancecheck";
public const string EXIT_IMMEDIATELY = "exitimmediately";
public StartupContext(params string[] args)

@ -6,16 +6,17 @@ namespace NzbDrone.Common.Extensions
{
public static class DictionaryExtensions
{
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue = default(TValue))
{
TValue value;
return dictionary.TryGetValue(key, out value) ? value : defaultValue;
}
public static Dictionary<T1, T2> Merge<T1, T2>(this Dictionary<T1, T2> first, Dictionary<T1, T2> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
if (first == null)
{
throw new ArgumentNullException("first");
}
if (second == null)
{
throw new ArgumentNullException("second");
}
var merged = new Dictionary<T1, T2>();
first.ToList().ForEach(kv => merged[kv.Key] = kv.Value);
@ -28,5 +29,19 @@ namespace NzbDrone.Common.Extensions
{
collection.Add(new KeyValuePair<TKey, TValue>(key, value));
}
public static IDictionary<TNewKey, TNewValue> SelectDictionary<TKey, TValue, TNewKey, TNewValue>(this IDictionary<TKey, TValue> dictionary,
Func<KeyValuePair<TKey, TValue>, ValueTuple<TNewKey, TNewValue>> selection)
{
return dictionary.Select(selection).ToDictionary(t => t.Item1, t => t.Item2);
}
public static IDictionary<TNewKey, TNewValue> SelectDictionary<TKey, TValue, TNewKey, TNewValue>(
this IDictionary<TKey, TValue> dictionary,
Func<KeyValuePair<TKey, TValue>, TNewKey> keySelector,
Func<KeyValuePair<TKey, TValue>, TNewValue> valueSelector)
{
return dictionary.SelectDictionary(p => { return (keySelector(p), valueSelector(p)); });
}
}
}

@ -7,13 +7,6 @@ namespace NzbDrone.Common.Extensions
{
public static class EnumerableExtensions
{
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
var knownKeys = new HashSet<TKey>();
return source.Where(element => knownKeys.Add(keySelector(element)));
}
public static IEnumerable<TFirst> IntersectBy<TFirst, TSecond, TKey>(this IEnumerable<TFirst> first, Func<TFirst, TKey> firstKeySelector,
IEnumerable<TSecond> second, Func<TSecond, TKey> secondKeySelector,
IEqualityComparer<TKey> keyComparer)
@ -117,5 +110,14 @@ namespace NzbDrone.Common.Extensions
//
// return descending ? source.OrderByDescending(orderByFunc) : source.OrderBy(orderByFunc);
// }
public static string ConcatToString<TSource>(this IEnumerable<TSource> source, string separator = ", ")
{
return string.Join(separator, source.Select(x => x.ToString()));
}
public static string ConcatToString<TSource>(this IEnumerable<TSource> source, Func<TSource, string> predicate, string separator = ", ")
{
return string.Join(separator, source.Select(predicate));
}
}
}

@ -16,7 +16,7 @@ namespace NzbDrone.Common.Extensions
private const string DB_RESTORE = "sonarr.restore";
private const string LOG_DB = "logs.db";
private const string NLOG_CONFIG_FILE = "nlog.config";
private const string UPDATE_CLIENT_EXE = "Sonarr.Update.exe";
private const string UPDATE_CLIENT_EXE_NAME = "Sonarr.Update";
private static readonly string UPDATE_SANDBOX_FOLDER_NAME = "sonarr_update" + Path.DirectorySeparatorChar;
private static readonly string UPDATE_PACKAGE_FOLDER_NAME = "Sonarr" + Path.DirectorySeparatorChar;
@ -34,7 +34,8 @@ namespace NzbDrone.Common.Extensions
var info = new FileInfo(path.Trim());
if (OsInfo.IsWindows && info.FullName.StartsWith(@"\\")) //UNC
//UNC
if (OsInfo.IsWindows && info.FullName.StartsWith(@"\\"))
{
return info.FullName.TrimEnd('/', '\\', ' ');
}
@ -54,7 +55,11 @@ namespace NzbDrone.Common.Extensions
comparison = DiskProviderBase.PathStringComparison;
}
if (firstPath.Equals(secondPath, comparison.Value)) return true;
if (firstPath.Equals(secondPath, comparison.Value))
{
return true;
}
return string.Equals(firstPath.CleanFilePath(), secondPath.CleanFilePath(), comparison.Value);
}
@ -260,6 +265,21 @@ namespace NzbDrone.Common.Extensions
return substring.Substring(0, lastSeparatorIndex);
}
public static string ProcessNameToExe(this string processName, PlatformType runtime)
{
if (OsInfo.IsWindows || runtime != PlatformType.NetCore)
{
processName += ".exe";
}
return processName;
}
public static string ProcessNameToExe(this string processName)
{
return processName.ProcessNameToExe(PlatformInfo.Platform);
}
public static string GetAppDataPath(this IAppFolderInfo appFolderInfo)
{
return appFolderInfo.AppDataFolder;
@ -320,9 +340,9 @@ namespace NzbDrone.Common.Extensions
return Path.Combine(GetUpdatePackageFolder(appFolderInfo), UPDATE_CLIENT_FOLDER_NAME);
}
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo)
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo, PlatformType runtime)
{
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE);
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe(runtime);
}
public static string GetDatabase(this IAppFolderInfo appFolderInfo)

@ -11,7 +11,7 @@ namespace NzbDrone.Common
byte[] bytes = Encoding.UTF8.GetBytes(input);
foreach (byte myByte in bytes)
{
mCrc ^= ((uint)(myByte) << 24);
mCrc ^= (uint)myByte << 24;
for (var i = 0; i < 8; i++)
{
if ((Convert.ToUInt32(mCrc) & 0x80000000) == 0x80000000)
@ -33,4 +33,4 @@ namespace NzbDrone.Common
return HashUtil.CalculateCrc(seed);
}
}
}
}

@ -33,19 +33,10 @@ namespace NzbDrone.Common.Http.Dispatchers
{
var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url);
if (PlatformInfo.IsMono && request.ResponseStream == null)
{
// On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case.
webRequest.AutomaticDecompression = DecompressionMethods.None;
webRequest.Headers.Add("Accept-Encoding", "gzip");
}
else
{
// Deflate is not a standard and could break depending on implementation.
// we should just stick with the more compatible Gzip
//http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net
webRequest.AutomaticDecompression = DecompressionMethods.GZip;
}
// Deflate is not a standard and could break depending on implementation.
// we should just stick with the more compatible Gzip
//http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net
webRequest.AutomaticDecompression = DecompressionMethods.GZip;
webRequest.Method = request.Method.ToString();
webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent);
@ -121,29 +112,7 @@ namespace NzbDrone.Common.Http.Dispatchers
{
try
{
if (request.ResponseStream != null && httpWebResponse.StatusCode == HttpStatusCode.OK)
{
// A target ResponseStream was specified, write to that instead.
// But only on the OK status code, since we don't want to write failures and redirects.
responseStream.CopyTo(request.ResponseStream);
}
else
{
data = responseStream.ToBytes();
if (PlatformInfo.IsMono && httpWebResponse.ContentEncoding == "gzip")
{
using (var compressedStream = new MemoryStream(data))
using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress))
using (var decompressedStream = new MemoryStream())
{
gzip.CopyTo(decompressedStream);
data = decompressedStream.ToArray();
}
httpWebResponse.Headers.Remove("Content-Encoding");
}
}
data = responseStream.ToBytes();
}
catch (Exception ex)
{

@ -4,6 +4,7 @@
{
public static readonly HttpAccept Rss = new HttpAccept("application/rss+xml, text/rss+xml, application/xml, text/xml");
public static readonly HttpAccept Json = new HttpAccept("application/json");
public static readonly HttpAccept JsonCharset = new HttpAccept("application/json; charset=utf-8");
public static readonly HttpAccept Html = new HttpAccept("text/html");
public string Value { get; private set; }

@ -32,7 +32,10 @@ namespace NzbDrone.Common.Instrumentation
{
var exception = e.ExceptionObject as Exception;
if (exception == null) return;
if (exception == null)
{
return;
}
if (exception is NullReferenceException &&
exception.ToString().Contains("Microsoft.AspNet.SignalR.Transports.TransportHeartbeat.ProcessServerCommand"))
@ -41,16 +44,6 @@ namespace NzbDrone.Common.Instrumentation
return;
}
if (PlatformInfo.IsMono)
{
if (exception is TypeInitializationException && exception.InnerException is DllNotFoundException ||
exception is DllNotFoundException)
{
Logger.Debug(exception, "Minor Fail: " + exception.Message);
return;
}
}
Console.WriteLine("EPIC FAIL: {0}", exception);
Logger.Fatal(exception, "EPIC FAIL.");
}

@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using NLog;
using NLog.Fluent;
using NzbDrone.Common.EnvironmentInfo;

@ -1,10 +1,9 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Threading;
using NLog;
using NLog.Common;
@ -22,7 +21,8 @@ namespace NzbDrone.Common.Instrumentation.Sentry
// don't report uninformative SQLite exceptions
// busy/locked are benign https://forums.sonarr.tv/t/owin-sqlite-error-5-database-is-locked/5423/11
// The others will be user configuration problems and silt up Sentry
private static readonly HashSet<SQLiteErrorCode> FilteredSQLiteErrors = new HashSet<SQLiteErrorCode> {
private static readonly HashSet<SQLiteErrorCode> FilteredSQLiteErrors = new HashSet<SQLiteErrorCode>
{
SQLiteErrorCode.Busy,
SQLiteErrorCode.Locked,
SQLiteErrorCode.Perm,
@ -36,7 +36,8 @@ namespace NzbDrone.Common.Instrumentation.Sentry
// use string and not Type so we don't need a reference to the project
// where these are defined
private static readonly HashSet<string> FilteredExceptionTypeNames = new HashSet<string> {
private static readonly HashSet<string> FilteredExceptionTypeNames = new HashSet<string>
{
// UnauthorizedAccessExceptions will just be user configuration issues
"UnauthorizedAccessException",
// Filter out people stuck in boot loops
@ -45,49 +46,51 @@ namespace NzbDrone.Common.Instrumentation.Sentry
"TinyIoCResolutionException"
};
public static readonly List<string> FilteredExceptionMessages = new List<string> {
public static readonly List<string> FilteredExceptionMessages = new List<string>
{
// Swallow the many, many exceptions flowing through from Jackett
"Jackett.Common.IndexerException"
"Jackett.Common.IndexerException",
// Fix openflixr being stupid with permissions
"openflixr"
};
// exception types in this list will additionally have the exception message added to the
// sentry fingerprint. Make sure that this message doesn't vary by exception
// (e.g. containing a path or a url) so that the sentry grouping is sensible
private static readonly HashSet<string> IncludeExceptionMessageTypes = new HashSet<string> {
private static readonly HashSet<string> IncludeExceptionMessageTypes = new HashSet<string>
{
"SQLiteException"
};
private static readonly IDictionary<LogLevel, SentryLevel> LoggingLevelMap = new Dictionary<LogLevel, SentryLevel>
{
{LogLevel.Debug, SentryLevel.Debug},
{LogLevel.Error, SentryLevel.Error},
{LogLevel.Fatal, SentryLevel.Fatal},
{LogLevel.Info, SentryLevel.Info},
{LogLevel.Trace, SentryLevel.Debug},
{LogLevel.Warn, SentryLevel.Warning},
{ LogLevel.Debug, SentryLevel.Debug },
{ LogLevel.Error, SentryLevel.Error },
{ LogLevel.Fatal, SentryLevel.Fatal },
{ LogLevel.Info, SentryLevel.Info },
{ LogLevel.Trace, SentryLevel.Debug },
{ LogLevel.Warn, SentryLevel.Warning },
};
private static readonly IDictionary<LogLevel, BreadcrumbLevel> BreadcrumbLevelMap = new Dictionary<LogLevel, BreadcrumbLevel>
{
{LogLevel.Debug, BreadcrumbLevel.Debug},
{LogLevel.Error, BreadcrumbLevel.Error},
{LogLevel.Fatal, BreadcrumbLevel.Critical},
{LogLevel.Info, BreadcrumbLevel.Info},
{LogLevel.Trace, BreadcrumbLevel.Debug},
{LogLevel.Warn, BreadcrumbLevel.Warning},
{ LogLevel.Debug, BreadcrumbLevel.Debug },
{ LogLevel.Error, BreadcrumbLevel.Error },
{ LogLevel.Fatal, BreadcrumbLevel.Critical },
{ LogLevel.Info, BreadcrumbLevel.Info },
{ LogLevel.Trace, BreadcrumbLevel.Debug },
{ LogLevel.Warn, BreadcrumbLevel.Warning },
};
private readonly DateTime _startTime = DateTime.UtcNow;
private readonly IDisposable _sdk;
private bool _disposed;
private readonly SentryDebounce _debounce;
private bool _disposed;
private bool _unauthorized;
public bool FilterEvents { get; set; }
public Version DatabaseVersion { get; set; }
public int DatabaseMigration { get; set; }
public bool SentryEnabled { get; set; }
public SentryTarget(string dsn)
@ -98,7 +101,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
o.AttachStacktrace = true;
o.MaxBreadcrumbs = 200;
o.SendDefaultPii = false;
o.AttachStacktrace = true;
o.Debug = false;
o.DiagnosticsLevel = SentryLevel.Debug;
o.Release = BuildInfo.Release;
@ -136,11 +138,6 @@ namespace NzbDrone.Common.Instrumentation.Sentry
scope.SetTag("culture", Thread.CurrentThread.CurrentCulture.Name);
scope.SetTag("branch", BuildInfo.Branch);
if (DatabaseVersion != default(Version))
{
scope.SetTag("sqlite_version", $"{DatabaseVersion}");
}
});
}
@ -148,12 +145,20 @@ namespace NzbDrone.Common.Instrumentation.Sentry
{
SentrySdk.ConfigureScope(scope =>
{
if (osInfo.Name != null && PlatformInfo.IsMono)
});
}
public void UpdateScope(Version databaseVersion, int migration, string updateBranch, IPlatformInfo platformInfo)
{
SentrySdk.ConfigureScope(scope =>
{
scope.Environment = updateBranch;
scope.SetTag("runtime_version", $"{PlatformInfo.PlatformName} {platformInfo.Version}");
if (databaseVersion != default(Version))
{
// Sentry auto-detection of non-Windows platforms isn't that accurate on certain devices.
scope.Contexts.OperatingSystem.Name = osInfo.Name.FirstCharToUpper();
scope.Contexts.OperatingSystem.RawDescription = osInfo.FullName;
scope.Contexts.OperatingSystem.Version = osInfo.Version.ToString();
scope.SetTag("sqlite_version", $"{databaseVersion}");
scope.SetTag("database_migration", $"{migration}");
}
});
}
@ -221,7 +226,8 @@ namespace NzbDrone.Common.Instrumentation.Sentry
{
if (FilterEvents)
{
if (logEvent.Exception is SQLiteException sqliteException && FilteredSQLiteErrors.Contains(sqliteException.ResultCode))
var sqlEx = logEvent.Exception as SQLiteException;
if (sqlEx != null && FilteredSQLiteErrors.Contains(sqlEx.ResultCode))
{
return false;
}
@ -230,7 +236,7 @@ namespace NzbDrone.Common.Instrumentation.Sentry
{
return false;
}
if (FilteredExceptionMessages.Any(x => logEvent.Exception.Message.Contains(x)))
{
return false;

@ -14,13 +14,11 @@ namespace NzbDrone.Common.Processes
public class PidFileProvider : IProvidePidFile
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IProcessProvider _processProvider;
private readonly Logger _logger;
public PidFileProvider(IAppFolderInfo appFolderInfo, IProcessProvider processProvider, Logger logger)
public PidFileProvider(IAppFolderInfo appFolderInfo, Logger logger)
{
_appFolderInfo = appFolderInfo;
_processProvider = processProvider;
_logger = logger;
}
@ -34,11 +32,11 @@ namespace NzbDrone.Common.Processes
var filename = Path.Combine(_appFolderInfo.AppDataFolder, "sonarr.pid");
try
{
File.WriteAllText(filename, _processProvider.GetCurrentProcessId().ToString());
File.WriteAllText(filename, ProcessProvider.GetCurrentProcessId().ToString());
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to write PID file {0}", filename);
_logger.Error(ex, "Unable to write PID file: " + filename);
throw new SonarrStartupException(ex, "Unable to write PID file {0}", filename);
}
}

@ -14,7 +14,6 @@ namespace NzbDrone.Common.Processes
{
public interface IProcessProvider
{
int GetCurrentProcessId();
ProcessInfo GetCurrentProcess();
ProcessInfo GetProcessById(int id);
List<ProcessInfo> FindProcessByName(string name);
@ -43,9 +42,9 @@ namespace NzbDrone.Common.Processes
_logger = logger;
}
public int GetCurrentProcessId()
public static int GetCurrentProcessId()
{
return Process.GetCurrentProcess().Id;
return Environment.ProcessId;
}
public ProcessInfo GetCurrentProcess()
@ -156,7 +155,10 @@ namespace NzbDrone.Common.Processes
process.OutputDataReceived += (sender, eventArgs) =>
{
if (string.IsNullOrWhiteSpace(eventArgs.Data)) return;
if (string.IsNullOrWhiteSpace(eventArgs.Data))
{
return;
}
logger.Debug(eventArgs.Data);
@ -168,7 +170,10 @@ namespace NzbDrone.Common.Processes
process.ErrorDataReceived += (sender, eventArgs) =>
{
if (string.IsNullOrWhiteSpace(eventArgs.Data)) return;
if (string.IsNullOrWhiteSpace(eventArgs.Data))
{
return;
}
logger.Error(eventArgs.Data);
@ -209,8 +214,11 @@ namespace NzbDrone.Common.Processes
public ProcessOutput StartAndCapture(string path, string args = null, StringDictionary environmentVariables = null)
{
var output = new ProcessOutput();
var process = Start(path, args, environmentVariables, s => output.Lines.Add(new ProcessOutputLine(ProcessOutputLevel.Standard, s)),
error => output.Lines.Add(new ProcessOutputLine(ProcessOutputLevel.Error, error)));
var process = Start(path,
args,
environmentVariables,
s => output.Lines.Add(new ProcessOutputLine(ProcessOutputLevel.Standard, s)),
error => output.Lines.Add(new ProcessOutputLine(ProcessOutputLevel.Error, error)));
process.WaitForExit();
output.ExitCode = process.ExitCode;
@ -249,7 +257,7 @@ namespace NzbDrone.Common.Processes
process.Refresh();
if (process.Id != Process.GetCurrentProcess().Id && process.HasExited)
if (process.Id != GetCurrentProcessId() && process.HasExited)
{
_logger.Debug("Process has already exited");
return;
@ -270,7 +278,7 @@ namespace NzbDrone.Common.Processes
foreach (var processInfo in processes)
{
if (processInfo.Id == Process.GetCurrentProcess().Id)
if (processInfo.Id == GetCurrentProcessId())
{
_logger.Debug("Tried killing own process, skipping: {0} [{1}]", processInfo.Id, processInfo.ProcessName);
continue;
@ -283,7 +291,10 @@ namespace NzbDrone.Common.Processes
private ProcessInfo ConvertToProcessInfo(Process process)
{
if (process == null) return null;
if (process == null)
{
return null;
}
process.Refresh();
@ -291,14 +302,17 @@ namespace NzbDrone.Common.Processes
try
{
if (process.Id <= 0) return null;
if (process.Id <= 0)
{
return null;
}
processInfo = new ProcessInfo();
processInfo.Id = process.Id;
processInfo.Name = process.ProcessName;
processInfo.StartPath = GetExeFileName(process);
if (process.Id != Process.GetCurrentProcess().Id && process.HasExited)
if (process.Id != GetCurrentProcessId() && process.HasExited)
{
processInfo = null;
}
@ -324,17 +338,7 @@ namespace NzbDrone.Common.Processes
private List<Process> GetProcessesByName(string name)
{
//TODO: move this to an OS specific class
var monoProcesses = Process.GetProcessesByName("mono")
.Union(Process.GetProcessesByName("mono-sgen"))
.Where(process =>
process.Modules.Cast<ProcessModule>()
.Any(module =>
module.ModuleName.ToLower() == name.ToLower() + ".exe"));
var processes = Process.GetProcessesByName(name)
.Union(monoProcesses).ToList();
var processes = Process.GetProcessesByName(name).ToList();
_logger.Debug("Found {0} processes with the name: {1}", processes.Count, name);
@ -355,15 +359,6 @@ namespace NzbDrone.Common.Processes
private (string Path, string Args) GetPathAndArgs(string path, string args)
{
if (PlatformInfo.IsMono && path.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))
{
return ("mono", $"--debug {path} {args}");
}
if (OsInfo.IsOsx && path.EndsWith(".app", StringComparison.InvariantCultureIgnoreCase))
{
return ("/usr/bin/open", $"--new {path} --args {args}");
}
if (OsInfo.IsWindows && path.EndsWith(".bat", StringComparison.InvariantCultureIgnoreCase))
{
return ("cmd.exe", $"/c {path} {args}");

@ -1,6 +1,4 @@
using System;
using System.Collections.Specialized;
using System.Configuration.Install;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
@ -64,28 +62,33 @@ namespace NzbDrone.Common
{
_logger.Info("Installing service '{0}'", serviceName);
var args = $"create {serviceName} " +
$"DisplayName= \"{serviceName}\" " +
$"binpath= \"{Process.GetCurrentProcess().MainModule.FileName}\" " +
"start= auto " +
"depend= EventLog/Tcpip/http " +
"obj= \"NT AUTHORITY\\LocalService\"";
var installer = new ServiceProcessInstaller
{
Account = ServiceAccount.LocalService
};
_logger.Info(args);
var serviceInstaller = new ServiceInstaller();
var installOutput = _processProvider.StartAndCapture("sc.exe", args);
if (installOutput.ExitCode != 0)
{
_logger.Error($"Failed to install service: {installOutput.Lines.Select(x => x.Content).ConcatToString("\n")}");
throw new ApplicationException("Failed to install service");
}
string[] cmdline = { @"/assemblypath=" + Process.GetCurrentProcess().MainModule.FileName };
var context = new InstallContext("service_install.log", cmdline);
serviceInstaller.Context = context;
serviceInstaller.DisplayName = serviceName;
serviceInstaller.ServiceName = serviceName;
serviceInstaller.Description = "Sonarr Application Server";
serviceInstaller.StartType = ServiceStartMode.Automatic;
serviceInstaller.ServicesDependedOn = new[] { "EventLog", "Tcpip", "http" };
_logger.Info(installOutput.Lines.Select(x => x.Content).ConcatToString("\n"));
serviceInstaller.Parent = installer;
var descOutput = _processProvider.StartAndCapture("sc.exe", $"description {serviceName} \"Sonarr Application Server\"");
if (descOutput.ExitCode != 0)
{
_logger.Error($"Failed to install service: {descOutput.Lines.Select(x => x.Content).ConcatToString("\n")}");
throw new ApplicationException("Failed to install service");
}
serviceInstaller.Install(new ListDictionary());
_logger.Info(descOutput.Lines.Select(x => x.Content).ConcatToString("\n"));
_logger.Info("Service Has installed successfully.");
}
@ -96,12 +99,8 @@ namespace NzbDrone.Common
Stop(serviceName);
var serviceInstaller = new ServiceInstaller();
var context = new InstallContext("service_uninstall.log", null);
serviceInstaller.Context = context;
serviceInstaller.ServiceName = serviceName;
serviceInstaller.Uninstall(null);
var output = _processProvider.StartAndCapture("sc.exe", $"delete {serviceName}");
_logger.Info(output.Lines.Select(x => x.Content).ConcatToString("\n"));
_logger.Info("{0} successfully uninstalled", serviceName);
}
@ -151,7 +150,7 @@ namespace NzbDrone.Common
public ServiceControllerStatus GetStatus(string serviceName)
{
return GetService(serviceName).Status;
return GetService(serviceName).Status;
}
public void Start(string serviceName)

@ -1,21 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<Platforms>x86</Platforms>
<TargetFrameworks>net6.0</TargetFrameworks>
<DefineConstants Condition="'$(RuntimeIdentifier)' == 'linux-musl-x64' or '$(RuntimeIdentifier)' == 'linux-musl-arm64'">ISMUSL</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DotNet4.SocksProxy" Version="1.4.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NLog" Version="4.7.14" />
<PackageReference Include="NLog.Targets.Syslog" Version="6.0.3" />
<PackageReference Include="Sentry" Version="1.2.0" />
<PackageReference Include="SharpZipLib" Version="1.2.0" />
<PackageReference Include="Sentry" Version="2.1.8" />
<PackageReference Include="SharpZipLib" Version="1.3.3" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.0-0" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Configuration.Install" />
<Reference Include="System.ServiceProcess" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="6.0.0-preview.5.21301.5" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="6.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Update="EnsureThat\Resources\ExceptionMessages.Designer.cs">

@ -17,7 +17,7 @@ namespace NzbDrone.Common.TPL
_timer.Elapsed += timer_Elapsed;
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (_paused == 0)
{
@ -60,4 +60,4 @@ namespace NzbDrone.Common.TPL
}
}
}
}
}

@ -33,7 +33,7 @@ namespace NzbDrone.Common.TPL
var waitUntil = DateTime.UtcNow.Add(interval);
if (subKey.IsNotNullOrWhiteSpace())
{
{
// Expand the base key timer, but don't extend it beyond now+interval.
var baseUntil = _rateLimitStore.AddOrUpdate(key,
(s) => waitUntil,

@ -25,4 +25,4 @@ namespace NzbDrone.Common.TPL
return task;
}
}
}
}

@ -1,12 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net462</TargetFramework>
<Platforms>x86</Platforms>
<TargetFrameworks>net6.0</TargetFrameworks>
<ApplicationIcon>..\NzbDrone.Host\Sonarr.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup Condition="!$(RuntimeIdentifier.StartsWith('win'))">
<AssemblyName>Sonarr</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Host\Sonarr.Host.csproj" />
<ProjectReference Include="..\Sonarr.RuntimePatches\Sonarr.RuntimePatches.csproj" />

@ -164,7 +164,7 @@ namespace NzbDrone.Core.Test.Download
Mocker.GetMock<IIndexerStatusService>()
.Verify(v => v.RecordFailure(It.IsAny<int>(),
It.IsInRange<TimeSpan>(TimeSpan.FromMinutes(4.9), TimeSpan.FromMinutes(5.1), Range.Inclusive)), Times.Once());
It.IsInRange<TimeSpan>(TimeSpan.FromMinutes(4.9), TimeSpan.FromMinutes(5.1), Moq.Range.Inclusive)), Times.Once());
}
[Test]

@ -1,69 +0,0 @@
using System;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class DotnetVersionCheckFixture : CoreTest<DotnetVersionCheck>
{
private void GivenOutput(string version)
{
WindowsOnly();
Mocker.GetMock<IPlatformInfo>()
.SetupGet(s => s.Version)
.Returns(new Version(version));
}
[TestCase("4.7.2")]
[TestCase("4.8")]
public void should_return_ok(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeOk();
}
public void should_return_notice(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeNotice();
}
public void should_return_warning(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeWarning();
}
[TestCase("4.5")]
[TestCase("4.5.2")]
[TestCase("4.6.1")]
[TestCase("4.6.2")]
[TestCase("4.7")]
[TestCase("4.7.1")]
public void should_return_error(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeError();
}
[Test]
public void should_return_nok_for_net462_on_Win1511()
{
Mocker.GetMock<IOsInfo>()
.SetupGet(v => v.Version)
.Returns("10.0.14392");
GivenOutput("4.6.2");
Subject.Check().ShouldBeError();
}
}
}

@ -1,59 +0,0 @@
using NUnit.Framework;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
using static NzbDrone.Core.HealthCheck.Checks.MonoDebugCheck;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class MonoDebugFixture : CoreTest<MonoDebugCheck>
{
private void GivenHasStackFrame(bool hasStackFrame)
{
Mocker.GetMock<StackFrameHelper>()
.Setup(f => f.HasStackFrameInfo())
.Returns(hasStackFrame);
}
[Test]
public void should_return_ok_if_windows()
{
WindowsOnly();
Subject.Check().ShouldBeOk();
}
[Test]
public void should_return_ok_if_not_debug()
{
MonoOnly();
GivenHasStackFrame(false);
Subject.Check().ShouldBeOk();
}
[Test]
public void should_log_warning_if_not_debug()
{
MonoOnly();
GivenHasStackFrame(false);
Subject.Check();
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_return_ok_if_debug()
{
MonoOnly();
GivenHasStackFrame(true);
Subject.Check().ShouldBeOk();
}
}
}

@ -1,71 +0,0 @@
using System;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.HealthCheck.Checks;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.HealthCheck.Checks
{
[TestFixture]
public class MonoVersionCheckFixture : CoreTest<MonoVersionCheck>
{
private void GivenOutput(string version)
{
MonoOnly();
Mocker.GetMock<IPlatformInfo>()
.SetupGet(s => s.Version)
.Returns(new Version(version));
}
[TestCase("5.18")]
[TestCase("5.20")]
[TestCase("6.4")]
public void should_return_ok(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeOk();
}
public void should_return_notice(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeNotice();
}
public void should_return_warning(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeWarning();
}
[TestCase("2.10.2")]
[TestCase("2.10.8.1")]
[TestCase("3.0.0.1")]
[TestCase("3.2.0.1")]
[TestCase("3.2.1")]
[TestCase("3.2.7")]
[TestCase("3.6.1")]
[TestCase("3.8")]
[TestCase("3.10")]
[TestCase("4.0.0.0")]
[TestCase("4.2")]
[TestCase("4.4.0")]
[TestCase("4.4.1")]
[TestCase("5.4")]
[TestCase("5.8")]
[TestCase("5.10")]
[TestCase("5.12")]
[TestCase("5.14")]
[TestCase("5.16")]
public void should_return_error(string version)
{
GivenOutput(version);
Subject.Check().ShouldBeError();
}
}
}

@ -1,4 +1,4 @@
using Moq;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
@ -31,7 +31,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test]
public void should_return_error_when_app_folder_is_write_protected_and_update_automatically_is_enabled()
{
MonoOnly();
PosixOnly();
const string startupFolder = @"/opt/nzbdrone";
@ -53,7 +53,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test]
public void should_return_error_when_ui_folder_is_write_protected_and_update_automatically_is_enabled()
{
MonoOnly();
PosixOnly();
const string startupFolder = @"/opt/nzbdrone";
const string uiFolder = @"/opt/nzbdrone/UI";
@ -80,7 +80,7 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
[Test]
public void should_not_return_error_when_app_folder_is_write_protected_and_external_script_enabled()
{
MonoOnly();
PosixOnly();
Mocker.GetMock<IConfigFileProvider>()
.Setup(s => s.UpdateAutomatically)

@ -302,7 +302,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
[Test]
public void should_get_relative_path_when_there_is_no_grandparent_mono()
{
MonoOnly();
PosixOnly();
var name = "Series.Title.S01E01.720p.HDTV.x264-Sonarr";
var outputPath = "/";

@ -1,4 +1,4 @@
using System;
using System;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
@ -74,7 +74,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
[Test]
public void should_return_false_if_unopacking_on_linux()
{
MonoOnly();
PosixOnly();
GivenInWorkingFolder();
GivenLastWriteTimeUtc(DateTime.UtcNow.AddDays(-5));

@ -111,7 +111,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
[Test]
public void filter_should_return_none_existing_files_not_ignoring_case()
{
MonoOnly();
PosixOnly();
var files = new List<string>()
{
@ -147,4 +147,4 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaFileServiceTests
Subject.FilterExistingFiles(files, _series).Should().Contain(files.First());
}
}
}
}

@ -1,10 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net462</TargetFramework>
<Platforms>x86</Platforms>
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NBuilder" Version="6.0.0" />
<PackageReference Include="NBuilder" Version="6.1.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.0-0" />
</ItemGroup>
<ItemGroup>

@ -14,6 +14,11 @@ namespace NzbDrone.Core.Test.UpdateTests
[SetUp]
public void Setup()
{
if (OsInfo.Os == Os.LinuxMusl || OsInfo.Os == Os.Bsd)
{
throw new IgnoreException("Ignore until we have musl releases");
}
Mocker.GetMock<IPlatformInfo>().SetupGet(c => c.Version).Returns(new Version("9.9.9"));
}

@ -3,6 +3,7 @@ using System.IO;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Validation.Paths;
@ -50,10 +51,11 @@ namespace NzbDrone.Core.Test.ValidationTests
[Test]
public void should_not_be_valid_if_set_to_bin_folder()
{
MonoOnly();
PosixOnly();
var bin = OsInfo.IsOsx ? "/System" : "/bin";
var series = Builder<Series>.CreateNew()
.With(s => s.Path = "/bin")
.With(s => s.Path = bin)
.Build();
_validator.Validate(series).IsValid.Should().BeFalse();
@ -62,10 +64,11 @@ namespace NzbDrone.Core.Test.ValidationTests
[Test]
public void should_not_be_valid_if_child_of_bin_folder()
{
MonoOnly();
PosixOnly();
var bin = OsInfo.IsOsx ? "/System" : "/bin";
var series = Builder<Series>.CreateNew()
.With(s => s.Path = "/bin/test")
.With(s => s.Path = Path.Combine(bin, "test"))
.Build();
_validator.Validate(series).IsValid.Should().BeFalse();

@ -181,7 +181,7 @@ namespace NzbDrone.Core.Configuration
public string Branch => GetValue("Branch", "main").ToLowerInvariant();
public string LogLevel => GetValue("LogLevel", "info");
public string LogLevel => GetValue("LogLevel", "info").ToLowerInvariant();
public string ConsoleLogLevel => GetValue("ConsoleLogLevel", string.Empty, persist: false);
public string SslCertPath => GetValue("SslCertPath", "");
@ -202,8 +202,7 @@ namespace NzbDrone.Core.Configuration
}
}
// public string UiFolder => GetValue("UiFolder", "UI", false);GetValue("UiFolder", "UI", false);
public string UiFolder => "UI";
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
public string InstanceName
{
@ -257,7 +256,7 @@ namespace NzbDrone.Core.Configuration
var valueHolder = parentContainer.Descendants(key).ToList();
if (valueHolder.Count() == 1)
if (valueHolder.Count == 1)
{
return valueHolder.First().Value.Trim();
}

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Net;
using NLog;
@ -12,7 +12,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
{
HadoukenSystemInfo GetSystemInfo(HadoukenSettings settings);
HadoukenTorrent[] GetTorrents(HadoukenSettings settings);
IDictionary<string, object> GetConfig(HadoukenSettings settings);
IReadOnlyDictionary<string, object> GetConfig(HadoukenSettings settings);
string AddTorrentFile(HadoukenSettings settings, byte[] fileContent);
void AddTorrentUri(HadoukenSettings settings, string torrentUrl);
void RemoveTorrent(HadoukenSettings settings, string downloadId);
@ -42,9 +42,9 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
return GetTorrents(result.Torrents);
}
public IDictionary<string, object> GetConfig(HadoukenSettings settings)
public IReadOnlyDictionary<string, object> GetConfig(HadoukenSettings settings)
{
return ProcessRequest<IDictionary<string, object>>(settings, "webui.getSettings");
return ProcessRequest<IReadOnlyDictionary<string, object>>(settings, "webui.getSettings");
}
public string AddTorrentFile(HadoukenSettings settings, byte[] fileContent)
@ -181,4 +181,4 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
return HadoukenTorrentState.Unknown;
}
}
}
}

@ -1,4 +1,4 @@
using FluentValidation.Results;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
@ -35,7 +35,7 @@ namespace NzbDrone.Core.Download.Clients.Vuze
// - A multi-file torrent is downloaded in a job folder and 'outputPath' points to that directory directly.
// - A single-file torrent is downloaded in the root folder and 'outputPath' poinst to that root folder.
// We have to make sure the return value points to the job folder OR file.
if (outputPath == null || outputPath.FileName == torrent.Name || torrent.FileCount > 1)
if (outputPath.FileName == torrent.Name || torrent.FileCount > 1)
{
_logger.Trace("Vuze output directory: {0}", outputPath);
}

@ -313,11 +313,8 @@ namespace NzbDrone.Core.Extras.Metadata.Consumers.Xbmc
video.Add(new XElement("scantype", episodeFile.MediaInfo.ScanType));
video.Add(new XElement("width", episodeFile.MediaInfo.Width));
if (episodeFile.MediaInfo.RunTime != null)
{
video.Add(new XElement("duration", episodeFile.MediaInfo.RunTime.TotalMinutes));
video.Add(new XElement("durationinseconds", Math.Round(episodeFile.MediaInfo.RunTime.TotalSeconds)));
}
video.Add(new XElement("duration", episodeFile.MediaInfo.RunTime.TotalMinutes));
video.Add(new XElement("durationinseconds", Math.Round(episodeFile.MediaInfo.RunTime.TotalSeconds)));
streamDetails.Add(video);

@ -8,7 +8,7 @@ namespace NzbDrone.Core
{
public static string SHA256Hash(this string input)
{
using (var hash = SHA256Managed.Create())
using (var hash = SHA256.Create())
{
var enc = Encoding.UTF8;
return GetHash(hash.ComputeHash(enc.GetBytes(input)));
@ -17,7 +17,7 @@ namespace NzbDrone.Core
public static string SHA256Hash(this Stream input)
{
using (var hash = SHA256Managed.Create())
using (var hash = SHA256.Create())
{
return GetHash(hash.ComputeHash(input));
}

@ -1,69 +0,0 @@
using System;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Core.HealthCheck.Checks
{
public class DotnetVersionCheck : HealthCheckBase
{
private readonly IPlatformInfo _platformInfo;
private readonly IOsInfo _osInfo;
private readonly Logger _logger;
public DotnetVersionCheck(IPlatformInfo platformInfo, IOsInfo osInfo, Logger logger)
{
_platformInfo = platformInfo;
_osInfo = osInfo;
_logger = logger;
}
public override HealthCheck Check()
{
if (!PlatformInfo.IsDotNet)
{
return new HealthCheck(GetType());
}
var dotnetVersion = _platformInfo.Version;
// Target .Net version, which would allow us to increase our target framework
var targetVersion = new Version("4.7.2");
if (dotnetVersion >= targetVersion)
{
_logger.Debug("Dotnet version is {0} or better: {1}", targetVersion, dotnetVersion);
return new HealthCheck(GetType());
}
// Supported .net version but below our desired target
var stableVersion = new Version("4.7.2");
if (dotnetVersion >= stableVersion)
{
_logger.Debug("Dotnet version is {0} or better: {1}", stableVersion, dotnetVersion);
return new HealthCheck(GetType(), HealthCheckResult.Notice,
$"Currently installed .Net Framework {dotnetVersion} is supported but we recommend upgrading to at least {targetVersion}.",
"#currently-installed-net-framework-is-supported-but-upgrading-is-recommended");
}
if (Version.TryParse(_osInfo.Version, out var osVersion) && osVersion < new Version("10.0.14393"))
{
return new HealthCheck(GetType(), HealthCheckResult.Error,
$"Currently installed .Net Framework {dotnetVersion} is no longer supported. However your Operating System cannot be upgraded to {targetVersion}.",
"#currently-installed-net-framework-is-old-and-unsupported");
}
var oldVersion = new Version("4.6.2");
if (dotnetVersion >= oldVersion)
{
return new HealthCheck(GetType(), HealthCheckResult.Error,
$"Currently installed .Net Framework {dotnetVersion} is no longer supported. Please upgrade the .Net Framework to at least {targetVersion}.",
"#currently-installed-net-framework-is-old-and-unsupported");
}
return new HealthCheck(GetType(), HealthCheckResult.Error,
$"Currently installed .Net Framework {dotnetVersion} is old and unsupported. Please upgrade the .Net Framework to at least {targetVersion}.",
"#currently-installed-net-framework-is-old-and-unsupported");
}
public override bool CheckOnSchedule => false;
}
}

@ -1,47 +0,0 @@
using System.Diagnostics;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.HealthCheck.Checks
{
public class MonoDebugCheck : HealthCheckBase
{
private readonly Logger _logger;
private readonly StackFrameHelper _stackFrameHelper;
public override bool CheckOnSchedule => false;
public MonoDebugCheck(Logger logger, StackFrameHelper stackFrameHelper)
{
_logger = logger;
_stackFrameHelper = stackFrameHelper;
}
public class StackFrameHelper
{
public virtual bool HasStackFrameInfo()
{
var stackTrace = new StackTrace(true);
return stackTrace.FrameCount > 0 && stackTrace.GetFrame(0).GetFileName().IsNotNullOrWhiteSpace();
}
}
public override HealthCheck Check()
{
if (!PlatformInfo.IsMono)
{
return new HealthCheck(GetType());
}
if (!_stackFrameHelper.HasStackFrameInfo())
{
_logger.Warn("Mono is not running with --debug switch");
return new HealthCheck(GetType());
}
return new HealthCheck(GetType());
}
}
}

@ -1,46 +0,0 @@
using System;
using System.Linq;
using System.Reflection;
using NLog;
using NLog.Fluent;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Instrumentation.Extensions;
namespace NzbDrone.Core.HealthCheck.Checks
{
public class MonoTlsCheck : HealthCheckBase
{
private readonly IPlatformInfo _platformInfo;
private readonly Logger _logger;
public MonoTlsCheck(IPlatformInfo platformInfo, Logger logger)
{
_platformInfo = platformInfo;
_logger = logger;
}
public override HealthCheck Check()
{
if (!PlatformInfo.IsMono)
{
return new HealthCheck(GetType());
}
var monoVersion = _platformInfo.Version;
if (monoVersion >= new Version("5.8.0") && Environment.GetEnvironmentVariable("MONO_TLS_PROVIDER") == "legacy")
{
_logger.Debug()
.Message("Mono version {0} and legacy TLS provider is selected, recommending user to switch to btls.", monoVersion)
.WriteSentryDebug("LegacyTlsProvider", monoVersion.ToString())
.Write();
return new HealthCheck(GetType(), HealthCheckResult.Warning, "Sonarr Mono 4.x tls workaround still enabled, consider removing MONO_TLS_PROVIDER=legacy environment option");
}
return new HealthCheck(GetType());
}
public override bool CheckOnSchedule => false;
}
}

@ -1,70 +0,0 @@
using System;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Core.HealthCheck.Checks
{
public class MonoVersionCheck : HealthCheckBase
{
private readonly IPlatformInfo _platformInfo;
private readonly Logger _logger;
public MonoVersionCheck(IPlatformInfo platformInfo, Logger logger)
{
_platformInfo = platformInfo;
_logger = logger;
}
public override HealthCheck Check()
{
if (!PlatformInfo.IsMono)
{
return new HealthCheck(GetType());
}
var monoVersion = _platformInfo.Version;
// Known buggy Mono versions
if (monoVersion == new Version("4.4.0") || monoVersion == new Version("4.4.1"))
{
_logger.Debug("Mono version {0}", monoVersion);
return new HealthCheck(GetType(), HealthCheckResult.Error,
$"Currently installed Mono version {monoVersion} has a bug that causes issues connecting to indexers/download clients. You should upgrade to a higher version",
"#currently-installed-mono-version-is-old-and-unsupported");
}
// Currently best stable Mono version (5.18 gets us .net 4.7.2 support)
var bestVersion = new Version("5.20");
var targetVersion = new Version("5.18");
if (monoVersion >= targetVersion)
{
_logger.Debug("Mono version is {0} or better: {1}", targetVersion, monoVersion);
return new HealthCheck(GetType());
}
// Stable Mono versions
var stableVersion = new Version("5.18");
if (monoVersion >= stableVersion)
{
_logger.Debug("Mono version is {0} or better: {1}", stableVersion, monoVersion);
return new HealthCheck(GetType(), HealthCheckResult.Notice,
$"Currently installed Mono version {monoVersion} is supported but upgrading to {bestVersion} is recommended.",
"#currently-installed-mono-version-is-supported-but-upgrading-is-recommended");
}
var oldVersion = new Version("5.4");
if (monoVersion >= oldVersion)
{
return new HealthCheck(GetType(), HealthCheckResult.Error,
$"Currently installed Mono version {monoVersion} is no longer supported. Please upgrade Mono to version {bestVersion}.",
"#currently-installed-mono-version-is-old-and-unsupported");
}
return new HealthCheck(GetType(), HealthCheckResult.Error,
$"Currently installed Mono version {monoVersion} is old and unsupported. Please upgrade Mono to version {bestVersion}.",
"#currently-installed-mono-version-is-old-and-unsupported");
}
public override bool CheckOnSchedule => false;
}
}

@ -1,7 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI.WebControls;
using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.Indexers.Newznab

@ -1,4 +1,4 @@
using NzbDrone.Common.Disk;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
@ -21,12 +21,6 @@ namespace NzbDrone.Core.MediaCover
{
_diskProvider = diskProvider;
// Random segfaults on mono 5.0 and 5.4
if (PlatformInfo.IsMono && platformInfo.Version < new System.Version(5, 8))
{
return;
}
_enabled = true;
// More conservative memory allocation

@ -1,8 +1,6 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.UI;
using Newtonsoft.Json.Serialization;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Profiles.Releases;

@ -312,58 +312,58 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
return (int)MediaInfo_Count_Get(_handle, (IntPtr)streamKind, (IntPtr)streamNumber);
}
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfo_New();
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern void MediaInfo_Delete(IntPtr handle);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfo_Open(IntPtr handle, IntPtr fileName);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfo_Open_Buffer_Init(IntPtr handle, long fileSize, long fileOffset);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfo_Open_Buffer_Continue(IntPtr handle, byte[] buffer, IntPtr bufferSize);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern long MediaInfo_Open_Buffer_Continue_GoTo_Get(IntPtr handle);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfo_Open_Buffer_Finalize(IntPtr handle);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern void MediaInfo_Close(IntPtr handle);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfo_GetI(IntPtr handle, IntPtr streamKind, IntPtr streamNumber, IntPtr parameter, IntPtr infoKind);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfo_Get(IntPtr handle, IntPtr streamKind, IntPtr streamNumber, IntPtr parameter, IntPtr infoKind, IntPtr searchKind);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfo_Option(IntPtr handle, IntPtr option, IntPtr value);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfo_State_Get(IntPtr handle);
[DllImport("MediaInfo.dll")]
private static extern IntPtr MediaInfo_Count_Get(IntPtr handle, IntPtr StreamKind, IntPtr streamNumber);
[DllImport("mediainfo")]
private static extern IntPtr MediaInfo_Count_Get(IntPtr handle, IntPtr streamKind, IntPtr streamNumber);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfoA_New();
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern void MediaInfoA_Delete(IntPtr handle);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfoA_Open(IntPtr handle, IntPtr fileName);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfoA_Open_Buffer_Init(IntPtr handle, long fileSize, long fileOffset);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfoA_Open_Buffer_Continue(IntPtr handle, byte[] buffer, IntPtr bufferSize);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern long MediaInfoA_Open_Buffer_Continue_GoTo_Get(IntPtr handle);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfoA_Open_Buffer_Finalize(IntPtr handle);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern void MediaInfoA_Close(IntPtr handle);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfoA_GetI(IntPtr handle, IntPtr streamKind, IntPtr streamNumber, IntPtr parameter, IntPtr infoKind);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfoA_Get(IntPtr handle, IntPtr streamKind, IntPtr streamNumber, IntPtr parameter, IntPtr infoKind, IntPtr searchKind);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfoA_Option(IntPtr handle, IntPtr option, IntPtr value);
[DllImport("MediaInfo.dll")]
[DllImport("mediainfo")]
private static extern IntPtr MediaInfoA_State_Get(IntPtr handle);
[DllImport("MediaInfo.dll")]
private static extern IntPtr MediaInfoA_Count_Get(IntPtr handle, IntPtr StreamKind, IntPtr streamNumber);
[DllImport("mediainfo")]
private static extern IntPtr MediaInfoA_Count_Get(IntPtr handle, IntPtr streamKind, IntPtr streamNumber);
}
}

@ -608,7 +608,7 @@ namespace NzbDrone.Core.Organizer
private const string MediaInfoVideoDynamicRangeToken = "{MediaInfo VideoDynamicRange}";
private const string MediaInfoVideoDynamicRangeTypeToken = "{MediaInfo VideoDynamicRangeType}";
private static readonly IDictionary<string, int> MinimumMediaInfoSchemaRevisions =
private static readonly IReadOnlyDictionary<string, int> MinimumMediaInfoSchemaRevisions =
new Dictionary<string, int>(FileNameBuilderTokenEqualityComparer.Instance)
{
{MediaInfoVideoDynamicRangeToken, 5},

@ -1,9 +0,0 @@
using RestSharp;
namespace NzbDrone.Core.Rest
{
public interface IRestClientFactory
{
RestClient BuildClient(string baseUrl);
}
}

@ -1,23 +0,0 @@
using NzbDrone.Common.Serializer;
using RestSharp.Serializers;
namespace NzbDrone.Core.Rest
{
public class JsonNetSerializer : ISerializer
{
public JsonNetSerializer()
{
ContentType = "application/json";
}
public string Serialize(object obj)
{
return obj.ToJson();
}
public string RootElement { get; set; }
public string Namespace { get; set; }
public string DateFormat { get; set; }
public string ContentType { get; set; }
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save