2014-02-14 05:31:49 +00:00
using System ;
2014-06-04 20:08:36 +00:00
using System.IO ;
2014-02-14 05:31:49 +00:00
using System.Collections.Generic ;
using System.Linq ;
2014-08-12 23:40:40 +00:00
using System.Threading ;
2014-07-04 08:09:48 +00:00
using FluentValidation.Results ;
2014-02-14 05:31:49 +00:00
using NLog ;
using NzbDrone.Common ;
2014-07-04 20:27:21 +00:00
using NzbDrone.Common.Disk ;
2014-05-11 04:37:07 +00:00
using NzbDrone.Common.Http ;
2014-05-27 21:04:13 +00:00
using NzbDrone.Core.Configuration ;
2014-04-19 15:09:22 +00:00
using NzbDrone.Core.Indexers ;
2014-02-14 05:31:49 +00:00
using NzbDrone.Core.Parser ;
using NzbDrone.Core.Parser.Model ;
2014-07-09 19:35:05 +00:00
using NzbDrone.Core.Validation ;
2014-09-11 20:24:00 +00:00
using NzbDrone.Core.RemotePathMappings ;
2014-02-14 05:31:49 +00:00
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
2014-09-07 11:39:00 +00:00
public class Sabnzbd : UsenetClientBase < SabnzbdSettings >
2014-02-14 05:31:49 +00:00
{
2014-04-01 20:07:41 +00:00
private readonly ISabnzbdProxy _proxy ;
2014-02-14 05:31:49 +00:00
2014-07-04 20:27:21 +00:00
public Sabnzbd ( ISabnzbdProxy proxy ,
2014-09-11 23:49:41 +00:00
IHttpClient httpClient ,
2014-05-27 21:04:13 +00:00
IConfigService configService ,
2014-07-04 20:27:21 +00:00
IDiskProvider diskProvider ,
2014-05-27 21:04:13 +00:00
IParsingService parsingService ,
2014-09-11 20:24:00 +00:00
IRemotePathMappingService remotePathMappingService ,
2014-02-14 05:31:49 +00:00
Logger logger )
2014-09-11 20:24:00 +00:00
: base ( httpClient , configService , diskProvider , parsingService , remotePathMappingService , logger )
2014-02-14 05:31:49 +00:00
{
2014-04-01 20:07:41 +00:00
_proxy = proxy ;
2014-02-14 05:31:49 +00:00
}
2014-09-07 11:39:00 +00:00
protected override string AddFromNzbFile ( RemoteEpisode remoteEpisode , string filename , byte [ ] fileContent )
2014-04-19 15:09:22 +00:00
{
2014-02-14 05:31:49 +00:00
var title = remoteEpisode . Release . Title ;
var category = Settings . TvCategory ;
var priority = remoteEpisode . IsRecentEpisode ( ) ? Settings . RecentTvPriority : Settings . OlderTvPriority ;
2014-09-07 11:39:00 +00:00
var response = _proxy . DownloadNzb ( fileContent , title , category , priority , Settings ) ;
2014-02-14 05:31:49 +00:00
2014-09-07 11:39:00 +00:00
if ( response ! = null & & response . Ids . Any ( ) )
{
return response . Ids . First ( ) ;
2014-02-14 05:31:49 +00:00
}
2014-09-07 11:39:00 +00:00
return null ;
2014-02-14 05:31:49 +00:00
}
2014-04-19 15:09:22 +00:00
private IEnumerable < DownloadClientItem > GetQueue ( )
2014-02-14 05:31:49 +00:00
{
2014-04-19 15:09:22 +00:00
SabnzbdQueue sabQueue ;
try
2014-02-14 05:31:49 +00:00
{
2014-04-19 15:09:22 +00:00
sabQueue = _proxy . GetQueue ( 0 , 0 , Settings ) ;
}
catch ( DownloadClientException ex )
{
_logger . ErrorException ( ex . Message , ex ) ;
return Enumerable . Empty < DownloadClientItem > ( ) ;
}
var queueItems = new List < DownloadClientItem > ( ) ;
2014-03-07 06:28:06 +00:00
2014-04-19 15:09:22 +00:00
foreach ( var sabQueueItem in sabQueue . Items )
{
var queueItem = new DownloadClientItem ( ) ;
queueItem . DownloadClient = Definition . Name ;
queueItem . DownloadClientId = sabQueueItem . Id ;
queueItem . Category = sabQueueItem . Category ;
queueItem . Title = sabQueueItem . Title ;
queueItem . TotalSize = ( long ) ( sabQueueItem . Size * 1024 * 1024 ) ;
queueItem . RemainingSize = ( long ) ( sabQueueItem . Sizeleft * 1024 * 1024 ) ;
queueItem . RemainingTime = sabQueueItem . Timeleft ;
if ( sabQueue . Paused | | sabQueueItem . Status = = SabnzbdDownloadStatus . Paused )
2014-03-07 06:28:06 +00:00
{
2014-04-19 15:09:22 +00:00
queueItem . Status = DownloadItemStatus . Paused ;
2014-05-24 19:02:25 +00:00
queueItem . RemainingTime = null ;
2014-03-07 06:28:06 +00:00
}
2014-04-19 15:09:22 +00:00
else if ( sabQueueItem . Status = = SabnzbdDownloadStatus . Queued | | sabQueueItem . Status = = SabnzbdDownloadStatus . Grabbing )
2014-03-07 06:28:06 +00:00
{
2014-04-19 15:09:22 +00:00
queueItem . Status = DownloadItemStatus . Queued ;
2014-03-07 06:28:06 +00:00
}
2014-04-19 15:09:22 +00:00
else
2014-02-14 05:31:49 +00:00
{
2014-04-19 15:09:22 +00:00
queueItem . Status = DownloadItemStatus . Downloading ;
}
2014-02-14 05:31:49 +00:00
2014-04-19 15:09:22 +00:00
if ( queueItem . Title . StartsWith ( "ENCRYPTED /" ) )
{
queueItem . Title = queueItem . Title . Substring ( 11 ) ;
queueItem . IsEncrypted = true ;
2014-02-14 05:31:49 +00:00
}
2014-04-19 15:09:22 +00:00
queueItems . Add ( queueItem ) ;
}
return queueItems ;
2014-02-14 05:31:49 +00:00
}
2014-04-19 15:09:22 +00:00
private IEnumerable < DownloadClientItem > GetHistory ( )
2014-02-14 05:31:49 +00:00
{
2014-03-07 06:28:06 +00:00
SabnzbdHistory sabHistory ;
try
{
2014-05-27 21:04:13 +00:00
sabHistory = _proxy . GetHistory ( 0 , _configService . DownloadClientHistoryLimit , Settings ) ;
2014-03-07 06:28:06 +00:00
}
catch ( DownloadClientException ex )
{
_logger . ErrorException ( ex . Message , ex ) ;
2014-04-19 15:09:22 +00:00
return Enumerable . Empty < DownloadClientItem > ( ) ;
2014-03-07 06:28:06 +00:00
}
2014-04-19 15:09:22 +00:00
var historyItems = new List < DownloadClientItem > ( ) ;
2014-02-14 05:31:49 +00:00
2014-03-07 06:28:06 +00:00
foreach ( var sabHistoryItem in sabHistory . Items )
2014-02-14 05:31:49 +00:00
{
2014-04-19 15:09:22 +00:00
var historyItem = new DownloadClientItem
{
DownloadClient = Definition . Name ,
DownloadClientId = sabHistoryItem . Id ,
Category = sabHistoryItem . Category ,
Title = sabHistoryItem . Title ,
TotalSize = sabHistoryItem . Size ,
RemainingSize = 0 ,
DownloadTime = TimeSpan . FromSeconds ( sabHistoryItem . DownloadTime ) ,
RemainingTime = TimeSpan . Zero ,
Message = sabHistoryItem . FailMessage
} ;
if ( sabHistoryItem . Status = = SabnzbdDownloadStatus . Failed )
{
2014-09-18 21:00:52 +00:00
if ( sabHistoryItem . FailMessage . IsNotNullOrWhiteSpace ( ) & &
sabHistoryItem . FailMessage . Equals ( "Unpacking failed, write error or disk is full?" , StringComparison . InvariantCultureIgnoreCase ) )
{
historyItem . Status = DownloadItemStatus . Warning ;
}
else
{
historyItem . Status = DownloadItemStatus . Failed ;
}
2014-04-19 15:09:22 +00:00
}
else if ( sabHistoryItem . Status = = SabnzbdDownloadStatus . Completed )
{
historyItem . Status = DownloadItemStatus . Completed ;
}
else // Verifying/Moving etc
{
historyItem . Status = DownloadItemStatus . Downloading ;
}
2014-02-14 05:31:49 +00:00
2014-09-11 20:24:00 +00:00
var outputPath = _remotePathMappingService . RemapRemoteToLocal ( Settings . Host , sabHistoryItem . Storage ) ;
if ( ! outputPath . IsNullOrWhiteSpace ( ) )
2014-06-04 20:08:36 +00:00
{
2014-09-11 20:24:00 +00:00
historyItem . OutputPath = outputPath ;
2014-07-19 22:53:22 +00:00
2014-09-11 20:24:00 +00:00
var parent = outputPath . GetParentPath ( ) ;
2014-07-19 22:53:22 +00:00
while ( parent ! = null )
2014-06-04 20:08:36 +00:00
{
2014-07-19 22:53:22 +00:00
if ( Path . GetFileName ( parent ) = = sabHistoryItem . Title )
{
historyItem . OutputPath = parent ;
}
parent = parent . GetParentPath ( ) ;
2014-06-04 20:08:36 +00:00
}
}
2014-02-14 05:31:49 +00:00
historyItems . Add ( historyItem ) ;
}
return historyItems ;
}
2014-04-19 15:09:22 +00:00
public override IEnumerable < DownloadClientItem > GetItems ( )
2014-02-14 05:31:49 +00:00
{
2014-09-11 20:24:00 +00:00
MigrateLocalCategoryPath ( ) ;
2014-07-05 14:21:44 +00:00
2014-04-19 15:09:22 +00:00
foreach ( var downloadClientItem in GetQueue ( ) . Concat ( GetHistory ( ) ) )
{
2014-07-05 14:21:44 +00:00
if ( downloadClientItem . Category = = Settings . TvCategory )
{
yield return downloadClientItem ;
}
2014-04-19 15:09:22 +00:00
}
2014-02-14 05:31:49 +00:00
}
2014-07-19 17:37:06 +00:00
public override void RemoveItem ( String id )
2014-02-14 05:31:49 +00:00
{
2014-04-19 15:09:22 +00:00
if ( GetQueue ( ) . Any ( v = > v . DownloadClientId = = id ) )
{
_proxy . RemoveFrom ( "queue" , id , Settings ) ;
}
else
{
_proxy . RemoveFrom ( "history" , id , Settings ) ;
}
2014-04-01 20:07:41 +00:00
}
2014-07-19 17:37:06 +00:00
public override String RetryDownload ( String id )
2014-04-01 20:07:41 +00:00
{
2014-07-19 17:37:06 +00:00
// Sabnzbd changed the nzo_id for retried downloads without reporting it back to us. We need to try to determine the new ID.
var history = GetHistory ( ) . Where ( v = > v . DownloadClientId = = id ) . ToList ( ) ;
2014-04-01 20:07:41 +00:00
_proxy . RetryDownload ( id , Settings ) ;
2014-07-19 17:37:06 +00:00
if ( history . Count ( ) ! = 1 )
{
2014-08-12 23:40:40 +00:00
_logger . Warn ( "History item missing. Couldn't get the new nzoid." ) ;
2014-07-19 17:37:06 +00:00
return id ;
}
2014-07-27 23:13:39 +00:00
for ( int i = 0 ; i < 3 ; i + + )
2014-07-19 17:37:06 +00:00
{
2014-08-12 23:40:40 +00:00
var queue = GetQueue ( ) . Where ( v = > v . Category = = history . First ( ) . Category & & v . Title = = history . First ( ) . Title ) . ToList ( ) ;
2014-07-27 23:13:39 +00:00
if ( queue . Count ( ) = = 1 )
{
return queue . First ( ) . DownloadClientId ;
}
if ( queue . Count ( ) > 2 )
{
2014-08-12 23:40:40 +00:00
_logger . Warn ( "Multiple items with the correct title. Couldn't get the new nzoid." ) ;
2014-07-27 23:13:39 +00:00
return id ;
}
2014-08-12 23:40:40 +00:00
Thread . Sleep ( 500 ) ;
2014-07-19 17:37:06 +00:00
}
2014-08-12 23:40:40 +00:00
_logger . Warn ( "No items with the correct title. Couldn't get the new nzoid." ) ;
2014-07-27 23:13:39 +00:00
return id ;
2014-02-14 05:31:49 +00:00
}
2014-07-04 20:27:21 +00:00
protected IEnumerable < SabnzbdCategory > GetCategories ( SabnzbdConfig config )
{
var completeDir = config . Misc . complete_dir . TrimEnd ( '\\' , '/' ) ;
2014-07-11 22:16:49 +00:00
if ( ! completeDir . StartsWith ( "/" ) & & ! completeDir . StartsWith ( "\\" ) & & ! completeDir . Contains ( ':' ) )
{
var queue = _proxy . GetQueue ( 0 , 1 , Settings ) ;
if ( queue . DefaultRootFolder . StartsWith ( "/" ) )
{
completeDir = queue . DefaultRootFolder + "/" + completeDir ;
}
else
{
completeDir = queue . DefaultRootFolder + "\\" + completeDir ;
}
}
2014-07-04 20:27:21 +00:00
foreach ( var category in config . Categories )
{
var relativeDir = category . Dir . TrimEnd ( '*' ) ;
if ( relativeDir . IsNullOrWhiteSpace ( ) )
{
category . FullPath = completeDir ;
}
else if ( completeDir . StartsWith ( "/" ) )
{ // Process remote Linux paths irrespective of our own OS.
if ( relativeDir . StartsWith ( "/" ) )
{
category . FullPath = relativeDir ;
}
else
{
category . FullPath = completeDir + "/" + relativeDir ;
}
}
else
{ // Process remote Windows paths irrespective of our own OS.
if ( relativeDir . StartsWith ( "\\" ) | | relativeDir . Contains ( ':' ) )
{
category . FullPath = relativeDir ;
}
else
{
category . FullPath = completeDir + "\\" + relativeDir ;
}
}
yield return category ;
}
}
2014-06-06 05:55:38 +00:00
public override DownloadClientStatus GetStatus ( )
{
2014-07-04 20:27:21 +00:00
var config = _proxy . GetConfig ( Settings ) ;
var categories = GetCategories ( config ) . ToArray ( ) ;
var category = categories . FirstOrDefault ( v = > v . Name = = Settings . TvCategory ) ;
if ( category = = null )
{
category = categories . FirstOrDefault ( v = > v . Name = = "*" ) ;
}
2014-06-06 05:55:38 +00:00
var status = new DownloadClientStatus
{
IsLocalhost = Settings . Host = = "127.0.0.1" | | Settings . Host = = "localhost"
} ;
2014-07-04 20:27:21 +00:00
if ( category ! = null )
{
2014-09-11 20:24:00 +00:00
status . OutputRootFolders = new List < String > { _remotePathMappingService . RemapRemoteToLocal ( Settings . Host , category . FullPath ) } ;
2014-07-04 20:27:21 +00:00
}
2014-06-06 05:55:38 +00:00
return status ;
}
2014-07-09 22:11:57 +00:00
protected override void Test ( List < ValidationFailure > failures )
2014-02-26 05:40:47 +00:00
{
2014-07-04 08:09:48 +00:00
failures . AddIfNotNull ( TestConnection ( ) ) ;
2014-07-09 22:11:57 +00:00
failures . AddIfNotNull ( TestAuthentication ( ) ) ;
2014-08-02 22:51:20 +00:00
failures . AddIfNotNull ( TestGlobalConfig ( ) ) ;
2014-07-04 08:09:48 +00:00
failures . AddIfNotNull ( TestCategory ( ) ) ;
}
private ValidationFailure TestConnection ( )
{
try
{
2014-07-04 20:27:21 +00:00
_proxy . GetVersion ( Settings ) ;
2014-07-04 08:09:48 +00:00
}
catch ( Exception ex )
2014-06-12 21:46:08 +00:00
{
2014-07-04 08:09:48 +00:00
_logger . ErrorException ( ex . Message , ex ) ;
return new ValidationFailure ( "Host" , "Unable to connect to SABnzbd" ) ;
2014-06-12 21:46:08 +00:00
}
2014-07-04 08:09:48 +00:00
return null ;
2014-02-26 05:40:47 +00:00
}
2014-07-09 22:11:57 +00:00
private ValidationFailure TestAuthentication ( )
{
try
{
_proxy . GetConfig ( Settings ) ;
}
catch ( Exception ex )
{
if ( ex . Message . ContainsIgnoreCase ( "API Key Incorrect" ) )
{
return new ValidationFailure ( "APIKey" , "API Key Incorrect" ) ;
}
if ( ex . Message . ContainsIgnoreCase ( "API Key Required" ) )
{
return new ValidationFailure ( "APIKey" , "API Key Required" ) ;
}
throw ;
}
return null ;
}
2014-09-11 23:49:41 +00:00
2014-08-02 22:51:20 +00:00
private ValidationFailure TestGlobalConfig ( )
{
var config = _proxy . GetConfig ( Settings ) ;
if ( config . Misc . pre_check )
{
return new NzbDroneValidationFailure ( "" , "Disable 'Check before download' option in Sabnbzd" )
{
InfoLink = String . Format ( "http://{0}:{1}/sabnzbd/config/switches/" , Settings . Host , Settings . Port ) ,
DetailedDescription = "Using Check before download affects NzbDrone ability to track new downloads. Also Sabnzbd recommends 'Abort jobs that cannot be completed' instead since it's more effective."
} ;
}
return null ;
}
2014-07-09 22:11:57 +00:00
2014-07-04 08:09:48 +00:00
private ValidationFailure TestCategory ( )
2014-02-14 05:31:49 +00:00
{
2014-08-02 22:51:20 +00:00
var config = _proxy . GetConfig ( Settings ) ;
2014-07-04 20:27:21 +00:00
var category = GetCategories ( config ) . FirstOrDefault ( ( SabnzbdCategory v ) = > v . Name = = Settings . TvCategory ) ;
2014-07-04 08:09:48 +00:00
2014-07-04 20:27:21 +00:00
if ( category ! = null )
2014-07-04 08:09:48 +00:00
{
2014-07-04 20:27:21 +00:00
if ( category . Dir . EndsWith ( "*" ) )
{
2014-07-09 19:35:05 +00:00
return new NzbDroneValidationFailure ( "TvCategory" , "Enable Job folders" )
{
InfoLink = String . Format ( "http://{0}:{1}/sabnzbd/config/categories/" , Settings . Host , Settings . Port ) ,
DetailedDescription = "NzbDrone prefers each download to have a separate folder. With * appended to the Folder/Path Sabnzbd will not create these job folders. Go to Sabnzbd to fix it."
} ;
2014-07-04 20:27:21 +00:00
}
}
else
{
if ( ! Settings . TvCategory . IsNullOrWhiteSpace ( ) )
{
2014-07-09 19:35:05 +00:00
return new NzbDroneValidationFailure ( "TvCategory" , "Category does not exist" )
{
InfoLink = String . Format ( "http://{0}:{1}/sabnzbd/config/categories/" , Settings . Host , Settings . Port ) ,
DetailedDescription = "The Category your entered doesn't exist in Sabnzbd. Go to Sabnzbd to create it."
} ;
2014-07-04 20:27:21 +00:00
}
}
if ( config . Misc . enable_tv_sorting )
{
if ( ! config . Misc . tv_categories . Any < string > ( ) | |
config . Misc . tv_categories . Contains ( Settings . TvCategory ) | |
( Settings . TvCategory . IsNullOrWhiteSpace ( ) & & config . Misc . tv_categories . Contains ( "Default" ) ) )
{
2014-07-09 19:35:05 +00:00
return new NzbDroneValidationFailure ( "TvCategory" , "Disable TV Sorting" )
{
InfoLink = String . Format ( "http://{0}:{1}/sabnzbd/config/sorting/" , Settings . Host , Settings . Port ) ,
DetailedDescription = "You must disable Sabnzbd TV Sorting for the category NzbDrone uses to prevent import issues. Go to Sabnzbd to fix it."
} ;
2014-07-04 20:27:21 +00:00
}
2014-07-04 08:09:48 +00:00
}
2014-02-14 05:31:49 +00:00
2014-07-04 08:09:48 +00:00
return null ;
2014-02-14 05:31:49 +00:00
}
2014-08-12 14:14:24 +00:00
2014-09-11 20:24:00 +00:00
private void MigrateLocalCategoryPath ( )
2014-08-12 14:14:24 +00:00
{
2014-09-11 20:24:00 +00:00
// TODO: Remove around January 2015, this code moves the settings to the RemotePathMappingService.
if ( ! Settings . TvCategoryLocalPath . IsNullOrWhiteSpace ( ) )
2014-08-12 14:14:24 +00:00
{
2014-09-11 20:24:00 +00:00
try
{
_logger . Debug ( "Has legacy TvCategoryLocalPath, trying to migrate to RemotePathMapping list." ) ;
2014-08-12 14:14:24 +00:00
2014-09-11 20:24:00 +00:00
var config = _proxy . GetConfig ( Settings ) ;
var category = GetCategories ( config ) . FirstOrDefault ( v = > v . Name = = Settings . TvCategory ) ;
if ( category ! = null )
{
var localPath = Settings . TvCategoryLocalPath ;
Settings . TvCategoryLocalPath = null ;
_remotePathMappingService . MigrateLocalCategoryPath ( Definition . Id , Settings , Settings . Host , category . FullPath , localPath ) ;
_logger . Info ( "Discovered Local Category Path for {0}, the setting was automatically moved to the Remote Path Mapping table." , Definition . Name ) ;
}
}
catch ( DownloadClientException ex )
{
_logger . ErrorException ( "Unable to migrate local category path" , ex ) ;
throw ;
}
}
2014-08-12 14:14:24 +00:00
}
2014-02-14 05:31:49 +00:00
}
}