SickBeard - Auto-updating from Git

- Not quite complete - still needs testing
 - Seems to break SABnzbd+ when installing Python??
This commit is contained in:
Iristyle
2012-10-16 01:44:06 -04:00
parent 542015d081
commit f3310f6f1a
5 changed files with 614 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
#Requires -Version 2.0
function Get-IniContent
{
<#
.Synopsis
Reads the contents of an INI file into an OrderedDictionary
.Description
The dictionary can be manipulated the same way a Hashtable can, by
adding or removing keys to the various sections.
By using an OrderedDictionary, the contents of the file can be
roundtripped through the Out-IniFile cmdlet.
Nested INI sections represented like the following are supported:
[foo]
name = value
[[bar]]
name = value
;name = value
Comment lines prefixed with a ; are returned in the output with a name
of {Comment-X} where X is the comment index within the entire INI file
Comments also have an IsComment property attached to the values, so
that Out-IniFile may properly handle them.
.Notes
Inspiration from Oliver Lipkau <oliver@lipkau.net>
http://tinyurl.com/9g4zonn
.Inputs
String or FileInfo
.Outputs
Collections.Specialized.OrderedDictionary
Keys with a OrderdedDictionary Value are representative of sections
Sections may be nested to any arbitrary depth
.Parameter Path
Specifies the path to the input file. Can be a string or FileInfo
object
.Example
$configFile = Get-IniContent .\foo.ini
Description
-----------
Parses the foo.ini file contents into an OrderedDictionary for local
reading or manipulation
.Example
$configFile = .\foo.ini | Get-IniContent
$configFile.SectionName | Select *
Description
-----------
Same as the first example, but using pipeline input.
Additionally outputs all values stored in the [SectionName] section of
the INI file.
#>
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True, Mandatory=$True)]
[ValidateNotNullOrEmpty()]
[ValidateScript({ (Test-Path $_) -and ($_.Extension -eq '.ini') })]
[IO.FileInfo]
$Path
)
Process
{
Write-Verbose "[INFO]: Get-IniContent processing file [$Path]"
# TODO: once Powershell 3 is common, this can be $ini = [ordered]@{}
$ini = New-Object Collections.Specialized.OrderedDictionary
function getCurrentOrEmptySection($section)
{
if (!$section)
{
if (!$ini.Keys -contains '')
{
$ini[''] = New-Object Collections.Specialized.OrderedDictionary
}
$section = $ini['']
}
return $section
}
$comments = 0
$sections = @($ini)
switch -regex -file $Path
{
#http://stackoverflow.com/questions/9155483/regular-expressions-balancing-group
'\[((?:[^\[\]]|(?<BR> \[)|(?<-BR> \]))+(?(BR)(?!)))\]' # Section
{
$name = $matches[1]
# since the regex above is balanced, depth is a simple count
$depth = ($_ | Select-String '\[' -All).Matches |
Measure-Object |
Select -ExpandProperty Count
# root section
Write-Verbose "Parsing section $_ at depth $depth"
# handles any level of nested section
$section = New-Object Collections.Specialized.OrderedDictionary
$sections[$depth - 1][$name] = $section
if ($sections.Length -le $depth)
{
$sections += $section
}
else
{
$sections[$depth] = $section
}
}
'^(;.*)$' # Comment
{
$section = getCurrentOrEmptySection $section
$name = '{Comment-' + ($comments++) + '}'
$section[$name] = $matches[1] |
Add-Member -MemberType NoteProperty -Name IsComment -Value $true -PassThru
}
'(.+?)\s*=\s*(.*)' # Key
{
$name, $value = $matches[1..2]
(getCurrentOrEmptySection $section)[$name] = $value
}
}
Write-Verbose "[SUCCESS]: Get-IniContent processed file [$path]"
return $ini
}
}

View File

