Compare commits
4 Commits
develop
...
phantom-fe
Author | SHA1 | Date |
---|---|---|
Taloth Saldono | d93923f4b4 | |
Taloth Saldono | 5f197800fc | |
Taloth Saldono | 1400b96816 | |
leaty | 4e77a9df10 |
|
@ -16,4 +16,9 @@
|
||||||
color: #3a3f51;
|
color: #3a3f51;
|
||||||
font-size: 21px;
|
font-size: 21px;
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
|
|
||||||
|
&.small {
|
||||||
|
color: #909293;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
import styles from './FieldSet.css';
|
import styles from './FieldSet.css';
|
||||||
|
|
||||||
class FieldSet extends Component {
|
class FieldSet extends Component {
|
||||||
|
@ -9,13 +11,14 @@ class FieldSet extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
size,
|
||||||
legend,
|
legend,
|
||||||
children
|
children
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<fieldset className={styles.fieldSet}>
|
<fieldset className={styles.fieldSet}>
|
||||||
<legend className={styles.legend}>
|
<legend className={classNames(styles.legend, (size === sizes.SMALL) && styles.small)}>
|
||||||
{legend}
|
{legend}
|
||||||
</legend>
|
</legend>
|
||||||
{children}
|
{children}
|
||||||
|
@ -26,8 +29,13 @@ class FieldSet extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
FieldSet.propTypes = {
|
FieldSet.propTypes = {
|
||||||
|
size: PropTypes.oneOf(sizes.all).isRequired,
|
||||||
legend: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
|
legend: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
|
||||||
children: PropTypes.node
|
children: PropTypes.node
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FieldSet.defaultProps = {
|
||||||
|
size: sizes.MEDIUM
|
||||||
|
};
|
||||||
|
|
||||||
export default FieldSet;
|
export default FieldSet;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { inputTypes, kinds } from 'Helpers/Props';
|
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||||
import Alert from 'Components/Alert';
|
import Alert from 'Components/Alert';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||||
|
@ -9,6 +9,7 @@ import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import FieldSet from 'Components/FieldSet';
|
||||||
import Form from 'Components/Form/Form';
|
import Form from 'Components/Form/Form';
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
@ -44,7 +45,10 @@ class EditDownloadClientModalContent extends Component {
|
||||||
implementationName,
|
implementationName,
|
||||||
name,
|
name,
|
||||||
enable,
|
enable,
|
||||||
|
protocol,
|
||||||
priority,
|
priority,
|
||||||
|
removeCompletedDownloads,
|
||||||
|
removeFailedDownloads,
|
||||||
fields,
|
fields,
|
||||||
message
|
message
|
||||||
} = item;
|
} = item;
|
||||||
|
@ -133,6 +137,37 @@ class EditDownloadClientModalContent extends Component {
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
<FieldSet
|
||||||
|
size={sizes.SMALL}
|
||||||
|
legend="Completed Download Handling"
|
||||||
|
>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Remove Completed</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="removeCompletedDownloads"
|
||||||
|
helpText="Remove imported downloads from download client history (when finished seeding for torrents)"
|
||||||
|
{...removeCompletedDownloads}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
{
|
||||||
|
protocol.value !== 'torrent' &&
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>Remove Failed</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.CHECK}
|
||||||
|
name="removeFailedDownloads"
|
||||||
|
helpText="Remove failed downloads from download client history"
|
||||||
|
{...removeFailedDownloads}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
}
|
||||||
|
</FieldSet>
|
||||||
</Form>
|
</Form>
|
||||||
}
|
}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { inputTypes, sizes } from 'Helpers/Props';
|
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
import FieldSet from 'Components/FieldSet';
|
import FieldSet from 'Components/FieldSet';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import Form from 'Components/Form/Form';
|
import Form from 'Components/Form/Form';
|
||||||
|
@ -31,11 +32,16 @@ function DownloadClientOptions(props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
hasSettings && !isFetching && !error &&
|
hasSettings && !isFetching && !error && advancedSettings &&
|
||||||
<div>
|
<div>
|
||||||
<FieldSet legend="Completed Download Handling">
|
<FieldSet legend="Completed Download Handling">
|
||||||
|
|
||||||
<Form>
|
<Form>
|
||||||
<FormGroup size={sizes.MEDIUM}>
|
<FormGroup
|
||||||
|
advancedSettings={advancedSettings}
|
||||||
|
isAdvanced={true}
|
||||||
|
size={sizes.MEDIUM}
|
||||||
|
>
|
||||||
<FormLabel>Enable</FormLabel>
|
<FormLabel>Enable</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
|
@ -52,25 +58,7 @@ function DownloadClientOptions(props) {
|
||||||
isAdvanced={true}
|
isAdvanced={true}
|
||||||
size={sizes.MEDIUM}
|
size={sizes.MEDIUM}
|
||||||
>
|
>
|
||||||
<FormLabel>Remove</FormLabel>
|
<FormLabel>Redownload Failed</FormLabel>
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="removeCompletedDownloads"
|
|
||||||
helpText="Remove imported downloads from download client history"
|
|
||||||
onChange={onInputChange}
|
|
||||||
{...settings.removeCompletedDownloads}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</Form>
|
|
||||||
</FieldSet>
|
|
||||||
|
|
||||||
<FieldSet
|
|
||||||
legend="Failed Download Handling"
|
|
||||||
>
|
|
||||||
<Form>
|
|
||||||
<FormGroup size={sizes.MEDIUM}>
|
|
||||||
<FormLabel>Redownload</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
|
@ -80,23 +68,11 @@ function DownloadClientOptions(props) {
|
||||||
{...settings.autoRedownloadFailed}
|
{...settings.autoRedownloadFailed}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
<FormGroup
|
|
||||||
advancedSettings={advancedSettings}
|
|
||||||
isAdvanced={true}
|
|
||||||
size={sizes.MEDIUM}
|
|
||||||
>
|
|
||||||
<FormLabel>Remove</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="removeFailedDownloads"
|
|
||||||
helpText="Remove failed downloads from download client history"
|
|
||||||
onChange={onInputChange}
|
|
||||||
{...settings.removeFailedDownloads}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
<Alert kind={kinds.INFO}>
|
||||||
|
The Remove settings were moved to the individual Download Client settings in the table above.
|
||||||
|
</Alert>
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,7 @@ namespace NzbDrone.Api.Config
|
||||||
public string DownloadClientWorkingFolders { get; set; }
|
public string DownloadClientWorkingFolders { get; set; }
|
||||||
|
|
||||||
public bool EnableCompletedDownloadHandling { get; set; }
|
public bool EnableCompletedDownloadHandling { get; set; }
|
||||||
public bool RemoveCompletedDownloads { get; set; }
|
|
||||||
|
|
||||||
public bool AutoRedownloadFailed { get; set; }
|
public bool AutoRedownloadFailed { get; set; }
|
||||||
public bool RemoveFailedDownloads { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DownloadClientConfigResourceMapper
|
public static class DownloadClientConfigResourceMapper
|
||||||
|
@ -23,10 +20,7 @@ namespace NzbDrone.Api.Config
|
||||||
DownloadClientWorkingFolders = model.DownloadClientWorkingFolders,
|
DownloadClientWorkingFolders = model.DownloadClientWorkingFolders,
|
||||||
|
|
||||||
EnableCompletedDownloadHandling = model.EnableCompletedDownloadHandling,
|
EnableCompletedDownloadHandling = model.EnableCompletedDownloadHandling,
|
||||||
RemoveCompletedDownloads = model.RemoveCompletedDownloads,
|
AutoRedownloadFailed = model.AutoRedownloadFailed
|
||||||
|
|
||||||
AutoRedownloadFailed = model.AutoRedownloadFailed,
|
|
||||||
RemoveFailedDownloads = model.RemoveFailedDownloads
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ namespace NzbDrone.Api.DownloadClient
|
||||||
resource.Enable = definition.Enable;
|
resource.Enable = definition.Enable;
|
||||||
resource.Protocol = definition.Protocol;
|
resource.Protocol = definition.Protocol;
|
||||||
resource.Priority = definition.Priority;
|
resource.Priority = definition.Priority;
|
||||||
|
resource.RemoveCompletedDownloads = definition.RemoveCompletedDownloads;
|
||||||
|
resource.RemoveFailedDownloads = definition.RemoveFailedDownloads;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void MapToModel(DownloadClientDefinition definition, DownloadClientResource resource)
|
protected override void MapToModel(DownloadClientDefinition definition, DownloadClientResource resource)
|
||||||
|
@ -25,6 +27,8 @@ namespace NzbDrone.Api.DownloadClient
|
||||||
definition.Enable = resource.Enable;
|
definition.Enable = resource.Enable;
|
||||||
definition.Protocol = resource.Protocol;
|
definition.Protocol = resource.Protocol;
|
||||||
definition.Priority = resource.Priority;
|
definition.Priority = resource.Priority;
|
||||||
|
definition.RemoveCompletedDownloads = resource.RemoveCompletedDownloads;
|
||||||
|
definition.RemoveFailedDownloads = resource.RemoveFailedDownloads;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Validate(DownloadClientDefinition definition, bool includeWarnings)
|
protected override void Validate(DownloadClientDefinition definition, bool includeWarnings)
|
||||||
|
|
|
@ -7,5 +7,7 @@ namespace NzbDrone.Api.DownloadClient
|
||||||
public bool Enable { get; set; }
|
public bool Enable { get; set; }
|
||||||
public DownloadProtocol Protocol { get; set; }
|
public DownloadProtocol Protocol { get; set; }
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
|
public bool RemoveCompletedDownloads { get; set; }
|
||||||
|
public bool RemoveFailedDownloads { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Datastore.Migration;
|
||||||
|
using NzbDrone.Core.Download.Clients.RTorrent;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.Datastore.Migration
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class cdh_per_downloadclientFixture : MigrationTest<cdh_per_downloadclient>
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void should_set_cdh_to_enabled()
|
||||||
|
{
|
||||||
|
var db = WithMigrationTestDb(c =>
|
||||||
|
{
|
||||||
|
c.Insert.IntoTable("DownloadClients").Row(new
|
||||||
|
{
|
||||||
|
Enable = 1,
|
||||||
|
Name = "Deluge",
|
||||||
|
Implementation = "Deluge",
|
||||||
|
Priority = 1,
|
||||||
|
Settings = new DelugeSettings85
|
||||||
|
{
|
||||||
|
Host = "127.0.0.1",
|
||||||
|
TvCategory = "abc",
|
||||||
|
UrlBase = "/my/"
|
||||||
|
}.ToJson(),
|
||||||
|
ConfigContract = "DelugeSettings"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var items = db.Query<DownloadClientDefinition158>("SELECT * FROM DownloadClients");
|
||||||
|
|
||||||
|
items.Should().HaveCount(1);
|
||||||
|
items.First().RemoveCompletedDownloads.Should().BeTrue();
|
||||||
|
items.First().RemoveFailedDownloads.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_set_cdh_to_disabled_when_globally_disabled()
|
||||||
|
{
|
||||||
|
var db = WithMigrationTestDb(c =>
|
||||||
|
{
|
||||||
|
c.Insert.IntoTable("Config").Row(new
|
||||||
|
{
|
||||||
|
Key = "removecompleteddownloads",
|
||||||
|
Value = "False"
|
||||||
|
});
|
||||||
|
|
||||||
|
c.Insert.IntoTable("DownloadClients").Row(new
|
||||||
|
{
|
||||||
|
Enable = 1,
|
||||||
|
Name = "Deluge",
|
||||||
|
Implementation = "Deluge",
|
||||||
|
Priority = 1,
|
||||||
|
Settings = new DelugeSettings85
|
||||||
|
{
|
||||||
|
Host = "127.0.0.1",
|
||||||
|
TvCategory = "abc",
|
||||||
|
UrlBase = "/my/"
|
||||||
|
}.ToJson(),
|
||||||
|
ConfigContract = "DelugeSettings"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var items = db.Query<DownloadClientDefinition158>("SELECT * FROM DownloadClients");
|
||||||
|
|
||||||
|
items.Should().HaveCount(1);
|
||||||
|
items.First().RemoveCompletedDownloads.Should().BeFalse();
|
||||||
|
items.First().RemoveFailedDownloads.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_disable_remove_for_existing_rtorrent()
|
||||||
|
{
|
||||||
|
var db = WithMigrationTestDb(c =>
|
||||||
|
{
|
||||||
|
c.Insert.IntoTable("DownloadClients").Row(new
|
||||||
|
{
|
||||||
|
Enable = 1,
|
||||||
|
Name = "RTorrent",
|
||||||
|
Implementation = "RTorrent",
|
||||||
|
Priority = 1,
|
||||||
|
Settings = new RTorrentSettings
|
||||||
|
{
|
||||||
|
Host = "127.0.0.1",
|
||||||
|
TvCategory = "abc",
|
||||||
|
UrlBase = "/my/"
|
||||||
|
}.ToJson(),
|
||||||
|
ConfigContract = "RTorrentSettings"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var items = db.Query<DownloadClientDefinition158>("SELECT * FROM DownloadClients");
|
||||||
|
|
||||||
|
items.Should().HaveCount(1);
|
||||||
|
items.First().RemoveCompletedDownloads.Should().BeFalse();
|
||||||
|
items.First().RemoveFailedDownloads.Should().BeFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DownloadClientDefinition158
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public bool Enable { get; set; }
|
||||||
|
public int Priority { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Implementation { get; set; }
|
||||||
|
public JObject Settings { get; set; }
|
||||||
|
public string ConfigContract { get; set; }
|
||||||
|
public bool RemoveCompletedDownloads { get; set; }
|
||||||
|
public bool RemoveFailedDownloads { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -134,13 +134,6 @@ namespace NzbDrone.Core.Configuration
|
||||||
set { SetValue("EnableCompletedDownloadHandling", value); }
|
set { SetValue("EnableCompletedDownloadHandling", value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RemoveCompletedDownloads
|
|
||||||
{
|
|
||||||
get { return GetValueBoolean("RemoveCompletedDownloads", false); }
|
|
||||||
|
|
||||||
set { SetValue("RemoveCompletedDownloads", value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AutoRedownloadFailed
|
public bool AutoRedownloadFailed
|
||||||
{
|
{
|
||||||
get { return GetValueBoolean("AutoRedownloadFailed", true); }
|
get { return GetValueBoolean("AutoRedownloadFailed", true); }
|
||||||
|
@ -148,13 +141,6 @@ namespace NzbDrone.Core.Configuration
|
||||||
set { SetValue("AutoRedownloadFailed", value); }
|
set { SetValue("AutoRedownloadFailed", value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RemoveFailedDownloads
|
|
||||||
{
|
|
||||||
get { return GetValueBoolean("RemoveFailedDownloads", true); }
|
|
||||||
|
|
||||||
set { SetValue("RemoveFailedDownloads", value); }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool CreateEmptySeriesFolders
|
public bool CreateEmptySeriesFolders
|
||||||
{
|
{
|
||||||
get { return GetValueBoolean("CreateEmptySeriesFolders", false); }
|
get { return GetValueBoolean("CreateEmptySeriesFolders", false); }
|
||||||
|
|
|
@ -19,10 +19,7 @@ namespace NzbDrone.Core.Configuration
|
||||||
|
|
||||||
//Completed/Failed Download Handling (Download client)
|
//Completed/Failed Download Handling (Download client)
|
||||||
bool EnableCompletedDownloadHandling { get; set; }
|
bool EnableCompletedDownloadHandling { get; set; }
|
||||||
bool RemoveCompletedDownloads { get; set; }
|
|
||||||
|
|
||||||
bool AutoRedownloadFailed { get; set; }
|
bool AutoRedownloadFailed { get; set; }
|
||||||
bool RemoveFailedDownloads { get; set; }
|
|
||||||
|
|
||||||
//Media Management
|
//Media Management
|
||||||
bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
|
bool AutoUnmonitorPreviouslyDownloadedEpisodes { get; set; }
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using FluentMigrator;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(158)]
|
||||||
|
public class cdh_per_downloadclient : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("DownloadClients")
|
||||||
|
.AddColumn("RemoveCompletedDownloads").AsBoolean().NotNullable().WithDefaultValue(true)
|
||||||
|
.AddColumn("RemoveFailedDownloads").AsBoolean().NotNullable().WithDefaultValue(true);
|
||||||
|
|
||||||
|
Execute.WithConnection(MoveRemoveSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveRemoveSettings(IDbConnection conn, IDbTransaction tran)
|
||||||
|
{
|
||||||
|
var removeCompletedDownloads = true;
|
||||||
|
var removeFailedDownloads = false;
|
||||||
|
|
||||||
|
using (var removeCompletedDownloadsCmd = conn.CreateCommand(tran, "SELECT Value FROM Config WHERE Key = 'removecompleteddownloads'"))
|
||||||
|
{
|
||||||
|
if ("False" == (removeCompletedDownloadsCmd.ExecuteScalar() as string))
|
||||||
|
removeCompletedDownloads = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var removeFailedDownloadsCmd = conn.CreateCommand(tran, "SELECT Value FROM Config WHERE Key = 'removefaileddownloads'"))
|
||||||
|
{
|
||||||
|
if ("True" == (removeFailedDownloadsCmd.ExecuteScalar() as string))
|
||||||
|
removeFailedDownloads = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var updateClientCmd = conn.CreateCommand(tran, $"UPDATE DownloadClients SET RemoveCompletedDownloads = (CASE WHEN Implementation = \"RTorrent\" THEN 0 ELSE ? END), RemoveFailedDownloads = ?"))
|
||||||
|
{
|
||||||
|
updateClientCmd.AddParameter(removeCompletedDownloads ? 1 : 0);
|
||||||
|
updateClientCmd.AddParameter(removeFailedDownloads ? 1 : 0);
|
||||||
|
updateClientCmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
using (var removeConfigCmd = conn.CreateCommand(tran, $"DELETE FROM Config WHERE Key IN ('removecompleteddownloads', 'removefaileddownloads')"))
|
||||||
|
{
|
||||||
|
removeConfigCmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using FluentMigrator.Builders.Create;
|
using System.Data;
|
||||||
|
using FluentMigrator.Builders.Create;
|
||||||
using FluentMigrator.Builders.Create.Table;
|
using FluentMigrator.Builders.Create.Table;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore.Migration.Framework
|
namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||||
|
@ -10,7 +11,16 @@ namespace NzbDrone.Core.Datastore.Migration.Framework
|
||||||
return expressionRoot.Table(name).WithColumn("Id").AsInt32().PrimaryKey().Identity();
|
return expressionRoot.Table(name).WithColumn("Id").AsInt32().PrimaryKey().Identity();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddParameter(this System.Data.IDbCommand command, object value)
|
public static IDbCommand CreateCommand(this IDbConnection conn, IDbTransaction tran, string query)
|
||||||
|
{
|
||||||
|
var command = conn.CreateCommand();
|
||||||
|
command.Transaction = tran;
|
||||||
|
command.CommandText = query;
|
||||||
|
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddParameter(this IDbCommand command, object value)
|
||||||
{
|
{
|
||||||
var parameter = command.CreateParameter();
|
var parameter = command.CreateParameter();
|
||||||
parameter.Value = value;
|
parameter.Value = value;
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
||||||
|
@ -22,6 +23,8 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||||
{
|
{
|
||||||
private readonly IRTorrentProxy _proxy;
|
private readonly IRTorrentProxy _proxy;
|
||||||
private readonly IRTorrentDirectoryValidator _rTorrentDirectoryValidator;
|
private readonly IRTorrentDirectoryValidator _rTorrentDirectoryValidator;
|
||||||
|
private readonly IDownloadSeedConfigProvider _downloadSeedConfigProvider;
|
||||||
|
private readonly string _imported_view = String.Concat(BuildInfo.AppName.ToLower(), "_imported");
|
||||||
|
|
||||||
public RTorrent(IRTorrentProxy proxy,
|
public RTorrent(IRTorrentProxy proxy,
|
||||||
ITorrentFileInfoReader torrentFileInfoReader,
|
ITorrentFileInfoReader torrentFileInfoReader,
|
||||||
|
@ -29,17 +32,19 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IRemotePathMappingService remotePathMappingService,
|
IRemotePathMappingService remotePathMappingService,
|
||||||
|
IDownloadSeedConfigProvider downloadSeedConfigProvider,
|
||||||
IRTorrentDirectoryValidator rTorrentDirectoryValidator,
|
IRTorrentDirectoryValidator rTorrentDirectoryValidator,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
|
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
|
||||||
{
|
{
|
||||||
_proxy = proxy;
|
_proxy = proxy;
|
||||||
_rTorrentDirectoryValidator = rTorrentDirectoryValidator;
|
_rTorrentDirectoryValidator = rTorrentDirectoryValidator;
|
||||||
|
_downloadSeedConfigProvider = downloadSeedConfigProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void MarkItemAsImported(DownloadClientItem downloadClientItem)
|
public override void MarkItemAsImported(DownloadClientItem downloadClientItem)
|
||||||
{
|
{
|
||||||
// set post-import category
|
// Set post-import label
|
||||||
if (Settings.TvImportedCategory.IsNotNullOrWhiteSpace() &&
|
if (Settings.TvImportedCategory.IsNotNullOrWhiteSpace() &&
|
||||||
Settings.TvImportedCategory != Settings.TvCategory)
|
Settings.TvImportedCategory != Settings.TvCategory)
|
||||||
{
|
{
|
||||||
|
@ -53,6 +58,17 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||||
Settings.TvImportedCategory, downloadClientItem.Title);
|
Settings.TvImportedCategory, downloadClientItem.Title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink)
|
||||||
|
@ -95,7 +111,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||||
|
|
||||||
public override string Name => "rTorrent";
|
public override string Name => "rTorrent";
|
||||||
|
|
||||||
public override ProviderMessage Message => new ProviderMessage("Sonarr is unable to remove torrents that have finished seeding when using rTorrent", ProviderMessageType.Warning);
|
public override ProviderMessage Message => new ProviderMessage($"Sonarr will handle automatic removal of torrents based on the current seed criteria in Settings->Indexers. After importing it will also set \"{_imported_view}\" as an rTorrent view, which can be used in rTorrent scripts to customize behavior.", ProviderMessageType.Info);
|
||||||
|
|
||||||
public override IEnumerable<DownloadClientItem> GetItems()
|
public override IEnumerable<DownloadClientItem> GetItems()
|
||||||
{
|
{
|
||||||
|
@ -147,8 +163,16 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||||
item.Status = DownloadItemStatus.Paused;
|
item.Status = DownloadItemStatus.Paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No stop ratio data is present, so do not delete
|
// Grab cached seedConfig
|
||||||
item.CanMoveFiles = item.CanBeRemoved = false;
|
var seedConfig = _downloadSeedConfigProvider.GetSeedConfiguration(torrent.Hash);
|
||||||
|
|
||||||
|
// Check if torrent is finished and if it exceeds cached seedConfig
|
||||||
|
item.CanMoveFiles = item.CanBeRemoved =
|
||||||
|
torrent.IsFinished && seedConfig != null &&
|
||||||
|
(
|
||||||
|
(torrent.Ratio / 1000.0) >= seedConfig.Ratio ||
|
||||||
|
(DateTimeOffset.Now - DateTimeOffset.FromUnixTimeSeconds(torrent.FinishedTime)) >= seedConfig.SeedTime
|
||||||
|
);
|
||||||
|
|
||||||
items.Add(item);
|
items.Add(item);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||||
void RemoveTorrent(string hash, RTorrentSettings settings);
|
void RemoveTorrent(string hash, RTorrentSettings settings);
|
||||||
void SetTorrentLabel(string hash, string label, RTorrentSettings settings);
|
void SetTorrentLabel(string hash, string label, RTorrentSettings settings);
|
||||||
bool HasHashTorrent(string hash, RTorrentSettings settings);
|
bool HasHashTorrent(string hash, RTorrentSettings settings);
|
||||||
|
void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IRTorrent : IXmlRpcProxy
|
public interface IRTorrent : IXmlRpcProxy
|
||||||
|
@ -48,6 +49,9 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||||
[XmlRpcMethod("d.custom1.set")]
|
[XmlRpcMethod("d.custom1.set")]
|
||||||
string SetLabel(string hash, string label);
|
string SetLabel(string hash, string label);
|
||||||
|
|
||||||
|
[XmlRpcMethod("d.views.push_back_unique")]
|
||||||
|
int PushUniqueView(string hash, string view);
|
||||||
|
|
||||||
[XmlRpcMethod("system.client_version")]
|
[XmlRpcMethod("system.client_version")]
|
||||||
string GetVersion();
|
string GetVersion();
|
||||||
}
|
}
|
||||||
|
@ -87,7 +91,8 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||||
"d.ratio=", // long
|
"d.ratio=", // long
|
||||||
"d.is_open=", // long
|
"d.is_open=", // long
|
||||||
"d.is_active=", // long
|
"d.is_active=", // long
|
||||||
"d.complete=") //long
|
"d.complete=", // long
|
||||||
|
"d.timestamp.finished=") // long (unix timestamp)
|
||||||
);
|
);
|
||||||
|
|
||||||
var items = new List<RTorrentTorrent>();
|
var items = new List<RTorrentTorrent>();
|
||||||
|
@ -108,6 +113,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||||
item.IsOpen = Convert.ToBoolean((long)torrent[8]);
|
item.IsOpen = Convert.ToBoolean((long)torrent[8]);
|
||||||
item.IsActive = Convert.ToBoolean((long)torrent[9]);
|
item.IsActive = Convert.ToBoolean((long)torrent[9]);
|
||||||
item.IsFinished = Convert.ToBoolean((long)torrent[10]);
|
item.IsFinished = Convert.ToBoolean((long)torrent[10]);
|
||||||
|
item.FinishedTime = (long)torrent[11];
|
||||||
|
|
||||||
items.Add(item);
|
items.Add(item);
|
||||||
}
|
}
|
||||||
|
@ -174,6 +180,18 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings)
|
||||||
|
{
|
||||||
|
_logger.Debug("Executing remote method: d.views.push_back_unique");
|
||||||
|
|
||||||
|
var client = BuildClient(settings);
|
||||||
|
var response = ExecuteRequest(() => client.PushUniqueView(hash, view));
|
||||||
|
if (response != 0)
|
||||||
|
{
|
||||||
|
throw new DownloadClientException("Could not push unique view {0} for torrent: {1}.", view, hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void RemoveTorrent(string hash, RTorrentSettings settings)
|
public void RemoveTorrent(string hash, RTorrentSettings settings)
|
||||||
{
|
{
|
||||||
_logger.Debug("Executing remote method: d.erase");
|
_logger.Debug("Executing remote method: d.erase");
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
public long RemainingSize { get; set; }
|
public long RemainingSize { get; set; }
|
||||||
public long DownRate { get; set; }
|
public long DownRate { get; set; }
|
||||||
public long Ratio { get; set; }
|
public long Ratio { get; set; }
|
||||||
|
public long FinishedTime { get; set; }
|
||||||
public bool IsFinished { get; set; }
|
public bool IsFinished { get; set; }
|
||||||
public bool IsOpen { get; set; }
|
public bool IsOpen { get; set; }
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
|
|
|
@ -7,5 +7,8 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
public DownloadProtocol Protocol { get; set; }
|
public DownloadProtocol Protocol { get; set; }
|
||||||
public int Priority { get; set; } = 1;
|
public int Priority { get; set; } = 1;
|
||||||
|
|
||||||
|
public bool RemoveCompletedDownloads { get; set; } = true;
|
||||||
|
public bool RemoveFailedDownloads { get; set; } = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,38 +27,65 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
var trackedDownload = message.TrackedDownload;
|
var trackedDownload = message.TrackedDownload;
|
||||||
|
|
||||||
if (trackedDownload == null || !trackedDownload.DownloadItem.CanBeRemoved || _configService.RemoveFailedDownloads == false)
|
if (trackedDownload == null ||
|
||||||
|
message.TrackedDownload.DownloadItem.Removed ||
|
||||||
|
!trackedDownload.DownloadItem.CanBeRemoved)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveFromDownloadClient(trackedDownload);
|
var downloadClient = _downloadClientProvider.Get(message.TrackedDownload.DownloadClient);
|
||||||
|
var definition = downloadClient.Definition as DownloadClientDefinition;
|
||||||
|
|
||||||
|
if (!definition.RemoveFailedDownloads)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveFromDownloadClient(trackedDownload, downloadClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(DownloadCompletedEvent message)
|
public void Handle(DownloadCompletedEvent message)
|
||||||
{
|
{
|
||||||
if (_configService.RemoveCompletedDownloads &&
|
var trackedDownload = message.TrackedDownload;
|
||||||
!message.TrackedDownload.DownloadItem.Removed &&
|
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
|
||||||
message.TrackedDownload.DownloadItem.CanBeRemoved &&
|
var definition = downloadClient.Definition as DownloadClientDefinition;
|
||||||
message.TrackedDownload.DownloadItem.Status != DownloadItemStatus.Downloading)
|
|
||||||
|
MarkItemAsImported(trackedDownload, downloadClient);
|
||||||
|
|
||||||
|
if (trackedDownload.DownloadItem.Removed ||
|
||||||
|
!trackedDownload.DownloadItem.CanBeRemoved ||
|
||||||
|
trackedDownload.DownloadItem.Status == DownloadItemStatus.Downloading)
|
||||||
{
|
{
|
||||||
RemoveFromDownloadClient(message.TrackedDownload);
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (!definition.RemoveCompletedDownloads)
|
||||||
{
|
{
|
||||||
MarkItemAsImported(message.TrackedDownload);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RemoveFromDownloadClient(message.TrackedDownload, downloadClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(DownloadCanBeRemovedEvent message)
|
public void Handle(DownloadCanBeRemovedEvent message)
|
||||||
{
|
{
|
||||||
// Already verified that it can be removed, just needs to be removed
|
var trackedDownload = message.TrackedDownload;
|
||||||
RemoveFromDownloadClient(message.TrackedDownload);
|
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
|
||||||
|
var definition = downloadClient.Definition as DownloadClientDefinition;
|
||||||
|
|
||||||
|
if (trackedDownload.DownloadItem.Removed ||
|
||||||
|
!trackedDownload.DownloadItem.CanBeRemoved ||
|
||||||
|
!definition.RemoveCompletedDownloads)
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveFromDownloadClient(TrackedDownload trackedDownload)
|
RemoveFromDownloadClient(message.TrackedDownload, downloadClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveFromDownloadClient(TrackedDownload trackedDownload, IDownloadClient downloadClient)
|
||||||
{
|
{
|
||||||
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Debug("[{0}] Removing download from {1} history", trackedDownload.DownloadItem.Title, trackedDownload.DownloadItem.DownloadClientInfo.Name);
|
_logger.Debug("[{0}] Removing download from {1} history", trackedDownload.DownloadItem.Title, trackedDownload.DownloadItem.DownloadClientInfo.Name);
|
||||||
|
@ -75,9 +102,8 @@ namespace NzbDrone.Core.Download
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MarkItemAsImported(TrackedDownload trackedDownload)
|
private void MarkItemAsImported(TrackedDownload trackedDownload, IDownloadClient downloadClient)
|
||||||
{
|
{
|
||||||
var downloadClient = _downloadClientProvider.Get(trackedDownload.DownloadClient);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Debug("[{0}] Marking download as imported from {1}", trackedDownload.DownloadItem.Title, trackedDownload.DownloadItem.DownloadClientInfo.Name);
|
_logger.Debug("[{0}] Marking download as imported from {1}", trackedDownload.DownloadItem.Title, trackedDownload.DownloadItem.DownloadClientInfo.Name);
|
||||||
|
|
|
@ -48,7 +48,6 @@ namespace NzbDrone.Core.Download
|
||||||
public void Execute(ProcessMonitoredDownloadsCommand message)
|
public void Execute(ProcessMonitoredDownloadsCommand message)
|
||||||
{
|
{
|
||||||
var enableCompletedDownloadHandling = _configService.EnableCompletedDownloadHandling;
|
var enableCompletedDownloadHandling = _configService.EnableCompletedDownloadHandling;
|
||||||
var removeCompletedDownloads = _configService.RemoveCompletedDownloads;
|
|
||||||
var trackedDownloads = _trackedDownloadService.GetTrackedDownloads()
|
var trackedDownloads = _trackedDownloadService.GetTrackedDownloads()
|
||||||
.Where(t => t.IsTrackable)
|
.Where(t => t.IsTrackable)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
@ -73,10 +72,7 @@ namespace NzbDrone.Core.Download
|
||||||
}
|
}
|
||||||
|
|
||||||
// Imported downloads are no longer trackable so process them after processing trackable downloads
|
// Imported downloads are no longer trackable so process them after processing trackable downloads
|
||||||
if (removeCompletedDownloads)
|
|
||||||
{
|
|
||||||
RemoveCompletedDownloads();
|
RemoveCompletedDownloads();
|
||||||
}
|
|
||||||
|
|
||||||
_eventAggregator.PublishEvent(new DownloadsProcessedEvent());
|
_eventAggregator.PublishEvent(new DownloadsProcessedEvent());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
using System;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Download.Clients;
|
||||||
|
using NzbDrone.Core.Download.History;
|
||||||
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public interface IDownloadSeedConfigProvider
|
||||||
|
{
|
||||||
|
TorrentSeedConfiguration GetSeedConfiguration(string infoHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DownloadSeedConfigProvider : IDownloadSeedConfigProvider
|
||||||
|
{
|
||||||
|
private readonly Logger _logger;
|
||||||
|
private readonly ISeedConfigProvider _indexerSeedConfigProvider;
|
||||||
|
private readonly IDownloadHistoryService _downloadHistoryService;
|
||||||
|
|
||||||
|
class CachedSeedConfiguration
|
||||||
|
{
|
||||||
|
public int IndexerId { get; set; }
|
||||||
|
public bool FullSeason { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ICached<CachedSeedConfiguration> _cacheDownloads;
|
||||||
|
|
||||||
|
public DownloadSeedConfigProvider(IDownloadHistoryService downloadHistoryService, ISeedConfigProvider indexerSeedConfigProvider, ICacheManager cacheManager, Logger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_indexerSeedConfigProvider = indexerSeedConfigProvider;
|
||||||
|
_downloadHistoryService = downloadHistoryService;
|
||||||
|
|
||||||
|
_cacheDownloads = cacheManager.GetRollingCache<CachedSeedConfiguration>(GetType(), "indexerByHash", TimeSpan.FromHours(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public TorrentSeedConfiguration GetSeedConfiguration(string infoHash)
|
||||||
|
{
|
||||||
|
if (infoHash.IsNullOrWhiteSpace()) return null;
|
||||||
|
|
||||||
|
infoHash = infoHash.ToUpper();
|
||||||
|
|
||||||
|
var cachedConfig = _cacheDownloads.Get(infoHash, () => FetchIndexer(infoHash));
|
||||||
|
|
||||||
|
if (cachedConfig == null) return null;
|
||||||
|
|
||||||
|
var seedConfig = _indexerSeedConfigProvider.GetSeedConfiguration(cachedConfig.IndexerId, cachedConfig.FullSeason);
|
||||||
|
|
||||||
|
return seedConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CachedSeedConfiguration FetchIndexer(string infoHash)
|
||||||
|
{
|
||||||
|
var historyItem = _downloadHistoryService.GetLatestGrab(infoHash);
|
||||||
|
|
||||||
|
if (historyItem == null)
|
||||||
|
{
|
||||||
|
_logger.Debug("No download history item for infohash {0}, unable to provide seed configuration", infoHash);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsedEpisodeInfo parsedEpisodeInfo = null;
|
||||||
|
if (historyItem.Release != null)
|
||||||
|
{
|
||||||
|
parsedEpisodeInfo = Parser.Parser.ParseTitle(historyItem.Release.Title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedEpisodeInfo == null)
|
||||||
|
{
|
||||||
|
_logger.Debug("No parsed title in download history item for infohash {0}, unable to provide seed configuration", infoHash);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CachedSeedConfiguration
|
||||||
|
{
|
||||||
|
IndexerId = historyItem.IndexerId,
|
||||||
|
FullSeason = parsedEpisodeInfo.FullSeason
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
using NzbDrone.Common.Messaging;
|
|
||||||
using NzbDrone.Core.ThingiProvider;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers
|
|
||||||
{
|
|
||||||
public class IndexerSettingUpdatedEvent : IEvent
|
|
||||||
{
|
|
||||||
public string IndexerName { get; private set; }
|
|
||||||
public IProviderConfig IndexerSetting { get; private set; }
|
|
||||||
|
|
||||||
public IndexerSettingUpdatedEvent(string indexerName, IProviderConfig indexerSetting)
|
|
||||||
{
|
|
||||||
IndexerName = indexerName;
|
|
||||||
IndexerSetting = indexerSetting;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +1,30 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Download.Clients;
|
using NzbDrone.Core.Download.Clients;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using NzbDrone.Core.ThingiProvider.Events;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers
|
namespace NzbDrone.Core.Indexers
|
||||||
{
|
{
|
||||||
public interface ISeedConfigProvider
|
public interface ISeedConfigProvider
|
||||||
{
|
{
|
||||||
TorrentSeedConfiguration GetSeedConfiguration(RemoteEpisode release);
|
TorrentSeedConfiguration GetSeedConfiguration(RemoteEpisode release);
|
||||||
|
TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSeason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SeedConfigProvider : ISeedConfigProvider
|
public class SeedConfigProvider : ISeedConfigProvider, IHandle<ProviderUpdatedEvent<IIndexer>>
|
||||||
{
|
{
|
||||||
private readonly IIndexerFactory _indexerFactory;
|
private readonly IIndexerFactory _indexerFactory;
|
||||||
|
private readonly ICached<SeedCriteriaSettings> _cache;
|
||||||
|
|
||||||
public SeedConfigProvider(IIndexerFactory indexerFactory)
|
public SeedConfigProvider(IIndexerFactory indexerFactory, ICacheManager cacheManager)
|
||||||
{
|
{
|
||||||
_indexerFactory = indexerFactory;
|
_indexerFactory = indexerFactory;
|
||||||
|
_cache = cacheManager.GetRollingCache<SeedCriteriaSettings>(GetType(), "criteriaByIndexer", TimeSpan.FromHours(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public TorrentSeedConfiguration GetSeedConfiguration(RemoteEpisode remoteEpisode)
|
public TorrentSeedConfiguration GetSeedConfiguration(RemoteEpisode remoteEpisode)
|
||||||
|
@ -27,19 +32,23 @@ namespace NzbDrone.Core.Indexers
|
||||||
if (remoteEpisode.Release.DownloadProtocol != DownloadProtocol.Torrent) return null;
|
if (remoteEpisode.Release.DownloadProtocol != DownloadProtocol.Torrent) return null;
|
||||||
if (remoteEpisode.Release.IndexerId == 0) return null;
|
if (remoteEpisode.Release.IndexerId == 0) return null;
|
||||||
|
|
||||||
try
|
return GetSeedConfiguration(remoteEpisode.Release.IndexerId, remoteEpisode.ParsedEpisodeInfo.FullSeason);
|
||||||
{
|
}
|
||||||
var indexer = _indexerFactory.Get(remoteEpisode.Release.IndexerId);
|
|
||||||
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
|
|
||||||
|
|
||||||
if (torrentIndexerSettings != null && torrentIndexerSettings.SeedCriteria != null)
|
public TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSeason)
|
||||||
{
|
{
|
||||||
|
if (indexerId == 0) return null;
|
||||||
|
|
||||||
|
var seedCriteria = _cache.Get(indexerId.ToString(), () => FetchSeedCriteria(indexerId));
|
||||||
|
|
||||||
|
if (seedCriteria == null) return null;
|
||||||
|
|
||||||
var seedConfig = new TorrentSeedConfiguration
|
var seedConfig = new TorrentSeedConfiguration
|
||||||
{
|
{
|
||||||
Ratio = torrentIndexerSettings.SeedCriteria.SeedRatio
|
Ratio = seedCriteria.SeedRatio
|
||||||
};
|
};
|
||||||
|
|
||||||
var seedTime = remoteEpisode.ParsedEpisodeInfo.FullSeason ? torrentIndexerSettings.SeedCriteria.SeasonPackSeedTime : torrentIndexerSettings.SeedCriteria.SeedTime;
|
var seedTime = fullSeason ? seedCriteria.SeasonPackSeedTime : seedCriteria.SeedTime;
|
||||||
if (seedTime.HasValue)
|
if (seedTime.HasValue)
|
||||||
{
|
{
|
||||||
seedConfig.SeedTime = TimeSpan.FromMinutes(seedTime.Value);
|
seedConfig.SeedTime = TimeSpan.FromMinutes(seedTime.Value);
|
||||||
|
@ -47,13 +56,25 @@ namespace NzbDrone.Core.Indexers
|
||||||
|
|
||||||
return seedConfig;
|
return seedConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SeedCriteriaSettings FetchSeedCriteria(int indexerId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var indexer = _indexerFactory.Get(indexerId);
|
||||||
|
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
|
||||||
|
|
||||||
|
return torrentIndexerSettings?.SeedCriteria;
|
||||||
}
|
}
|
||||||
catch (ModelNotFoundException)
|
catch (ModelNotFoundException)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
public void Handle(ProviderUpdatedEvent<IIndexer> message)
|
||||||
|
{
|
||||||
|
_cache.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,10 @@ namespace NzbDrone.Core.ThingiProvider
|
||||||
|
|
||||||
public virtual TProviderDefinition Create(TProviderDefinition definition)
|
public virtual TProviderDefinition Create(TProviderDefinition definition)
|
||||||
{
|
{
|
||||||
return _providerRepository.Insert(definition);
|
var result = _providerRepository.Insert(definition);
|
||||||
|
_eventAggregator.PublishEvent(new ProviderUpdatedEvent<TProvider>(result));
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Update(TProviderDefinition definition)
|
public virtual void Update(TProviderDefinition definition)
|
||||||
|
|
|
@ -8,10 +8,7 @@ namespace Sonarr.Api.V3.Config
|
||||||
public string DownloadClientWorkingFolders { get; set; }
|
public string DownloadClientWorkingFolders { get; set; }
|
||||||
|
|
||||||
public bool EnableCompletedDownloadHandling { get; set; }
|
public bool EnableCompletedDownloadHandling { get; set; }
|
||||||
public bool RemoveCompletedDownloads { get; set; }
|
|
||||||
|
|
||||||
public bool AutoRedownloadFailed { get; set; }
|
public bool AutoRedownloadFailed { get; set; }
|
||||||
public bool RemoveFailedDownloads { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DownloadClientConfigResourceMapper
|
public static class DownloadClientConfigResourceMapper
|
||||||
|
@ -23,10 +20,7 @@ namespace Sonarr.Api.V3.Config
|
||||||
DownloadClientWorkingFolders = model.DownloadClientWorkingFolders,
|
DownloadClientWorkingFolders = model.DownloadClientWorkingFolders,
|
||||||
|
|
||||||
EnableCompletedDownloadHandling = model.EnableCompletedDownloadHandling,
|
EnableCompletedDownloadHandling = model.EnableCompletedDownloadHandling,
|
||||||
RemoveCompletedDownloads = model.RemoveCompletedDownloads,
|
AutoRedownloadFailed = model.AutoRedownloadFailed
|
||||||
|
|
||||||
AutoRedownloadFailed = model.AutoRedownloadFailed,
|
|
||||||
RemoveFailedDownloads = model.RemoveFailedDownloads
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ namespace Sonarr.Api.V3.DownloadClient
|
||||||
public bool Enable { get; set; }
|
public bool Enable { get; set; }
|
||||||
public DownloadProtocol Protocol { get; set; }
|
public DownloadProtocol Protocol { get; set; }
|
||||||
public int Priority { get; set; }
|
public int Priority { get; set; }
|
||||||
|
public bool RemoveCompletedDownloads { get; set; }
|
||||||
|
public bool RemoveFailedDownloads { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DownloadClientResourceMapper : ProviderResourceMapper<DownloadClientResource, DownloadClientDefinition>
|
public class DownloadClientResourceMapper : ProviderResourceMapper<DownloadClientResource, DownloadClientDefinition>
|
||||||
|
@ -21,6 +23,8 @@ namespace Sonarr.Api.V3.DownloadClient
|
||||||
resource.Enable = definition.Enable;
|
resource.Enable = definition.Enable;
|
||||||
resource.Protocol = definition.Protocol;
|
resource.Protocol = definition.Protocol;
|
||||||
resource.Priority = definition.Priority;
|
resource.Priority = definition.Priority;
|
||||||
|
resource.RemoveCompletedDownloads = definition.RemoveCompletedDownloads;
|
||||||
|
resource.RemoveFailedDownloads = definition.RemoveFailedDownloads;
|
||||||
|
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
@ -34,6 +38,8 @@ namespace Sonarr.Api.V3.DownloadClient
|
||||||
definition.Enable = resource.Enable;
|
definition.Enable = resource.Enable;
|
||||||
definition.Protocol = resource.Protocol;
|
definition.Protocol = resource.Protocol;
|
||||||
definition.Priority = resource.Priority;
|
definition.Priority = resource.Priority;
|
||||||
|
definition.RemoveCompletedDownloads = resource.RemoveCompletedDownloads;
|
||||||
|
definition.RemoveFailedDownloads = resource.RemoveFailedDownloads;
|
||||||
|
|
||||||
return definition;
|
return definition;
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,8 +172,7 @@ namespace Sonarr.Http.ClientSchema
|
||||||
{
|
{
|
||||||
Value = value,
|
Value = value,
|
||||||
Name = name,
|
Name = name,
|
||||||
Order = value,
|
Order = value
|
||||||
Hint = $"({value})"
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue