using System; using System.Collections.Generic; using System.Linq; using FluentValidation.Results; using NLog; using NzbDrone.Common.Disk; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download.Clients.Flood.Models; using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Download.Clients.Flood { public class Flood : TorrentClientBase { private readonly IFloodProxy _proxy; private readonly IDownloadSeedConfigProvider _downloadSeedConfigProvider; public Flood(IFloodProxy proxy, IDownloadSeedConfigProvider downloadSeedConfigProvider, ITorrentFileInfoReader torrentFileInfoReader, IHttpClient httpClient, IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, Logger logger) : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) { _proxy = proxy; _downloadSeedConfigProvider = downloadSeedConfigProvider; } private static IEnumerable HandleTags(RemoteAlbum remoteAlbum, FloodSettings settings) { var result = new HashSet(); if (settings.Tags.Any()) { result.UnionWith(settings.Tags); } if (settings.AdditionalTags.Any()) { foreach (var additionalTag in settings.AdditionalTags) { switch (additionalTag) { case (int)AdditionalTags.Artist: result.Add(remoteAlbum.Artist.Name); break; case (int)AdditionalTags.Quality: result.Add(remoteAlbum.ParsedAlbumInfo.Quality.Quality.ToString()); break; case (int)AdditionalTags.ReleaseGroup: result.Add(remoteAlbum.ParsedAlbumInfo.ReleaseGroup); break; case (int)AdditionalTags.Year: result.Add(remoteAlbum.ParsedAlbumInfo.ArtistTitleInfo.Year.ToString()); break; case (int)AdditionalTags.Indexer: result.Add(remoteAlbum.Release.Indexer); break; default: throw new DownloadClientException("Unexpected additional tag ID"); } } } return result; } public override string Name => "Flood"; public override ProviderMessage Message => new ProviderMessage("Lidarr will handle automatic removal of torrents based on the current seed criteria in Settings -> Indexers", ProviderMessageType.Info); protected override string AddFromTorrentFile(RemoteAlbum remoteAlbum, string hash, string filename, byte[] fileContent) { _proxy.AddTorrentByFile(Convert.ToBase64String(fileContent), HandleTags(remoteAlbum, Settings), Settings); return hash; } protected override string AddFromMagnetLink(RemoteAlbum remoteAlbum, string hash, string magnetLink) { _proxy.AddTorrentByUrl(magnetLink, HandleTags(remoteAlbum, Settings), Settings); return hash; } public override IEnumerable GetItems() { var items = new List(); var list = _proxy.GetTorrents(Settings); foreach (var torrent in list) { var properties = torrent.Value; if (!Settings.Tags.All(tag => properties.Tags.Contains(tag))) { continue; } var item = new DownloadClientItem { DownloadClientInfo = DownloadClientItemClientInfo.FromDownloadClient(this), DownloadId = torrent.Key, Title = properties.Name, OutputPath = _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(properties.Directory)), Category = properties.Tags.Count > 0 ? properties.Tags[0] : null, RemainingSize = properties.SizeBytes - properties.BytesDone, TotalSize = properties.SizeBytes, SeedRatio = properties.Ratio, Message = properties.Message, CanMoveFiles = false, CanBeRemoved = false, }; if (properties.Eta > 0) { item.RemainingTime = TimeSpan.FromSeconds(properties.Eta); } if (properties.Status.Contains("seeding") || properties.Status.Contains("complete")) { item.Status = DownloadItemStatus.Completed; } else if (properties.Status.Contains("stopped")) { item.Status = DownloadItemStatus.Paused; } else if (properties.Status.Contains("error")) { item.Status = DownloadItemStatus.Warning; } else if (properties.Status.Contains("downloading")) { item.Status = DownloadItemStatus.Downloading; } if (item.Status == DownloadItemStatus.Completed) { // Grab cached seedConfig var seedConfig = _downloadSeedConfigProvider.GetSeedConfiguration(item.DownloadId); if (seedConfig != null) { if (item.SeedRatio >= seedConfig.Ratio) { // Check if seed ratio reached item.CanMoveFiles = item.CanBeRemoved = true; } else if (properties.DateFinished != null && properties.DateFinished > 0) { // Check if seed time reached if ((DateTimeOffset.Now - DateTimeOffset.FromUnixTimeSeconds((long)properties.DateFinished)) >= seedConfig.SeedTime) { item.CanMoveFiles = item.CanBeRemoved = true; } } } } items.Add(item); } return items; } public override DownloadClientItem GetImportItem(DownloadClientItem item, DownloadClientItem previousImportAttempt) { var result = item.Clone(); var contentPaths = _proxy.GetTorrentContentPaths(item.DownloadId, Settings); if (contentPaths.Count < 1) { throw new DownloadClientUnavailableException($"Failed to fetch list of contents of torrent: {item.DownloadId}"); } if (contentPaths.Count == 1) { // For single-file torrent, OutputPath should be the path of file. result.OutputPath = item.OutputPath + new OsPath(contentPaths[0]); } else { // For multi-file torrent, OutputPath should be the path of base directory of torrent. var baseDirectoryPaths = contentPaths.ConvertAll(path => path.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries)[0]); // Check first segment (directory) of paths of contents. If all contents share the same directory, use that directory. if (baseDirectoryPaths.TrueForAll(path => path == baseDirectoryPaths[0])) { result.OutputPath = item.OutputPath + new OsPath(baseDirectoryPaths[0]); } // Otherwise, OutputPath is already the base directory. } return result; } public override void MarkItemAsImported(DownloadClientItem downloadClientItem) { if (Settings.PostImportTags.Any()) { var list = _proxy.GetTorrents(Settings); if (list.ContainsKey(downloadClientItem.DownloadId)) { _proxy.SetTorrentsTags(downloadClientItem.DownloadId, new HashSet(list[downloadClientItem.DownloadId].Tags.Concat(Settings.PostImportTags)), Settings); } } } public override void RemoveItem(DownloadClientItem item, bool deleteData) { _proxy.DeleteTorrent(item.DownloadId, deleteData, Settings); } public override DownloadClientInfo GetStatus() { var destDir = _proxy.GetClientSettings(Settings).DirectoryDefault; if (Settings.Destination.IsNotNullOrWhiteSpace()) { destDir = Settings.Destination; } return new DownloadClientInfo { IsLocalhost = Settings.Host == "127.0.0.1" || Settings.Host == "::1" || Settings.Host == "localhost", OutputRootFolders = new List { _remotePathMappingService.RemapRemoteToLocal(Settings.Host, new OsPath(destDir)) } }; } protected override void Test(List failures) { try { _proxy.AuthVerify(Settings); } catch (DownloadClientAuthenticationException ex) { failures.Add(new ValidationFailure("Password", ex.Message)); } catch (Exception ex) { failures.Add(new ValidationFailure("Host", ex.Message)); } } } }