2018-03-14 20:41:36 +00:00
using System ;
2015-05-08 18:50:22 +00:00
using System.Collections.Generic ;
2019-12-22 22:08:53 +00:00
using System.Linq ;
2015-05-08 18:50:22 +00:00
using System.Threading ;
2019-12-22 22:08:53 +00:00
using FluentValidation.Results ;
using NLog ;
2015-05-08 18:50:22 +00:00
using NzbDrone.Common.Disk ;
2020-03-19 14:47:25 +00:00
using NzbDrone.Common.EnvironmentInfo ;
2015-05-08 18:50:22 +00:00
using NzbDrone.Common.Extensions ;
using NzbDrone.Common.Http ;
using NzbDrone.Core.Configuration ;
2015-07-08 08:11:33 +00:00
using NzbDrone.Core.Download.Clients.rTorrent ;
2015-10-16 06:11:48 +00:00
using NzbDrone.Core.Exceptions ;
2019-12-22 22:08:53 +00:00
using NzbDrone.Core.MediaFiles.TorrentInfo ;
using NzbDrone.Core.Organizer ;
2015-05-08 18:50:22 +00:00
using NzbDrone.Core.Parser.Model ;
using NzbDrone.Core.RemotePathMappings ;
2015-06-28 21:06:03 +00:00
using NzbDrone.Core.ThingiProvider ;
2019-12-22 22:08:53 +00:00
using NzbDrone.Core.Validation ;
2015-05-08 18:50:22 +00:00
namespace NzbDrone.Core.Download.Clients.RTorrent
{
public class RTorrent : TorrentClientBase < RTorrentSettings >
{
private readonly IRTorrentProxy _proxy ;
2015-07-08 08:11:33 +00:00
private readonly IRTorrentDirectoryValidator _rTorrentDirectoryValidator ;
2020-03-19 14:47:25 +00:00
private readonly IDownloadSeedConfigProvider _downloadSeedConfigProvider ;
private readonly string _imported_view = string . Concat ( BuildInfo . AppName . ToLower ( ) , "_imported" ) ;
2015-05-08 18:50:22 +00:00
public RTorrent ( IRTorrentProxy proxy ,
ITorrentFileInfoReader torrentFileInfoReader ,
IHttpClient httpClient ,
IConfigService configService ,
2018-04-16 22:22:54 +00:00
INamingConfigService namingConfigService ,
2015-05-08 18:50:22 +00:00
IDiskProvider diskProvider ,
IRemotePathMappingService remotePathMappingService ,
2020-03-19 14:47:25 +00:00
IDownloadSeedConfigProvider downloadSeedConfigProvider ,
2015-07-08 08:11:33 +00:00
IRTorrentDirectoryValidator rTorrentDirectoryValidator ,
2015-05-08 18:50:22 +00:00
Logger logger )
2018-04-16 22:22:54 +00:00
: base ( torrentFileInfoReader , httpClient , configService , namingConfigService , diskProvider , remotePathMappingService , logger )
2015-05-08 18:50:22 +00:00
{
_proxy = proxy ;
2015-07-08 08:11:33 +00:00
_rTorrentDirectoryValidator = rTorrentDirectoryValidator ;
2020-03-19 14:47:25 +00:00
_downloadSeedConfigProvider = downloadSeedConfigProvider ;
2015-05-08 18:50:22 +00:00
}
2019-07-01 01:50:01 +00:00
public override void MarkItemAsImported ( DownloadClientItem downloadClientItem )
{
2020-03-19 14:47:25 +00:00
// Set post-import label
if ( Settings . MovieImportedCategory . IsNotNullOrWhiteSpace ( ) & & Settings . MovieImportedCategory ! = Settings . MovieCategory )
2019-07-01 01:50:01 +00:00
{
try
{
_proxy . SetTorrentLabel ( downloadClientItem . DownloadId . ToLower ( ) , Settings . MovieImportedCategory , Settings ) ;
}
catch ( Exception ex )
{
2020-03-19 14:47:25 +00:00
_logger . Warn ( ex , "Failed to set torrent post-import label \"{0}\" for {1} in rTorrent. Does the label exist?" , Settings . MovieImportedCategory , downloadClientItem . Title ) ;
2019-07-01 01:50:01 +00:00
}
}
2020-03-19 14:47:25 +00:00
// Set post-import view
try
{
_proxy . PushTorrentUniqueView ( downloadClientItem . DownloadId . ToLower ( ) , _imported_view , Settings ) ;
}
catch ( Exception ex )
{
_logger . Warn ( ex , "Failed to set torrent post-import view \"{0}\" for {1} in rTorrent." , _imported_view , downloadClientItem . Title ) ;
}
2019-07-01 01:50:01 +00:00
}
2017-01-13 16:21:34 +00:00
protected override string AddFromMagnetLink ( RemoteMovie remoteMovie , string hash , string magnetLink )
2015-05-08 18:50:22 +00:00
{
2022-03-20 15:55:47 +00:00
var priority = ( RTorrentPriority ) ( remoteMovie . Movie . MovieMetadata . Value . IsRecentMovie ? Settings . RecentMoviePriority : Settings . OlderMoviePriority ) ;
2018-04-15 03:03:32 +00:00
2019-06-14 03:54:25 +00:00
_proxy . AddTorrentFromUrl ( magnetLink , Settings . MovieCategory , priority , Settings . MovieDirectory , Settings ) ;
2015-05-08 18:50:22 +00:00
2015-10-02 12:34:38 +00:00
var tries = 10 ;
var retryDelay = 500 ;
2017-03-14 18:18:36 +00:00
// Wait a bit for the magnet to be resolved.
if ( ! WaitForTorrent ( hash , tries , retryDelay ) )
2015-05-08 18:50:22 +00:00
{
2016-01-17 13:32:52 +00:00
_logger . Warn ( "rTorrent could not resolve magnet within {0} seconds, download may remain stuck: {1}." , tries * retryDelay / 1000 , magnetLink ) ;
2015-10-02 12:34:38 +00:00
2016-01-17 13:32:52 +00:00
return hash ;
2015-05-08 18:50:22 +00:00
}
2017-03-14 18:18:36 +00:00
return hash ;
2015-05-08 18:50:22 +00:00
}
2017-01-07 02:54:24 +00:00
protected override string AddFromTorrentFile ( RemoteMovie remoteMovie , string hash , string filename , byte [ ] fileContent )
{
2022-03-20 15:55:47 +00:00
var priority = ( RTorrentPriority ) ( remoteMovie . Movie . MovieMetadata . Value . IsRecentMovie ? Settings . RecentMoviePriority : Settings . OlderMoviePriority ) ;
2018-04-15 03:03:32 +00:00
2019-06-14 03:54:25 +00:00
_proxy . AddTorrentFromFile ( filename , fileContent , Settings . MovieCategory , priority , Settings . MovieDirectory , Settings ) ;
2017-01-07 02:54:24 +00:00
2017-03-14 18:18:36 +00:00
var tries = 10 ;
var retryDelay = 500 ;
if ( ! WaitForTorrent ( hash , tries , retryDelay ) )
2017-01-07 02:54:24 +00:00
{
2017-03-14 18:18:36 +00:00
_logger . Debug ( "rTorrent didn't add the torrent within {0} seconds: {1}." , tries * retryDelay / 1000 , filename ) ;
2017-01-07 02:54:24 +00:00
throw new ReleaseDownloadException ( remoteMovie . Release , "Downloading torrent failed" ) ;
}
2017-03-14 18:18:36 +00:00
return hash ;
2017-01-07 02:54:24 +00:00
}
2016-12-09 06:54:15 +00:00
public override string Name = > "rTorrent" ;
2015-05-08 18:50:22 +00:00
2022-10-03 17:16:46 +00:00
public override ProviderMessage Message = > new ProviderMessage ( $"rTorrent will not pause torrents when they meet the seed criteria. Radarr will handle automatic removal of torrents based on the current seed criteria in Settings->Indexers only when Remove Completed is enabled. After importing it will also set \" { _imported_view } \ " as an rTorrent view, which can be used in rTorrent scripts to customize behavior." , ProviderMessageType . Info ) ;
2015-06-28 21:06:03 +00:00
2015-05-08 18:50:22 +00:00
public override IEnumerable < DownloadClientItem > GetItems ( )
{
2019-06-14 03:54:25 +00:00
var torrents = _proxy . GetTorrents ( Settings ) ;
_logger . Debug ( "Retrieved metadata of {0} torrents in client" , torrents . Count ) ;
var items = new List < DownloadClientItem > ( ) ;
2023-05-23 10:52:39 +00:00
foreach ( var torrent in torrents )
2015-05-08 18:50:22 +00:00
{
2019-06-14 03:54:25 +00:00
// Don't concern ourselves with categories other than specified
2019-12-22 22:08:53 +00:00
if ( Settings . MovieCategory . IsNotNullOrWhiteSpace ( ) & & torrent . Category ! = Settings . MovieCategory )
{
continue ;
}
2015-05-08 18:50:22 +00:00
2022-01-06 21:12:31 +00:00
// Ignore torrents with an empty path
if ( torrent . Path . IsNullOrWhiteSpace ( ) )
{
continue ;
}
2019-06-14 03:54:25 +00:00
if ( torrent . Path . StartsWith ( "." ) )
{
throw new DownloadClientException ( "Download paths must be absolute. Please specify variable \"directory\" in rTorrent." ) ;
}
2015-05-08 18:50:22 +00:00
2019-06-14 03:54:25 +00:00
var item = new DownloadClientItem ( ) ;
2020-03-29 20:05:48 +00:00
item . DownloadClientInfo = DownloadClientItemClientInfo . FromDownloadClient ( this ) ;
2019-06-14 03:54:25 +00:00
item . Title = torrent . Name ;
item . DownloadId = torrent . Hash ;
item . OutputPath = _remotePathMappingService . RemapRemoteToLocal ( Settings . Host , new OsPath ( torrent . Path ) ) ;
item . TotalSize = torrent . TotalSize ;
item . RemainingSize = torrent . RemainingSize ;
item . Category = torrent . Category ;
item . SeedRatio = torrent . Ratio ;
if ( torrent . DownRate > 0 )
2015-05-08 18:50:22 +00:00
{
2019-06-14 03:54:25 +00:00
var secondsLeft = torrent . RemainingSize / torrent . DownRate ;
item . RemainingTime = TimeSpan . FromSeconds ( secondsLeft ) ;
}
else
{
item . RemainingTime = TimeSpan . Zero ;
2015-05-08 18:50:22 +00:00
}
2019-06-14 03:54:25 +00:00
if ( torrent . IsFinished )
{
item . Status = DownloadItemStatus . Completed ;
}
else if ( torrent . IsActive )
{
item . Status = DownloadItemStatus . Downloading ;
}
else if ( ! torrent . IsActive )
{
item . Status = DownloadItemStatus . Paused ;
}
2020-03-19 14:47:25 +00:00
// Grab cached seedConfig
var seedConfig = _downloadSeedConfigProvider . GetSeedConfiguration ( torrent . Hash ) ;
2023-05-12 00:35:40 +00:00
if ( torrent . IsFinished & & seedConfig ! = null )
{
var canRemove = false ;
if ( torrent . Ratio / 1000.0 > = seedConfig . Ratio )
{
_logger . Trace ( $"{item} has met seed ratio goal of {seedConfig.Ratio}" ) ;
canRemove = true ;
}
else if ( DateTimeOffset . Now - DateTimeOffset . FromUnixTimeSeconds ( torrent . FinishedTime ) > = seedConfig . SeedTime )
{
_logger . Trace ( $"{item} has met seed time goal of {seedConfig.SeedTime} minutes" ) ;
canRemove = true ;
}
else
{
_logger . Trace ( $"{item} seeding goals have not yet been reached" ) ;
}
// Check if torrent is finished and if it exceeds cached seedConfig
item . CanMoveFiles = item . CanBeRemoved = canRemove ;
}
2019-06-14 03:54:25 +00:00
items . Add ( item ) ;
2015-05-08 18:50:22 +00:00
}
2019-06-14 03:54:25 +00:00
return items ;
2015-05-08 18:50:22 +00:00
}
2021-04-20 05:00:33 +00:00
public override void RemoveItem ( DownloadClientItem item , bool deleteData )
2015-05-08 18:50:22 +00:00
{
if ( deleteData )
{
2021-04-20 05:00:33 +00:00
DeleteItemData ( item ) ;
2015-05-08 18:50:22 +00:00
}
2021-04-20 05:00:33 +00:00
_proxy . RemoveTorrent ( item . DownloadId , Settings ) ;
2015-05-08 18:50:22 +00:00
}
2019-06-14 03:54:25 +00:00
public override DownloadClientInfo GetStatus ( )
2015-05-08 18:50:22 +00:00
{
// XXX: This function's correctness has not been considered
2019-06-14 03:54:25 +00:00
var status = new DownloadClientInfo
2015-05-08 18:50:22 +00:00
{
IsLocalhost = Settings . Host = = "127.0.0.1" | | Settings . Host = = "localhost"
} ;
return status ;
}
protected override void Test ( List < ValidationFailure > failures )
{
failures . AddIfNotNull ( TestConnection ( ) ) ;
2019-12-22 22:08:53 +00:00
if ( failures . HasErrors ( ) )
{
return ;
}
2015-05-08 18:50:22 +00:00
failures . AddIfNotNull ( TestGetTorrents ( ) ) ;
2015-07-08 08:11:33 +00:00
failures . AddIfNotNull ( TestDirectory ( ) ) ;
2015-05-08 18:50:22 +00:00
}
private ValidationFailure TestConnection ( )
{
try
{
var version = _proxy . GetVersion ( Settings ) ;
if ( new Version ( version ) < new Version ( "0.9.0" ) )
{
return new ValidationFailure ( string . Empty , "rTorrent version should be at least 0.9.0. Version reported is {0}" , version ) ;
}
}
catch ( Exception ex )
{
2019-06-14 03:54:25 +00:00
_logger . Error ( ex , "Failed to test rTorrent" ) ;
2020-10-25 22:59:40 +00:00
return new NzbDroneValidationFailure ( "Host" , "Unable to connect to rTorrent" )
{
DetailedDescription = ex . Message
} ;
2015-05-08 18:50:22 +00:00
}
return null ;
}
private ValidationFailure TestGetTorrents ( )
{
try
{
_proxy . GetTorrents ( Settings ) ;
}
catch ( Exception ex )
{
2019-06-14 03:54:25 +00:00
_logger . Error ( ex , "Failed to get torrents" ) ;
2015-05-08 18:50:22 +00:00
return new NzbDroneValidationFailure ( string . Empty , "Failed to get the list of torrents: " + ex . Message ) ;
}
return null ;
}
2015-07-08 08:11:33 +00:00
private ValidationFailure TestDirectory ( )
{
var result = _rTorrentDirectoryValidator . Validate ( Settings ) ;
if ( result . IsValid )
{
return null ;
}
return result . Errors . First ( ) ;
}
2015-10-02 12:34:38 +00:00
private bool WaitForTorrent ( string hash , int tries , int retryDelay )
{
for ( var i = 0 ; i < tries ; i + + )
{
if ( _proxy . HasHashTorrent ( hash , Settings ) )
{
return true ;
}
Thread . Sleep ( retryDelay ) ;
}
_logger . Debug ( "Could not find hash {0} in {1} tries at {2} ms intervals." , hash , tries , retryDelay ) ;
return false ;
}
2015-05-08 18:50:22 +00:00
}
}