@@ -0,0 +1,181 @@
#Requires -Version 2.0
function Out-IniFile
{
<#
.Synopsis
Write the contents of a Hashtable or OrderedDictionary to an INI file
.Description
The input can either be a standard Powershell hash created with @{},
an [ordered]@{} in Powershell 3, an OrderedDictionary created by the
Get-IniContent cmdlet.
Will write out the fully nested structure to an INI file
.Notes
Inspiration from Oliver Lipkau <oliver@lipkau.net>
http://tinyurl.com/94tdhdx
.Inputs
Accepts either a Collections.Specialized.OrderedDictionary or
a standard Powershell Hashtable
.Outputs
Returns an IO.FileInfo object if -PassThru is specified
System.IO.FileSystemInfo
.Parameter InputObject
Specifies the OrderedDictionary or Hashtable to be written to the file
.Parameter FilePath
Specifies the path to the output file.
.Parameter Encoding
Specifies the type of character encoding used in the file. Valid
values are "Unicode", "UTF7", "UTF8", "UTF32", "ASCII",
"BigEndianUnicode", "Default", and "OEM". "Unicode" is the default.
"Default" uses the encoding of the system's current ANSI code page.
"OEM" uses the current original equipment manufacturer code page
identifier for the operating system.
.Parameter Append
Adds the output to the end of an existing file, instead of replacing
the file contents.
.Parameter Force
Allows the cmdlet to overwrite an existing read-only file. Even using
the Force parameter, the cmdlet cannot override security restrictions.
.Parameter PassThru
Returns the newly written FileInfo. By default, this cmdlet does not
generate any output.
.Example
@{ Section = @{ Foo = 'bar'; Baz = 1} } |
Out-IniFile -FilePath .\foo.ini
Description
-----------
Writes the given Hashtable to foo.ini as
[Section]
Baz=1
Foo=bar
.Example
@{ Section = [ordered]@{ Foo = 'bar'; Baz = 1} } |
Out-IniFile -FilePath .\foo.ini
Description
-----------
Writes the given Hashtable to foo.ini, in the given order
[Section]
Foo=bar
Baz=1
.Example
@{ Section = [ordered]@{ Foo = 'bar'; Baz = 1} } |
Out-IniFile -FilePath .\foo.ini -Force
Description
-----------
Same as previous example, except that foo.ini is overwritten should
it already exist
.Example
$file = @{ Section = [ordered]@{ Foo = 'bar'; Baz = 1} } |
Out-IniFile -FilePath .\foo.ini
Description
-----------
Same as previous example, except that the FileInfo object is returned
.Example
$config = Get-IniContent .\foo.ini
$config.Section.Value = 'foo'
$config | Out-IniFile -Path .\foo.ini -Force
Description
-----------
Parses the foo.ini file contents into an OrderedDictionary with the
Get-IniContent cmdlet. Manipulates the contents, then overwrites the
existing file.
#>
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline=$true, Mandatory=$true)]
[ValidateScript({ ($_ -is [Collections.Specialized.OrderedDictionary]) -or `
($_ -is [Hashtable]) })]
[ValidateNotNullOrEmpty()]
$InputObject,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[ValidateScript({ Test-Path $_ -IsValid })]
[string]
$FilePath,
[Parameter(Mandatory=$false)]
[ValidateSet('Unicode','UTF7','UTF8','UTF32','ASCII','BigEndianUnicode',
'Default','OEM')]
[string]
$Encoding = 'Unicode',
[switch]
$Append,
[switch]
$Force,
[switch]
$PassThru
)
process
{
Write-Verbose "[INFO]: Out-IniFile writing file [$FilePath]"
if ((New-Object IO.FileInfo($FilePath)).Extension -ne '.ini')
{
Write-Warning 'Out-IniFile [$FilePath] does not end in .ini extension'
}
if ((Test-Path $FilePath) -and (!$Force))
{
throw "The -Force switch must be applied to overwrite $outFile"
}
$outFile = $null
if ($append) { $outFile = Get-Item $FilePath -ErrorAction SilentlyContinue }
if ([string]::IsNullOrEmpty($outFile) -or (!(Test-Path $outFile)))
{ $outFile = New-Item -ItemType File -Path $FilePath -Force:$Force }
#recursive function write sections at various depths
function WriteKeyValuePairs($dictionary, $sectionName = $null, $depth = 0)
{
#Sections - take into account nested depth
if ((![string]::IsNullOrEmpty($sectionName)) -and ($depth -gt 0))
{
$sectionName = "$('[' * $depth)$sectionName$(']' * $depth)"
Write-Verbose "[INFO]: writing section $sectionName to $outFile"
Add-Content -Path $outFile -Value $sectionName -Encoding $Encoding
}
$dictionary.GetEnumerator() |
% {
if ($_.Value -is [Collections.Specialized.OrderedDictionary] -or
$_.Value -is [Hashtable])
{
Write-Verbose "[INFO]: Writing section [$($_.Key)] of $sectionName"
WriteKeyValuePairs $_.Value $_.Key ($depth + 1)
}
elseif ($_.Value.IsComment -or ($_.Key -match '^\{Comment\-[\d]+\}'))
{
Write-Verbose "[INFO]: Writing comment $($_.Value)"
Add-Content -Path $outFile -Value $_.Value -Encoding $Encoding
}
else
{
Write-Verbose "[INFO]: Writing key $($_.Key)"
Add-Content -Path $outFile -Value "$($_.Key)=$($_.Value)" -Encoding $Encoding
}
}
}
WriteKeyValuePairs $InputObject
Write-Verbose "[SUCCESS]: Out-IniFile wrote file [$outFile]"
if ($PassThru) { return $outFile }
}
}

View File

@@ -0,0 +1,43 @@
<?xml version="1.0"?>
<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<metadata>
<id>SickBeard.Source</id>
<title>Sick Beard Usenet PVR</title>
<version>10.00.1600.22-Alpha</version>
<authors>midgetspy</authors>
<owners>Ethan Brown</owners>
<summary>The ultimate PVR application that searches for and manages your TV shows.</summary>
<description>Sick Beard is a PVR for newsgroup users (with limited torrent support). It watches for new episodes of your favorite shows and when they are posted it downloads them, sorts and renames them, and optionally generates metadata for them. It currently supports NZBs.org, NZBMatrix, NZBs'R'Us, Newzbin, Womble's Index, NZB.su, TVTorrents and EZRSS and retrieves show information from theTVDB.com and TVRage.com.
This package
- Installs SABnzbd+ (compiled version)
- Installs Python
- Installs Cheetah with Windows optimizations
- Git clones the SickBeard source to site-packages
- Configures Git within SickBeard to auto-update
- Configures for XBMC metadata
- Configures XMBC notifications - to port 9090 as SABnzbd+ uses 8080
- Configures SickBeard to send NZBs to SABnzbd+ using API key
- Configures the SickBeard post-processing script in SABnzbd+
- Configures SickBeard to run as a Windows service
</description>
<projectUrl>http://sickbeard.com/</projectUrl>
<tags>Sickbeard Python Usenet</tags>
<!-- Eula is in installer
<licenseUrl></licenseUrl>
-->
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<iconUrl>https://github.com/Iristyle/ChocolateyPackages/raw/master/SickBeard/sickbeard.png</iconUrl>
<releaseNotes></releaseNotes>
<dependencies>
<dependency id="python" version="[2.7,3.0)" />
<dependency id="Git.Install" />
<dependency id="rktools.2003" />
<dependency id="Python.Cheetah" />
<dependency id="SABnzbd" />
</dependencies>
</metadata>
<files>
<file src="tools\**" target="tools" />
</files>
</package>

BIN
Sickbeard/sickbeard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,259 @@
$package = 'Sickbeard'
try {
function Get-CurrentDirectory
{
$thisName = $MyInvocation.MyCommand.Name
[IO.Path]::GetDirectoryName((Get-Content function:$thisName).File)
}
# load INI parser
. (Join-Path (Get-CurrentDirectory) 'Get-IniContent.ps1')
. (Join-Path (Get-CurrentDirectory) 'Out-IniFile.ps1')
#simulate the unix command for finding things in path
#http://stackoverflow.com/questions/63805/equivalent-of-nix-which-command-in-powershell
function Which([string]$cmd)
{
Get-Command -ErrorAction "SilentlyContinue" $cmd |
Select -ExpandProperty Definition
}
function WaitService([string]$name, [int]$seconds)
{
Write-Host "Waiting up to $($seconds)s for $name to start..."
$result = 0..($seconds * 2) |
% {
$service = Get-Service $name -ErrorAction SilentlyContinue
if ($service -and ($service.Status -eq 'Running'))
{ return $true }
elseif ($service)
{ Start-Sleep -Milliseconds 500 }
return $false
} |
Select -Last 1
return $result
}
# Use PYTHONHOME if it exists, or fallback to 'Where' to search PATH
if ($Env:PYTHONHOME) { $localPython = Join-Path $Env:PYTHONHOME 'python.exe' }
if (!$Env:PYTHONHOME -or !(Test-Path $localPython))
{ $localPython = Which python.exe }
if (!(Test-Path $localPython))
{
Write-ChocolateyFailure 'SickBeard requires a Python runtime to install'
return
}
$pythonRoot = Split-Path $localPython
$sitePackages = (Join-Path (Join-Path $pythonRoot 'Lib') 'site-packages')
if (!(Test-Path $sitePackages))
{
Write-ChocolateyFailure 'Could not find Python site-packages directory'
return
}
# grab the latest sources if not present
Push-Location $sitePackages
$git = Which git
$sickBeardPath = (Join-Path $sitePackages 'Sick-Beard')
if (Test-Path $sickBeardPath)
{
Write-ChocolateySuccess 'SickBeard already installed!'
return
}
else
{
Write-ChocolateySuccess 'Cloning SickBeard source from GitHub'
&git clone https://github.com/midgetspy/Sick-Beard
}
# Read SABNzbd+ config file to find scripts directory
$sabDataPath = Join-Path $Env:LOCALAPPDATA 'sabnzbd'
$sabIniPath = Join-Path $sabDataPath 'sabnzbd.ini'
if (Test-Path $sabIniPath)
{
Write-Host "Reading SABnzbd+ config file at $sabIniPath"
$sabConfig = Get-IniContent $sabIniPath
# 3 options - missing script_dir, script_dir set to "", or configured script_dir
if (!$sabConfig.misc.script_dir -or `
($sabConfig.misc.script_dir -eq "`"`""))
{
$scriptDir = (Join-Path $sabDataPath 'scripts')
Write-Host "Configured SABnzbd+ script_dir to $scriptDir"
$sabConfig.misc.script_dir = $scriptDir
$sabConfig | Out-IniFile -FilePath $sabIniPath -Force
}
if (!(Test-Path $sabConfig.misc.script_dir))
{
[Void]New-Item -Path $sabConfig.misc.script_dir -Type Directory
}
# copy and configure autoprocessing scripts in SABNzbd+ scripts directory
Write-Host "Copying SickBeard post-processing scripts to SABnzbd+"
$sourceScripts = (Join-Path $sickBeardPath 'autoProcessTV')
Get-ChildItem $sourceScripts |
? { !(Test-Path (Join-Path $sabConfig.misc.script_dir $_.Name)) } |
Copy-Item -Destination $sabConfig.misc.script_dir
if (!$sabconfig.categories.tv)
{
Write-Host "Configuring tv category inside SABnzbd+"
$tv = New-Object Collections.Specialized.OrderedDictionary
$tv.priority = 0;
$tv.pp = 3; # Download + Unpack +Repair +Delete
$tv.name = 'tv';
$tv.script = 'sabToSickBeard.py';
$tv.newzbin = '';
$tv.dir = 'tv';
$sabconfig.categories.tv = $tv
}
if (([string]::IsNullOrEmpty($sabconfig.categories.tv.script)) -or `
($sabconfig.categories.tv.script -ieq 'None'))
{
$sabconfig.categories.tv.script = 'sabToSickBeard.py'
}
Write-Host 'Configured tv category in SABnzbd+'
$sabConfig | Out-IniFile -FilePath $sabIniPath -Force
}
# regardless of sabnzbd+ install status, .PY should be executable
if (($ENV:PATHEXT -split ';') -inotcontains '.PY')
{
Write-Host 'Adding .PY to PATHEXT'
$ENV:PATHEXT += ';.PY'
[Environment]::SetEnvironmentVariable('PATHEXT', $ENV:PATHEXT, 'Machine')
}
# find resource kit tools and configure sickbeard as a service
# http://htpcbuild.com/htpc-software/sickbeard/sickbeard-service/
# http://stackoverflow.com/questions/32404/can-i-run-a-python-script-as-a-service-in-windows-how
$resourceKit = ${Env:ProgramFiles(x86)}, $Env:ProgramFiles |
% { Join-Path (Join-Path $_ 'Windows Resource Kits') 'Tools' } |
? { Test-Path $_ } |
Select -First 1
if ($resourceKit)
{
Write-Host "Found resource kit - registering SickBeard as a service"
Push-Location $resourceKit
$srvAny = Join-Path $resourceKit 'srvany.exe'
.\instsrv SickBeard $srvany
# Set-Service cmdlet doesn't have depend OR delayed start :(
Write-Host "Configuring service delayed auto with Tcpip dependency"
sc.exe config SickBeard depend= Tcpip
sc.exe config SickBeard start= delayed-auto
New-Item HKLM:\SYSTEM\CurrentControlSet\Services -Name SickBeard `
-ErrorAction SilentlyContinue | Out-Null
New-Item HKLM:\SYSTEM\CurrentControlSet\Services\SickBeard `
-Name Parameters -ErrorAction SilentlyContinue | Out-Null
$sickParams = Get-Item HKLM:\SYSTEM\CurrentControlSet\Services\SickBeard\Parameters
New-ItemProperty -Path $sickParams.PSPath -PropertyType String `
-Name 'AppDirectory' -Value $pythonRoot -Force | Out-Null
$pythonW = (Join-Path $pythonRoot 'pythonw.exe')
New-ItemProperty -Path $sickParams.PSPath -PropertyType String `
-Name 'Application' -Value $pythonW -Force | Out-Null
$startSickBeard = (Join-Path $sickBeardPath 'sickbeard.py')
New-ItemProperty -Path $sickParams.PSPath -PropertyType String `
-Name 'AppParameters' -Value $startSickBeard -Force | Out-Null
Start-Service SickBeard
# config files are created on first start-up
if (WaitService 'SickBeard', 20)
{
$configPath = (Join-Path $sickBeardPath 'config.ini')
$sickBeardConfig = Get-IniContent $configPath
Write-Host "Configuring Windows Firewall for the SickBeard port"
# configure windows firewall
netsh advfirewall firewall delete rule name="SickBeard"
# program="$pythonW"
$port = $sickBeardConfig.General.web_port
netsh advfirewall firewall add rule name="SickBeard" dir=in protocol=tcp localport=$port action=allow
# http://forums.sabnzbd.org/viewtopic.php?t=3072&start=855
$sickBeardConfig.General.git_path = $git
$sickBeardConfig.General.launch_browser = 0
$sickBeardConfig.General.use_api = 1
$sickBeardConfig.General.process_automatically = 0
$sickBeardConfig.General.move_associated_files = 1
$sickBeardConfig.General.api_key = [Guid]::NewGuid().ToString('n')
$sickBeardConfig.General.metadata_xbmc = '1|1|1|1|1|1'
$sickBeardConfig.General.naming_pattern = '%SN - %Sx%0E - %EN'
#range like x03-05
$sickBeardConfig.General.naming_multi_ep = 8
# configure for XBMC with default user / pass of xbmc / xbmc
$sickBeardConfig.XBMC.use_xbmc = 1
$sickBeardConfig.XBMC.xbmc_update_library = 1
$sickBeardConfig.XBMC.xbmc_host = 'localhost:9090'
$sickBeardConfig.XBMC.xbmc_username = 'xbmc'
$sickBeardConfig.XBMC.xbmc_password = 'xbmc'
# configure SickBeard to use SABNzbd
$sickBeardConfig.General.nzb_method = 'sabnzbd'
$sickBeardConfig.SABnzbd.sab_username = $sabConfig.misc.username
$sickBeardConfig.SABnzbd.sab_password = $sabConfig.misc.password
$sickBeardConfig.SABnzbd.sab_apikey = $sabConfig.misc.api_key
$sickBeardConfig.SABnzbd.sab_category = 'tv'
$sickBeardConfig.SABnzbd.sab_host = "http://localhost:$($sabConfig.misc.port)/"
$sickBeardConfig | Out-IniFile -File $configPath -Force -Encoding ASCII
Stop-Service SickBeard
Start-Service SickBeard
}
$autoConfig = Join-Path $sabConfig.misc.script_dir 'autoProcessTV.cfg'
if (!(Test-Path $autoConfig))
{
$processConfig = @{
'SickBeard' = @{
host = $sickBeardConfig.General.web_host;
port = $sickBeardConfig.General.web_port;
username = $sickBeardConfig.General.web_username;
password = $sickBeardConfig.General.web_password;
web_root = $sickBeardConfig.General.web_root;
ssl = 0;
}
}
$processConfig | Out-IniFile -FilePath $autoConfig
Write-Host @"
SickBeard SABNzbd+ post-processing scripts configured
If SickBeard is reconfigured with a username or password or another
host then those same changes must be made to $sickBeardConfig
"@
}
Write-Host 'Restarting SABnzbd+ to accept configuration changes'
$url = ("http://localhost:$($sabConfig.misc.port)/api?mode=restart" +
"&apikey=$($sabConfig.misc.api_key)")
(New-Object Net.WebClient).DownloadString($url)
#wait up to 5 seconds for service to fire up
if (WaitService 'SickBeard' 5)
{
#launch local default browser for additional config
[Diagnostics.Process]::Start("http://localhost:$($sickBeardConfig.General.web_port)")
}
Pop-Location
}
Write-ChocolateySuccess $package
} catch {
Write-ChocolateyFailure $package "$($_.Exception.Message)"
throw
}