mirror of
https://github.com/Radarr/Radarr.git
synced 2024-10-06 16:17:20 +02:00
New: Support stoppedUP and stoppedDL states from qBittorrent
(cherry picked from commit 73a4bdea5247ee87e6bbae95f5325e1f03c88a7f) Closes #9995
This commit is contained in:
parent
256439304b
commit
cd4552ce6f
@ -178,8 +178,9 @@ public void error_item_should_have_required_properties()
|
|||||||
VerifyWarning(item);
|
VerifyWarning(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedDL")]
|
||||||
public void paused_item_should_have_required_properties()
|
[TestCase("stoppedDL")]
|
||||||
|
public void paused_item_should_have_required_properties(string state)
|
||||||
{
|
{
|
||||||
var torrent = new QBittorrentTorrent
|
var torrent = new QBittorrentTorrent
|
||||||
{
|
{
|
||||||
@ -188,7 +189,7 @@ public void paused_item_should_have_required_properties()
|
|||||||
Size = 1000,
|
Size = 1000,
|
||||||
Progress = 0.7,
|
Progress = 0.7,
|
||||||
Eta = 8640000,
|
Eta = 8640000,
|
||||||
State = "pausedDL",
|
State = state,
|
||||||
Label = "",
|
Label = "",
|
||||||
SavePath = ""
|
SavePath = ""
|
||||||
};
|
};
|
||||||
@ -200,6 +201,7 @@ public void paused_item_should_have_required_properties()
|
|||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("pausedUP")]
|
[TestCase("pausedUP")]
|
||||||
|
[TestCase("stoppedUP")]
|
||||||
[TestCase("queuedUP")]
|
[TestCase("queuedUP")]
|
||||||
[TestCase("uploading")]
|
[TestCase("uploading")]
|
||||||
[TestCase("stalledUP")]
|
[TestCase("stalledUP")]
|
||||||
@ -417,8 +419,9 @@ public void missingFiles_item_should_have_required_properties()
|
|||||||
item.RemainingTime.Should().NotHaveValue();
|
item.RemainingTime.Should().NotHaveValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void api_261_should_use_content_path()
|
[TestCase("stoppedUP")]
|
||||||
|
public void api_261_should_use_content_path(string state)
|
||||||
{
|
{
|
||||||
var torrent = new QBittorrentTorrent
|
var torrent = new QBittorrentTorrent
|
||||||
{
|
{
|
||||||
@ -427,7 +430,7 @@ public void api_261_should_use_content_path()
|
|||||||
Size = 1000,
|
Size = 1000,
|
||||||
Progress = 0.7,
|
Progress = 0.7,
|
||||||
Eta = 8640000,
|
Eta = 8640000,
|
||||||
State = "pausedUP",
|
State = state,
|
||||||
Label = "",
|
Label = "",
|
||||||
SavePath = @"C:\Torrents".AsOsAgnostic(),
|
SavePath = @"C:\Torrents".AsOsAgnostic(),
|
||||||
ContentPath = @"C:\Torrents\Droned.S01.12".AsOsAgnostic()
|
ContentPath = @"C:\Torrents\Droned.S01.12".AsOsAgnostic()
|
||||||
@ -656,44 +659,48 @@ public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio
|
|||||||
item.CanMoveFiles.Should().BeFalse();
|
item.CanMoveFiles.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(-1);
|
GivenGlobalSeedLimits(-1);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f);
|
GivenCompletedTorrent(state, ratio: 1.0f);
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeFalse();
|
item.CanBeRemoved.Should().BeFalse();
|
||||||
item.CanMoveFiles.Should().BeFalse();
|
item.CanMoveFiles.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(1.0f);
|
GivenGlobalSeedLimits(1.0f);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f);
|
GivenCompletedTorrent(state, ratio: 1.0f);
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeTrue();
|
item.CanBeRemoved.Should().BeTrue();
|
||||||
item.CanMoveFiles.Should().BeTrue();
|
item.CanMoveFiles.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(2.0f);
|
GivenGlobalSeedLimits(2.0f);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f, ratioLimit: 0.8f);
|
GivenCompletedTorrent(state, ratio: 1.0f, ratioLimit: 0.8f);
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeTrue();
|
item.CanBeRemoved.Should().BeTrue();
|
||||||
item.CanMoveFiles.Should().BeTrue();
|
item.CanMoveFiles.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(0.2f);
|
GivenGlobalSeedLimits(0.2f);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 0.5f, ratioLimit: 0.8f);
|
GivenCompletedTorrent(state, ratio: 0.5f, ratioLimit: 0.8f);
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeFalse();
|
item.CanBeRemoved.Should().BeFalse();
|
||||||
@ -711,33 +718,36 @@ public void should_not_be_removable_and_should_not_allow_move_files_if_max_seedi
|
|||||||
item.CanMoveFiles.Should().BeFalse();
|
item.CanMoveFiles.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_and_paused()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_and_paused(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(-1, 20);
|
GivenGlobalSeedLimits(-1, 20);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20);
|
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20);
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeTrue();
|
item.CanBeRemoved.Should().BeTrue();
|
||||||
item.CanMoveFiles.Should().BeTrue();
|
item.CanMoveFiles.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_seedingtime_reached_and_paused()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_be_removable_and_should_allow_move_files_if_overridden_max_seedingtime_reached_and_paused(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(-1, 40);
|
GivenGlobalSeedLimits(-1, 40);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20, seedingTimeLimit: 10);
|
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20, seedingTimeLimit: 10);
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeTrue();
|
item.CanBeRemoved.Should().BeTrue();
|
||||||
item.CanMoveFiles.Should().BeTrue();
|
item.CanMoveFiles.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_not_be_removable_if_overridden_max_seedingtime_not_reached_and_paused()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_not_be_removable_if_overridden_max_seedingtime_not_reached_and_paused(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(-1, 20);
|
GivenGlobalSeedLimits(-1, 20);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 30, seedingTimeLimit: 40);
|
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 30, seedingTimeLimit: 40);
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeFalse();
|
item.CanBeRemoved.Should().BeFalse();
|
||||||
@ -755,66 +765,72 @@ public void should_not_be_removable_and_should_not_allow_move_files_if_max_inact
|
|||||||
item.CanMoveFiles.Should().BeFalse();
|
item.CanMoveFiles.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_and_paused()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_and_paused(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
|
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
GivenCompletedTorrent(state, ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeTrue();
|
item.CanBeRemoved.Should().BeTrue();
|
||||||
item.CanMoveFiles.Should().BeTrue();
|
item.CanMoveFiles.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_be_removable_and_should_allow_move_files_if_overridden_max_inactive_seedingtime_reached_and_paused()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_be_removable_and_should_allow_move_files_if_overridden_max_inactive_seedingtime_reached_and_paused(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 40);
|
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 40);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20, inactiveSeedingTimeLimit: 10, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(15)).ToUnixTimeSeconds());
|
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20, inactiveSeedingTimeLimit: 10, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(15)).ToUnixTimeSeconds());
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeTrue();
|
item.CanBeRemoved.Should().BeTrue();
|
||||||
item.CanMoveFiles.Should().BeTrue();
|
item.CanMoveFiles.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
|
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 30, inactiveSeedingTimeLimit: 40, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(30)).ToUnixTimeSeconds());
|
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 30, inactiveSeedingTimeLimit: 40, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(30)).ToUnixTimeSeconds());
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeFalse();
|
item.CanBeRemoved.Should().BeFalse();
|
||||||
item.CanMoveFiles.Should().BeFalse();
|
item.CanMoveFiles.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(2.0f, 20);
|
GivenGlobalSeedLimits(2.0f, 20);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f, seedingTime: 30);
|
GivenCompletedTorrent(state, ratio: 1.0f, seedingTime: 30);
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeTrue();
|
item.CanBeRemoved.Should().BeTrue();
|
||||||
item.CanMoveFiles.Should().BeTrue();
|
item.CanMoveFiles.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(2.0f, maxInactiveSeedingTime: 20);
|
GivenGlobalSeedLimits(2.0f, maxInactiveSeedingTime: 20);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 1.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
GivenCompletedTorrent(state, ratio: 1.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeTrue();
|
item.CanBeRemoved.Should().BeTrue();
|
||||||
item.CanMoveFiles.Should().BeTrue();
|
item.CanMoveFiles.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_not_fetch_details_twice()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_not_fetch_details_twice(string state)
|
||||||
{
|
{
|
||||||
GivenGlobalSeedLimits(-1, 30);
|
GivenGlobalSeedLimits(-1, 30);
|
||||||
GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20);
|
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20);
|
||||||
|
|
||||||
var item = Subject.GetItems().Single();
|
var item = Subject.GetItems().Single();
|
||||||
item.CanBeRemoved.Should().BeFalse();
|
item.CanBeRemoved.Should().BeFalse();
|
||||||
@ -826,8 +842,9 @@ public void should_not_fetch_details_twice()
|
|||||||
.Verify(p => p.GetTorrentProperties(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once());
|
.Verify(p => p.GetTorrentProperties(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_get_category_from_the_category_if_set()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_get_category_from_the_category_if_set(string state)
|
||||||
{
|
{
|
||||||
const string category = "movies-radarr";
|
const string category = "movies-radarr";
|
||||||
GivenGlobalSeedLimits(1.0f);
|
GivenGlobalSeedLimits(1.0f);
|
||||||
@ -839,7 +856,7 @@ public void should_get_category_from_the_category_if_set()
|
|||||||
Size = 1000,
|
Size = 1000,
|
||||||
Progress = 1.0,
|
Progress = 1.0,
|
||||||
Eta = 8640000,
|
Eta = 8640000,
|
||||||
State = "pausedUP",
|
State = state,
|
||||||
Category = category,
|
Category = category,
|
||||||
SavePath = "",
|
SavePath = "",
|
||||||
Ratio = 1.0f
|
Ratio = 1.0f
|
||||||
@ -851,8 +868,9 @@ public void should_get_category_from_the_category_if_set()
|
|||||||
item.Category.Should().Be(category);
|
item.Category.Should().Be(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("pausedUP")]
|
||||||
public void should_get_category_from_the_label_if_the_category_is_not_available()
|
[TestCase("stoppedUP")]
|
||||||
|
public void should_get_category_from_the_label_if_the_category_is_not_available(string state)
|
||||||
{
|
{
|
||||||
const string category = "movies-radarr";
|
const string category = "movies-radarr";
|
||||||
GivenGlobalSeedLimits(1.0f);
|
GivenGlobalSeedLimits(1.0f);
|
||||||
@ -864,7 +882,7 @@ public void should_get_category_from_the_label_if_the_category_is_not_available(
|
|||||||
Size = 1000,
|
Size = 1000,
|
||||||
Progress = 1.0,
|
Progress = 1.0,
|
||||||
Eta = 8640000,
|
Eta = 8640000,
|
||||||
State = "pausedUP",
|
State = state,
|
||||||
Label = category,
|
Label = category,
|
||||||
SavePath = "",
|
SavePath = "",
|
||||||
Ratio = 1.0f
|
Ratio = 1.0f
|
||||||
|
@ -239,7 +239,7 @@ public override IEnumerable<DownloadClientItem> GetItems()
|
|||||||
|
|
||||||
// Avoid removing torrents that haven't reached the global max ratio.
|
// Avoid removing torrents that haven't reached the global max ratio.
|
||||||
// Removal also requires the torrent to be paused, in case a higher max ratio was set on the torrent itself (which is not exposed by the api).
|
// Removal also requires the torrent to be paused, in case a higher max ratio was set on the torrent itself (which is not exposed by the api).
|
||||||
item.CanMoveFiles = item.CanBeRemoved = torrent.State == "pausedUP" && HasReachedSeedLimit(torrent, config);
|
item.CanMoveFiles = item.CanBeRemoved = torrent.State is "pausedUP" or "stoppedUP" && HasReachedSeedLimit(torrent, config);
|
||||||
|
|
||||||
switch (torrent.State)
|
switch (torrent.State)
|
||||||
{
|
{
|
||||||
@ -248,7 +248,8 @@ public override IEnumerable<DownloadClientItem> GetItems()
|
|||||||
item.Message = _localizationService.GetLocalizedString("DownloadClientQbittorrentTorrentStateError");
|
item.Message = _localizationService.GetLocalizedString("DownloadClientQbittorrentTorrentStateError");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "pausedDL": // torrent is paused and has NOT finished downloading
|
case "stoppedDL": // torrent is stopped and has NOT finished downloading
|
||||||
|
case "pausedDL": // torrent is paused and has NOT finished downloading (qBittorrent < 5)
|
||||||
item.Status = DownloadItemStatus.Paused;
|
item.Status = DownloadItemStatus.Paused;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -259,7 +260,8 @@ public override IEnumerable<DownloadClientItem> GetItems()
|
|||||||
item.Status = DownloadItemStatus.Queued;
|
item.Status = DownloadItemStatus.Queued;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "pausedUP": // torrent is paused and has finished downloading:
|
case "pausedUP": // torrent is paused and has finished downloading (qBittorent < 5)
|
||||||
|
case "stoppedUP": // torrent is stopped and has finished downloading
|
||||||
case "uploading": // torrent is being seeded and data is being transferred
|
case "uploading": // torrent is being seeded and data is being transferred
|
||||||
case "stalledUP": // torrent is being seeded, but no connection were made
|
case "stalledUP": // torrent is being seeded, but no connection were made
|
||||||
case "queuedUP": // queuing is enabled and torrent is queued for upload
|
case "queuedUP": // queuing is enabled and torrent is queued for upload
|
||||||
|
Loading…
Reference in New Issue
Block a user