mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-04-24 13:57:11 -04:00
Move seed configuration logic to TorrentClientBase
This commit is contained in:
parent
d05128ca33
commit
596d3297da
15 changed files with 57 additions and 63 deletions
|
@ -5,8 +5,8 @@ using FluentValidation.Results;
|
|||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
|
@ -22,11 +22,11 @@ namespace NzbDrone.Core.Download.Clients.Aria2
|
|||
|
||||
public Aria2(IAria2Proxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ using FluentValidation.Results;
|
|||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Blackhole
|
||||
|
@ -17,11 +17,11 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
|
|||
public override bool PreferTorrentFile => true;
|
||||
|
||||
public TorrentBlackhole(ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ using FluentValidation.Results;
|
|||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
|
@ -20,11 +20,11 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
|||
|
||||
public Deluge(IDelugeProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ 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.DownloadStation.Proxies;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
@ -30,11 +30,11 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
|
|||
IDownloadStationInfoProxy dsInfoProxy,
|
||||
IDownloadStationTaskProxy dsTaskProxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_dsInfoProxy = dsInfoProxy;
|
||||
_dsTaskProxy = dsTaskProxy;
|
||||
|
|
|
@ -4,9 +4,9 @@ using System.Linq;
|
|||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download.Clients.Flood.Models;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
|
||||
|
@ -18,11 +18,11 @@ namespace NzbDrone.Core.Download.Clients.Flood
|
|||
|
||||
public Flood(IFloodProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ 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.FreeboxDownload.Responses;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
||||
|
@ -18,11 +18,11 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
|
|||
|
||||
public TorrentFreeboxDownload(IFreeboxDownloadProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ using FluentValidation.Results;
|
|||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
|
@ -17,11 +17,11 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
|
|||
|
||||
public Hadouken(IHadoukenProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ using NLog;
|
|||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
|
@ -26,12 +26,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
|||
|
||||
public QBittorrent(IQBittorrentProxySelector proxySelector,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
ICacheManager cacheManager,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxySelector = proxySelector;
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ using System.Text.RegularExpressions;
|
|||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Transmission
|
||||
{
|
||||
|
@ -12,11 +12,11 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
|||
{
|
||||
public Transmission(ITransmissionProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(proxy, torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ using FluentValidation.Results;
|
|||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
|
@ -17,11 +17,11 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
|||
|
||||
public TransmissionBase(ITransmissionProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Download.Clients.Transmission;
|
||||
using NzbDrone.Core.Indexers;
|
||||
|
||||
namespace NzbDrone.Core.Download.Clients.Vuze
|
||||
{
|
||||
|
@ -13,11 +13,11 @@ namespace NzbDrone.Core.Download.Clients.Vuze
|
|||
|
||||
public Vuze(ITransmissionProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(proxy, torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -6,10 +6,10 @@ 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.rTorrent;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
@ -23,12 +23,12 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
|||
|
||||
public RTorrent(IRTorrentProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
IRTorrentDirectoryValidator rTorrentDirectoryValidator,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
_rTorrentDirectoryValidator = rTorrentDirectoryValidator;
|
||||
|
|
|
@ -5,8 +5,8 @@ using FluentValidation.Results;
|
|||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Validation;
|
||||
|
||||
|
@ -18,11 +18,11 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
|||
|
||||
public UTorrent(IUTorrentProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, logger)
|
||||
: base(torrentFileInfoReader, seedConfigProvider, configService, diskProvider, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ namespace NzbDrone.Core.Download
|
|||
private readonly IIndexerStatusService _indexerStatusService;
|
||||
private readonly IRateLimitService _rateLimitService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ISeedConfigProvider _seedConfigProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DownloadService(IProvideDownloadClient downloadClientProvider,
|
||||
|
@ -38,7 +37,6 @@ namespace NzbDrone.Core.Download
|
|||
IIndexerStatusService indexerStatusService,
|
||||
IRateLimitService rateLimitService,
|
||||
IEventAggregator eventAggregator,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
Logger logger)
|
||||
{
|
||||
_downloadClientProvider = downloadClientProvider;
|
||||
|
@ -47,7 +45,6 @@ namespace NzbDrone.Core.Download
|
|||
_indexerStatusService = indexerStatusService;
|
||||
_rateLimitService = rateLimitService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_seedConfigProvider = seedConfigProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -61,14 +58,6 @@ namespace NzbDrone.Core.Download
|
|||
throw new DownloadClientUnavailableException($"{release.DownloadProtocol} Download client isn't configured yet");
|
||||
}
|
||||
|
||||
// Get the seed configuration for this release.
|
||||
var seedConfiguration = _seedConfigProvider.GetSeedConfiguration(release);
|
||||
|
||||
if (seedConfiguration != null)
|
||||
{
|
||||
((TorrentInfo)release).SeedConfiguration = _seedConfigProvider.GetSeedConfiguration(release);
|
||||
}
|
||||
|
||||
var indexer = _indexerFactory.GetInstance(_indexerFactory.Get(release.IndexerId));
|
||||
|
||||
var grabEvent = new IndexerDownloadEvent(release, true, source, host, release.Title, release.DownloadUrl)
|
||||
|
|
|
@ -5,7 +5,6 @@ using MonoTorrent;
|
|||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
|
@ -18,18 +17,18 @@ namespace NzbDrone.Core.Download
|
|||
public abstract class TorrentClientBase<TSettings> : DownloadClientBase<TSettings>
|
||||
where TSettings : IProviderConfig, new()
|
||||
{
|
||||
protected readonly IHttpClient _httpClient;
|
||||
protected readonly ITorrentFileInfoReader _torrentFileInfoReader;
|
||||
private readonly ITorrentFileInfoReader _torrentFileInfoReader;
|
||||
private readonly ISeedConfigProvider _seedConfigProvider;
|
||||
|
||||
protected TorrentClientBase(ITorrentFileInfoReader torrentFileInfoReader,
|
||||
IHttpClient httpClient,
|
||||
ISeedConfigProvider seedConfigProvider,
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
: base(configService, diskProvider, logger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_torrentFileInfoReader = torrentFileInfoReader;
|
||||
_seedConfigProvider = seedConfigProvider;
|
||||
}
|
||||
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
|
@ -44,6 +43,12 @@ namespace NzbDrone.Core.Download
|
|||
{
|
||||
var torrentInfo = release as TorrentInfo;
|
||||
|
||||
if (torrentInfo != null)
|
||||
{
|
||||
// Get the seed configuration for this release.
|
||||
torrentInfo.SeedConfiguration = _seedConfigProvider.GetSeedConfiguration(release);
|
||||
}
|
||||
|
||||
string magnetUrl = null;
|
||||
string torrentUrl = null;
|
||||
|
||||
|
@ -67,7 +72,7 @@ namespace NzbDrone.Core.Download
|
|||
{
|
||||
try
|
||||
{
|
||||
return await DownloadFromWebUrl(release, indexer, torrentUrl);
|
||||
return await DownloadFromWebUrl(torrentInfo, indexer, torrentUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -84,7 +89,7 @@ namespace NzbDrone.Core.Download
|
|||
{
|
||||
try
|
||||
{
|
||||
return DownloadFromMagnetUrl(release, magnetUrl);
|
||||
return DownloadFromMagnetUrl(torrentInfo, magnetUrl);
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
|
@ -98,7 +103,7 @@ namespace NzbDrone.Core.Download
|
|||
{
|
||||
try
|
||||
{
|
||||
return DownloadFromMagnetUrl(release, magnetUrl);
|
||||
return DownloadFromMagnetUrl(torrentInfo, magnetUrl);
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
|
@ -113,14 +118,14 @@ namespace NzbDrone.Core.Download
|
|||
|
||||
if (torrentUrl.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return await DownloadFromWebUrl(release, indexer, torrentUrl);
|
||||
return await DownloadFromWebUrl(torrentInfo, indexer, torrentUrl);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string> DownloadFromWebUrl(ReleaseInfo release, IIndexer indexer, string torrentUrl)
|
||||
private async Task<string> DownloadFromWebUrl(TorrentInfo release, IIndexer indexer, string torrentUrl)
|
||||
{
|
||||
byte[] torrentFile = null;
|
||||
|
||||
|
@ -142,7 +147,7 @@ namespace NzbDrone.Core.Download
|
|||
|
||||
var filename = string.Format("{0}.torrent", StringUtil.CleanFileName(release.Title));
|
||||
var hash = _torrentFileInfoReader.GetHashFromTorrentFile(torrentFile);
|
||||
var actualHash = AddFromTorrentFile((TorrentInfo)release, hash, filename, torrentFile);
|
||||
var actualHash = AddFromTorrentFile(release, hash, filename, torrentFile);
|
||||
|
||||
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
|
||||
{
|
||||
|
@ -155,7 +160,7 @@ namespace NzbDrone.Core.Download
|
|||
return actualHash;
|
||||
}
|
||||
|
||||
private string DownloadFromMagnetUrl(ReleaseInfo release, string magnetUrl)
|
||||
private string DownloadFromMagnetUrl(TorrentInfo release, string magnetUrl)
|
||||
{
|
||||
string hash = null;
|
||||
string actualHash = null;
|
||||
|
@ -173,7 +178,7 @@ namespace NzbDrone.Core.Download
|
|||
|
||||
if (hash != null)
|
||||
{
|
||||
actualHash = AddFromMagnetLink((TorrentInfo)release, hash, magnetUrl);
|
||||
actualHash = AddFromMagnetLink(release, hash, magnetUrl);
|
||||
}
|
||||
|
||||
if (actualHash.IsNotNullOrWhiteSpace() && hash != actualHash)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue