mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-04-17 21:26:13 -04:00
Compare commits
16 Commits
v4.0.5.176
...
v4.0.6.180
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55c1ce2e3d | ||
|
|
fd7f0ea973 | ||
|
|
d5dff8e8d6 | ||
|
|
8099ba10af | ||
|
|
143ccb1e2a | ||
|
|
29480d9544 | ||
|
|
6de536a7ad | ||
|
|
bce848facf | ||
|
|
ea4fe392a0 | ||
|
|
45fe585944 | ||
|
|
a0d2933134 | ||
|
|
4c622fd412 | ||
|
|
fb060730c7 | ||
|
|
6d5ff9c4d6 | ||
|
|
63bed3e670 | ||
|
|
e684c10432 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -22,7 +22,7 @@ env:
|
||||
FRAMEWORK: net6.0
|
||||
RAW_BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
SONARR_MAJOR_VERSION: 4
|
||||
VERSION: 4.0.5
|
||||
VERSION: 4.0.6
|
||||
|
||||
jobs:
|
||||
backend:
|
||||
|
||||
@@ -217,6 +217,7 @@ class Queue extends Component {
|
||||
>
|
||||
<TableOptionsModalWrapper
|
||||
columns={columns}
|
||||
maxPageSize={200}
|
||||
{...otherProps}
|
||||
optionsComponent={QueueOptionsConnector}
|
||||
>
|
||||
|
||||
@@ -70,6 +70,11 @@ function QueueStatus(props) {
|
||||
iconName = icons.DOWNLOADED;
|
||||
title = translate('Downloaded');
|
||||
|
||||
if (trackedDownloadState === 'importBlocked') {
|
||||
title += ` - ${translate('UnableToImportAutomatically')}`;
|
||||
iconKind = kinds.WARNING;
|
||||
}
|
||||
|
||||
if (trackedDownloadState === 'importPending') {
|
||||
title += ` - ${translate('WaitingToImport')}`;
|
||||
iconKind = kinds.PURPLE;
|
||||
|
||||
@@ -271,26 +271,32 @@ class EnhancedSelectInput extends Component {
|
||||
this.setState({ isOpen: !this.state.isOpen });
|
||||
};
|
||||
|
||||
onSelect = (value) => {
|
||||
if (Array.isArray(this.props.value)) {
|
||||
let newValue = null;
|
||||
const index = this.props.value.indexOf(value);
|
||||
onSelect = (newValue) => {
|
||||
const { name, value, values, onChange } = this.props;
|
||||
const additionalProperties = values.find((v) => v.key === newValue)?.additionalProperties;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
let arrayValue = null;
|
||||
const index = value.indexOf(newValue);
|
||||
|
||||
if (index === -1) {
|
||||
newValue = this.props.values.map((v) => v.key).filter((v) => (v === value) || this.props.value.includes(v));
|
||||
arrayValue = values.map((v) => v.key).filter((v) => (v === newValue) || value.includes(v));
|
||||
} else {
|
||||
newValue = [...this.props.value];
|
||||
newValue.splice(index, 1);
|
||||
arrayValue = [...value];
|
||||
arrayValue.splice(index, 1);
|
||||
}
|
||||
this.props.onChange({
|
||||
name: this.props.name,
|
||||
value: newValue
|
||||
onChange({
|
||||
name,
|
||||
value: arrayValue,
|
||||
additionalProperties
|
||||
});
|
||||
} else {
|
||||
this.setState({ isOpen: false });
|
||||
|
||||
this.props.onChange({
|
||||
name: this.props.name,
|
||||
value
|
||||
onChange({
|
||||
name,
|
||||
value: newValue,
|
||||
additionalProperties
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -485,7 +491,7 @@ class EnhancedSelectInput extends Component {
|
||||
values.map((v, index) => {
|
||||
const hasParent = v.parentKey !== undefined;
|
||||
const depth = hasParent ? 1 : 0;
|
||||
const parentSelected = hasParent && value.includes(v.parentKey);
|
||||
const parentSelected = hasParent && Array.isArray(value) && value.includes(v.parentKey);
|
||||
return (
|
||||
<OptionComponent
|
||||
key={v.key}
|
||||
|
||||
@@ -9,7 +9,8 @@ import EnhancedSelectInput from './EnhancedSelectInput';
|
||||
const importantFieldNames = [
|
||||
'baseUrl',
|
||||
'apiPath',
|
||||
'apiKey'
|
||||
'apiKey',
|
||||
'authToken'
|
||||
];
|
||||
|
||||
function getProviderDataKey(providerData) {
|
||||
@@ -34,7 +35,9 @@ function getSelectOptions(items) {
|
||||
key: option.value,
|
||||
value: option.name,
|
||||
hint: option.hint,
|
||||
parentKey: option.parentValue
|
||||
parentKey: option.parentValue,
|
||||
isDisabled: option.isDisabled,
|
||||
additionalProperties: option.additionalProperties
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -147,7 +150,7 @@ EnhancedSelectInputConnector.propTypes = {
|
||||
provider: PropTypes.string.isRequired,
|
||||
providerData: PropTypes.object.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])).isRequired,
|
||||
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.arrayOf(PropTypes.string), PropTypes.arrayOf(PropTypes.number)]).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
selectOptionsProviderAction: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
|
||||
@@ -49,11 +49,12 @@ class TableOptionsModal extends Component {
|
||||
|
||||
onPageSizeChange = ({ value }) => {
|
||||
let pageSizeError = null;
|
||||
const maxPageSize = this.props.maxPageSize ?? 250;
|
||||
|
||||
if (value < 5) {
|
||||
pageSizeError = translate('TablePageSizeMinimum', { minimumValue: '5' });
|
||||
} else if (value > 250) {
|
||||
pageSizeError = translate('TablePageSizeMaximum', { maximumValue: '250' });
|
||||
} else if (value > maxPageSize) {
|
||||
pageSizeError = translate('TablePageSizeMaximum', { maximumValue: `${maxPageSize}` });
|
||||
} else {
|
||||
this.props.onTableOptionChange({ pageSize: value });
|
||||
}
|
||||
@@ -248,6 +249,7 @@ TableOptionsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
pageSize: PropTypes.number,
|
||||
maxPageSize: PropTypes.number,
|
||||
canModifyColumns: PropTypes.bool.isRequired,
|
||||
optionsComponent: PropTypes.elementType,
|
||||
onTableOptionChange: PropTypes.func.isRequired,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import {
|
||||
saveNotification,
|
||||
setNotificationFieldValue,
|
||||
setNotificationFieldValues,
|
||||
setNotificationValue,
|
||||
testNotification,
|
||||
toggleAdvancedSettings
|
||||
@@ -27,7 +27,7 @@ function createMapStateToProps() {
|
||||
|
||||
const mapDispatchToProps = {
|
||||
setNotificationValue,
|
||||
setNotificationFieldValue,
|
||||
setNotificationFieldValues,
|
||||
saveNotification,
|
||||
testNotification,
|
||||
toggleAdvancedSettings
|
||||
@@ -51,8 +51,8 @@ class EditNotificationModalContentConnector extends Component {
|
||||
this.props.setNotificationValue({ name, value });
|
||||
};
|
||||
|
||||
onFieldChange = ({ name, value }) => {
|
||||
this.props.setNotificationFieldValue({ name, value });
|
||||
onFieldChange = ({ name, value, additionalProperties = {} }) => {
|
||||
this.props.setNotificationFieldValues({ properties: { ...additionalProperties, [name]: value } });
|
||||
};
|
||||
|
||||
onSavePress = () => {
|
||||
@@ -91,7 +91,7 @@ EditNotificationModalContentConnector.propTypes = {
|
||||
saveError: PropTypes.object,
|
||||
item: PropTypes.object.isRequired,
|
||||
setNotificationValue: PropTypes.func.isRequired,
|
||||
setNotificationFieldValue: PropTypes.func.isRequired,
|
||||
setNotificationFieldValues: PropTypes.func.isRequired,
|
||||
saveNotification: PropTypes.func.isRequired,
|
||||
testNotification: PropTypes.func.isRequired,
|
||||
toggleAdvancedSettings: PropTypes.func.isRequired,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import getSectionState from 'Utilities/State/getSectionState';
|
||||
import updateSectionState from 'Utilities/State/updateSectionState';
|
||||
|
||||
function createSetProviderFieldValuesReducer(section) {
|
||||
return (state, { payload }) => {
|
||||
if (section === payload.section) {
|
||||
const { properties } = payload;
|
||||
const newState = getSectionState(state, section);
|
||||
newState.pendingChanges = Object.assign({}, newState.pendingChanges);
|
||||
const fields = Object.assign({}, newState.pendingChanges.fields || {});
|
||||
|
||||
Object.keys(properties).forEach((name) => {
|
||||
fields[name] = properties[name];
|
||||
});
|
||||
|
||||
newState.pendingChanges.fields = fields;
|
||||
|
||||
return updateSectionState(state, section, newState);
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
}
|
||||
|
||||
export default createSetProviderFieldValuesReducer;
|
||||
@@ -5,6 +5,7 @@ import createRemoveItemHandler from 'Store/Actions/Creators/createRemoveItemHand
|
||||
import createSaveProviderHandler, { createCancelSaveProviderHandler } from 'Store/Actions/Creators/createSaveProviderHandler';
|
||||
import createTestProviderHandler, { createCancelTestProviderHandler } from 'Store/Actions/Creators/createTestProviderHandler';
|
||||
import createSetProviderFieldValueReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValueReducer';
|
||||
import createSetProviderFieldValuesReducer from 'Store/Actions/Creators/Reducers/createSetProviderFieldValuesReducer';
|
||||
import createSetSettingValueReducer from 'Store/Actions/Creators/Reducers/createSetSettingValueReducer';
|
||||
import { createThunk } from 'Store/thunks';
|
||||
import selectProviderSchema from 'Utilities/State/selectProviderSchema';
|
||||
@@ -22,6 +23,7 @@ export const FETCH_NOTIFICATION_SCHEMA = 'settings/notifications/fetchNotificati
|
||||
export const SELECT_NOTIFICATION_SCHEMA = 'settings/notifications/selectNotificationSchema';
|
||||
export const SET_NOTIFICATION_VALUE = 'settings/notifications/setNotificationValue';
|
||||
export const SET_NOTIFICATION_FIELD_VALUE = 'settings/notifications/setNotificationFieldValue';
|
||||
export const SET_NOTIFICATION_FIELD_VALUES = 'settings/notifications/setNotificationFieldValues';
|
||||
export const SAVE_NOTIFICATION = 'settings/notifications/saveNotification';
|
||||
export const CANCEL_SAVE_NOTIFICATION = 'settings/notifications/cancelSaveNotification';
|
||||
export const DELETE_NOTIFICATION = 'settings/notifications/deleteNotification';
|
||||
@@ -55,6 +57,13 @@ export const setNotificationFieldValue = createAction(SET_NOTIFICATION_FIELD_VAL
|
||||
};
|
||||
});
|
||||
|
||||
export const setNotificationFieldValues = createAction(SET_NOTIFICATION_FIELD_VALUES, (payload) => {
|
||||
return {
|
||||
section,
|
||||
...payload
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Details
|
||||
|
||||
@@ -99,6 +108,7 @@ export default {
|
||||
reducers: {
|
||||
[SET_NOTIFICATION_VALUE]: createSetSettingValueReducer(section),
|
||||
[SET_NOTIFICATION_FIELD_VALUE]: createSetProviderFieldValueReducer(section),
|
||||
[SET_NOTIFICATION_FIELD_VALUES]: createSetProviderFieldValuesReducer(section),
|
||||
|
||||
[SELECT_NOTIFICATION_SCHEMA]: (state, { payload }) => {
|
||||
return selectProviderSchema(state, section, payload, (selectedSchema) => {
|
||||
|
||||
@@ -366,7 +366,7 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
|
||||
Mocker.GetMock<IEventAggregator>()
|
||||
.Verify(v => v.PublishEvent(It.IsAny<DownloadCompletedEvent>()), Times.Never());
|
||||
|
||||
_trackedDownload.State.Should().Be(TrackedDownloadState.ImportPending);
|
||||
_trackedDownload.State.Should().Be(TrackedDownloadState.ImportBlocked);
|
||||
}
|
||||
|
||||
private void AssertImported()
|
||||
|
||||
@@ -39,6 +39,13 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
ExceptionVerification.IgnoreWarns();
|
||||
}
|
||||
|
||||
[TestCase("علم نف) أ.دعادل الأبيض ٢٠٢٤ ٣ ٣")]
|
||||
[TestCase("ror-240618_1007-1022-")]
|
||||
public void should_parse_unknown_formats_without_error(string title)
|
||||
{
|
||||
Parser.Parser.ParseTitle(title).Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_parse_md5()
|
||||
{
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Series No More S01 2023 1080p WEB-DL AVC AC3 2.0 Dual Audio -ZR-", "Series No More", 1)]
|
||||
[TestCase("Series Title / S1E1-8 of 8 [2024, WEB-DL 1080p] + Original + RUS", "Series Title", 1)]
|
||||
[TestCase("Series Title / S2E1-16 of 16 [2022, WEB-DL] RUS", "Series Title", 2)]
|
||||
[TestCase("[hchcsen] Mobile Series 00 S01 [BD Remux Dual Audio 1080p AVC 2xFLAC] (Kidou Senshi Gundam 00 Season 1)", "Mobile Series 00", 1)]
|
||||
[TestCase("[HorribleRips] Mobile Series 00 S1 [1080p]", "Mobile Series 00", 1)]
|
||||
public void should_parse_full_season_release(string postTitle, string title, int season)
|
||||
{
|
||||
var result = Parser.Parser.ParseTitle(postTitle);
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("[www.test-hyphen.ca] - Series (2011) S01", "Series (2011)")]
|
||||
[TestCase("test123.ca - Series Time S02 720p HDTV x264 CRON", "Series Time")]
|
||||
[TestCase("[www.test-hyphen123.co.za] - Series Title S01E01", "Series Title")]
|
||||
[TestCase("(seriesawake.com) Series Super - 57 [720p] [English Subbed]", "Series Super")]
|
||||
|
||||
public void should_not_parse_url_in_name(string postTitle, string title)
|
||||
{
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace NzbDrone.Core.Test.QueueTests
|
||||
|
||||
_trackedDownloads = Builder<TrackedDownload>.CreateListOfSize(1)
|
||||
.All()
|
||||
.With(v => v.IsTrackable = true)
|
||||
.With(v => v.DownloadItem = downloadItem)
|
||||
.With(v => v.RemoteEpisode = remoteEpisode)
|
||||
.Build()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace NzbDrone.Core.Annotations
|
||||
@@ -59,13 +60,27 @@ namespace NzbDrone.Core.Annotations
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
public class FieldSelectOption
|
||||
public class FieldSelectOption<T>
|
||||
where T : struct
|
||||
{
|
||||
public int Value { get; set; }
|
||||
public T Value { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string Hint { get; set; }
|
||||
public int? ParentValue { get; set; }
|
||||
public T? ParentValue { get; set; }
|
||||
public bool? IsDisabled { get; set; }
|
||||
public Dictionary<string, object> AdditionalProperties { get; set; }
|
||||
}
|
||||
|
||||
public class FieldSelectStringOption
|
||||
{
|
||||
public string Value { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string Hint { get; set; }
|
||||
public string ParentValue { get; set; }
|
||||
public bool? IsDisabled { get; set; }
|
||||
public Dictionary<string, object> AdditionalProperties { get; set; }
|
||||
}
|
||||
|
||||
public enum FieldType
|
||||
|
||||
@@ -124,14 +124,23 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
}
|
||||
|
||||
var items = new List<DownloadClientItem>();
|
||||
var ignoredCount = 0;
|
||||
|
||||
foreach (var torrent in torrents)
|
||||
{
|
||||
if (torrent.Hash == null)
|
||||
// Silently ignore torrents with no hash
|
||||
if (torrent.Hash.IsNullOrWhiteSpace())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore torrents without a name, but track to log a single warning for all invalid torrents.
|
||||
if (torrent.Name.IsNullOrWhiteSpace())
|
||||
{
|
||||
ignoredCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var item = new DownloadClientItem();
|
||||
item.DownloadId = torrent.Hash.ToUpper();
|
||||
item.Title = torrent.Name;
|
||||
@@ -189,6 +198,11 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
if (ignoredCount > 0)
|
||||
{
|
||||
_logger.Warn("{0} torrent(s) were ignored becuase they did not have a title, check Deluge and remove any invalid torrents");
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,8 +64,8 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
SetImportItem(trackedDownload);
|
||||
|
||||
// Only process tracked downloads that are still downloading
|
||||
if (trackedDownload.State != TrackedDownloadState.Downloading)
|
||||
// Only process tracked downloads that are still downloading or have been blocked for importing due to an issue with matching
|
||||
if (trackedDownload.State != TrackedDownloadState.Downloading && trackedDownload.State != TrackedDownloadState.ImportBlocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Download
|
||||
if (series == null)
|
||||
{
|
||||
trackedDownload.Warn("Series title mismatch; automatic import is not possible. Check the download troubleshooting entry on the wiki for common causes.");
|
||||
SendManualInteractionRequiredNotification(trackedDownload);
|
||||
SetStateToImportBlocked(trackedDownload);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -108,7 +108,7 @@ namespace NzbDrone.Core.Download
|
||||
if (seriesMatchType == SeriesMatchType.Id && releaseSource != ReleaseSourceType.InteractiveSearch)
|
||||
{
|
||||
trackedDownload.Warn("Found matching series via grab history, but release was matched to series by ID. Automatic import is not possible. See the FAQ for details.");
|
||||
SendManualInteractionRequiredNotification(trackedDownload);
|
||||
SetStateToImportBlocked(trackedDownload);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -129,7 +129,7 @@ namespace NzbDrone.Core.Download
|
||||
if (trackedDownload.RemoteEpisode == null)
|
||||
{
|
||||
trackedDownload.Warn("Unable to parse download, automatic import is not possible.");
|
||||
SendManualInteractionRequiredNotification(trackedDownload);
|
||||
SetStateToImportBlocked(trackedDownload);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -187,7 +187,7 @@ namespace NzbDrone.Core.Download
|
||||
if (statusMessages.Any())
|
||||
{
|
||||
trackedDownload.Warn(statusMessages.ToArray());
|
||||
SendManualInteractionRequiredNotification(trackedDownload);
|
||||
SetStateToImportBlocked(trackedDownload);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,8 +254,10 @@ namespace NzbDrone.Core.Download
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SendManualInteractionRequiredNotification(TrackedDownload trackedDownload)
|
||||
private void SetStateToImportBlocked(TrackedDownload trackedDownload)
|
||||
{
|
||||
trackedDownload.State = TrackedDownloadState.ImportBlocked;
|
||||
|
||||
if (!trackedDownload.HasNotifiedManualInteractionRequired)
|
||||
{
|
||||
var grabbedHistories = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId).Where(h => h.EventType == EpisodeHistoryEventType.Grabbed).ToList();
|
||||
|
||||
@@ -73,8 +73,8 @@ namespace NzbDrone.Core.Download
|
||||
|
||||
public void Check(TrackedDownload trackedDownload)
|
||||
{
|
||||
// Only process tracked downloads that are still downloading
|
||||
if (trackedDownload.State != TrackedDownloadState.Downloading)
|
||||
// Only process tracked downloads that are still downloading or import is blocked (if they fail after attempting to be processed)
|
||||
if (trackedDownload.State != TrackedDownloadState.Downloading && trackedDownload.State != TrackedDownloadState.ImportBlocked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
_trackedDownloadService.TrackDownload((DownloadClientDefinition)downloadClient.Definition,
|
||||
downloadItem);
|
||||
|
||||
if (trackedDownload != null && trackedDownload.State == TrackedDownloadState.Downloading)
|
||||
if (trackedDownload is { State: TrackedDownloadState.Downloading or TrackedDownloadState.ImportBlocked })
|
||||
{
|
||||
_failedDownloadService.Check(trackedDownload);
|
||||
_completedDownloadService.Check(trackedDownload);
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
public enum TrackedDownloadState
|
||||
{
|
||||
Downloading,
|
||||
ImportBlocked,
|
||||
ImportPending,
|
||||
Importing,
|
||||
Imported,
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
using NzbDrone.Core.Instrumentation;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Instrumentation;
|
||||
|
||||
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||
{
|
||||
public class TrimLogDatabase : IHousekeepingTask
|
||||
{
|
||||
private readonly ILogRepository _logRepo;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
|
||||
public TrimLogDatabase(ILogRepository logRepo)
|
||||
public TrimLogDatabase(ILogRepository logRepo, IConfigFileProvider configFileProvider)
|
||||
{
|
||||
_logRepo = logRepo;
|
||||
_configFileProvider = configFileProvider;
|
||||
}
|
||||
|
||||
public void Clean()
|
||||
{
|
||||
if (!_configFileProvider.LogDbEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logRepo.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
public static class NewznabCategoryFieldOptionsConverter
|
||||
{
|
||||
public static List<FieldSelectOption> GetFieldSelectOptions(List<NewznabCategory> categories)
|
||||
public static List<FieldSelectOption<int>> GetFieldSelectOptions(List<NewznabCategory> categories)
|
||||
{
|
||||
// Categories not relevant for Sonarr
|
||||
var ignoreCategories = new[] { 1000, 3000, 4000, 6000, 7000 };
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
// And maybe relevant for specific users
|
||||
var unimportantCategories = new[] { 0, 2000 };
|
||||
|
||||
var result = new List<FieldSelectOption>();
|
||||
var result = new List<FieldSelectOption<int>>();
|
||||
|
||||
if (categories == null)
|
||||
{
|
||||
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
foreach (var category in categories.Where(cat => !ignoreCategories.Contains(cat.Id)).OrderBy(cat => unimportantCategories.Contains(cat.Id)).ThenBy(cat => cat.Id))
|
||||
{
|
||||
result.Add(new FieldSelectOption
|
||||
result.Add(new FieldSelectOption<int>
|
||||
{
|
||||
Value = category.Id,
|
||||
Name = category.Name,
|
||||
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
foreach (var subcat in category.Subcategories.OrderBy(cat => cat.Id))
|
||||
{
|
||||
result.Add(new FieldSelectOption
|
||||
result.Add(new FieldSelectOption<int>
|
||||
{
|
||||
Value = subcat.Id,
|
||||
Name = subcat.Name,
|
||||
|
||||
@@ -68,16 +68,17 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
|
||||
{
|
||||
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
|
||||
|
||||
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
|
||||
{
|
||||
if (enclosureTypes.Intersect(TorrentEnclosureMimeTypes).Any())
|
||||
{
|
||||
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Torznab indexer?", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
|
||||
}
|
||||
|
||||
_logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, NzbEnclosureMimeType, enclosureTypes[0]);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -262,26 +262,26 @@ namespace NzbDrone.Core.Indexers
|
||||
protected virtual RssEnclosure[] GetEnclosures(XElement item)
|
||||
{
|
||||
var enclosures = item.Elements("enclosure")
|
||||
.Select(v =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return new RssEnclosure
|
||||
{
|
||||
Url = v.Attribute("url")?.Value,
|
||||
Type = v.Attribute("type")?.Value,
|
||||
Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warn(e, "Failed to get enclosure for: {0}", item.Title());
|
||||
}
|
||||
.Select(v =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return new RssEnclosure
|
||||
{
|
||||
Url = v.Attribute("url")?.Value,
|
||||
Type = v.Attribute("type")?.Value,
|
||||
Length = v.Attribute("length")?.Value?.ParseInt64() ?? 0
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "Failed to get enclosure for: {0}", item.Title());
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.Where(v => v != null)
|
||||
.ToArray();
|
||||
return null;
|
||||
})
|
||||
.Where(v => v != null)
|
||||
.ToArray();
|
||||
|
||||
return enclosures;
|
||||
}
|
||||
|
||||
@@ -59,16 +59,17 @@ namespace NzbDrone.Core.Indexers.Torznab
|
||||
protected override bool PostProcess(IndexerResponse indexerResponse, List<XElement> items, List<ReleaseInfo> releases)
|
||||
{
|
||||
var enclosureTypes = items.SelectMany(GetEnclosures).Select(v => v.Type).Distinct().ToArray();
|
||||
|
||||
if (enclosureTypes.Any() && enclosureTypes.Intersect(PreferredEnclosureMimeTypes).Empty())
|
||||
{
|
||||
if (enclosureTypes.Intersect(UsenetEnclosureMimeTypes).Any())
|
||||
{
|
||||
_logger.Warn("{0} does not contain {1}, found {2}, did you intend to add a Newznab indexer?", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Warn("{1} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
|
||||
}
|
||||
|
||||
_logger.Warn("{0} does not contain {1}, found {2}.", indexerResponse.Request.Url, TorrentEnclosureMimeType, enclosureTypes[0]);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1361,6 +1361,8 @@
|
||||
"NotificationsNtfyValidationAuthorizationRequired": "Authorization is required",
|
||||
"NotificationsPlexSettingsAuthToken": "Auth Token",
|
||||
"NotificationsPlexSettingsAuthenticateWithPlexTv": "Authenticate with Plex.tv",
|
||||
"NotificationsPlexSettingsServer": "Server",
|
||||
"NotificationsPlexSettingsServerHelpText": "Select server from plex.tv account after authenticating",
|
||||
"NotificationsPlexValidationNoTvLibraryFound": "At least one TV library is required",
|
||||
"NotificationsPushBulletSettingSenderId": "Sender ID",
|
||||
"NotificationsPushBulletSettingSenderIdHelpText": "The device ID to send notifications from, use device_iden in the device's URL on pushbullet.com (leave blank to send from yourself)",
|
||||
@@ -1992,6 +1994,7 @@
|
||||
"Umask770Description": "{octal} - Owner & Group write",
|
||||
"Umask775Description": "{octal} - Owner & Group write, Other read",
|
||||
"Umask777Description": "{octal} - Everyone write",
|
||||
"UnableToImportAutomatically": "Unable to Import Automatically",
|
||||
"UnableToLoadAutoTagging": "Unable to load auto tagging",
|
||||
"UnableToLoadBackups": "Unable to load backups",
|
||||
"UnableToUpdateSonarrDirectly": "Unable to update {appName} directly,",
|
||||
|
||||
@@ -2078,5 +2078,8 @@
|
||||
"TomorrowAt": "Mañana a las {time}",
|
||||
"YesterdayAt": "Ayer a las {time}",
|
||||
"TodayAt": "Hoy a las {time}",
|
||||
"DayOfWeekAt": "{day} a las {time}"
|
||||
"DayOfWeekAt": "{day} a las {time}",
|
||||
"UnableToImportAutomatically": "No se pudo importar automáticamente",
|
||||
"NotificationsPlexSettingsServer": "Servidor",
|
||||
"NotificationsPlexSettingsServerHelpText": "Selecciona el servidor desde una cuenta de plex.tv después de autenticarse"
|
||||
}
|
||||
|
||||
@@ -1779,7 +1779,7 @@
|
||||
"NotificationsPushoverSettingsDevicesHelpText": "Liste des noms des appareils (laisser vide pour envoyer à tous les appareils)",
|
||||
"NotificationsPushoverSettingsDevices": "Appareils",
|
||||
"NotificationsPushcutSettingsTimeSensitiveHelpText": "Activer pour marquer la notification comme « Time Sensitive »",
|
||||
"NotificationsPushcutSettingsTimeSensitive": "Time Sensitive",
|
||||
"NotificationsPushcutSettingsTimeSensitive": "Sensible au temps",
|
||||
"NotificationsPushcutSettingsNotificationNameHelpText": "Nom de la notification de l'onglet Notifications de l'app Pushcut",
|
||||
"NotificationsPushcutSettingsNotificationName": "Nom de la notification",
|
||||
"NotificationsPushcutSettingsApiKeyHelpText": "Les clés API peuvent être gérées dans la vue Compte de l'app Pushcut",
|
||||
@@ -2071,5 +2071,13 @@
|
||||
"NotificationsTelegramSettingsIncludeAppName": "Inclure {appName} dans le Titre",
|
||||
"NotificationsTelegramSettingsIncludeAppNameHelpText": "Préfixer éventuellement le titre du message par {appName} pour différencier les notifications des différentes applications",
|
||||
"IndexerSettingsMultiLanguageRelease": "Multilingue",
|
||||
"IndexerSettingsMultiLanguageReleaseHelpText": "Quelles langues sont normalement présentes dans une version multiple de l'indexeur ?"
|
||||
"IndexerSettingsMultiLanguageReleaseHelpText": "Quelles langues sont normalement présentes dans une version multiple de l'indexeur ?",
|
||||
"DownloadClientQbittorrentTorrentStateMissingFiles": "qBittorrent signale des fichiers manquants",
|
||||
"BlocklistFilterHasNoItems": "La liste de blocage sélectionnée ne contient aucun élément",
|
||||
"HasUnmonitoredSeason": "A une saison non surveillée",
|
||||
"YesterdayAt": "Hier à {time}",
|
||||
"UnableToImportAutomatically": "Impossible d'importer automatiquement",
|
||||
"DayOfWeekAt": "{day} à {time}",
|
||||
"TomorrowAt": "Demain à {time}",
|
||||
"TodayAt": "Aujourd'hui à {time}"
|
||||
}
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
{}
|
||||
{
|
||||
"About": "के बारे में",
|
||||
"Absolute": "पूर्ण"
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"ApplyChanges": "Applica Cambiamenti",
|
||||
"ApplyTags": "Applica Etichette",
|
||||
"BackupNow": "Esegui backup ora",
|
||||
"Backups": "Backups",
|
||||
"Backups": "Backup",
|
||||
"Blocklist": "Lista dei Blocchi",
|
||||
"Activity": "Attività",
|
||||
"About": "Info",
|
||||
@@ -64,7 +64,7 @@
|
||||
"BeforeUpdate": "Prima dell'aggiornamento",
|
||||
"CalendarFeed": "Feed calendario {appName}",
|
||||
"CalendarOptions": "Opzioni del Calendario",
|
||||
"ChooseImportMode": "Selezionare Metodo di Importazione",
|
||||
"ChooseImportMode": "Seleziona Metodo di Importazione",
|
||||
"CollapseMultipleEpisodes": "Collassa Episodi Multipli",
|
||||
"Conditions": "Condizioni",
|
||||
"Continuing": "In Corso",
|
||||
@@ -250,5 +250,178 @@
|
||||
"AutoRedownloadFailed": "Download fallito",
|
||||
"AddDelayProfileError": "Impossibile aggiungere un nuovo profilo di ritardo, riprova.",
|
||||
"Cutoff": "Taglio",
|
||||
"AddListExclusion": "Aggiungi elenco esclusioni"
|
||||
"AddListExclusion": "Aggiungi Lista esclusioni",
|
||||
"DownloadClientValidationApiKeyRequired": "API Key Richiesta",
|
||||
"Donate": "Dona",
|
||||
"DownloadClientDownloadStationValidationNoDefaultDestination": "Nessuna destinazione predefinita",
|
||||
"ImportListSettings": "Impostazioni delle Liste",
|
||||
"DownloadClientFreeboxSettingsAppId": "App ID",
|
||||
"DownloadClientFreeboxSettingsAppToken": "App Token",
|
||||
"DownloadClientPneumaticSettingsNzbFolderHelpText": "Questa cartella dovrà essere raggiungibile da XBMC",
|
||||
"DownloadClientPneumaticSettingsNzbFolder": "Cartella Nzb",
|
||||
"DownloadClientSabnzbdValidationEnableDisableDateSorting": "Disattiva ordinamento per data",
|
||||
"DownloadClientQbittorrentValidationCategoryUnsupported": "Categoria non è supportata",
|
||||
"DownloadClientValidationCategoryMissing": "Categoria non esiste",
|
||||
"DownloadClientRTorrentSettingsUrlPath": "Percorso Url",
|
||||
"Default": "Predefinito",
|
||||
"DownloadClientFreeboxSettingsApiUrl": "API URL",
|
||||
"DownloadClientQbittorrentValidationCategoryRecommended": "Categoria è raccomandata",
|
||||
"Discord": "Discord",
|
||||
"DownloadClientDownloadStationValidationFolderMissing": "Cartella non esiste",
|
||||
"DownloadClientValidationAuthenticationFailure": "Autenticazione Fallita",
|
||||
"DownloadClientDownloadStationValidationFolderMissingDetail": "La cartella '{downloadDir}' non esiste, deve essere creata manualmente all'interno della Cartella Condivisa '{sharedFolder}'.",
|
||||
"DownloadClientSabnzbdValidationUnknownVersion": "Versione sconosciuta: {rawVersion}",
|
||||
"DownloadClientValidationVerifySsl": "Verifica impostazioni SSL",
|
||||
"ChangeCategory": "Cambia Categoria",
|
||||
"DownloadClientPneumaticSettingsStrmFolder": "Cartella Strm",
|
||||
"Destination": "Destinazione",
|
||||
"DownloadClientDownloadStationValidationSharedFolderMissing": "Cartella condivisa non esiste",
|
||||
"DownloadClientQbittorrentValidationCategoryRecommendedDetail": "{appName} non tenterà di importare i download completati senza una categoria.",
|
||||
"DownloadClientValidationGroupMissing": "Gruppo non esistente",
|
||||
"DownloadWarning": "Avviso di download: {warningMessage}",
|
||||
"IndexerSettingsAdditionalParameters": "Parametri Addizionali",
|
||||
"IndexerSettingsCookie": "Cookie",
|
||||
"BlackholeWatchFolderHelpText": "Cartella da cui {appName} dovrebbe importare i download completati",
|
||||
"NotificationsEmailSettingsServer": "Server",
|
||||
"NotificationsNtfySettingsPasswordHelpText": "Password opzionale",
|
||||
"NotificationsPlexSettingsAuthenticateWithPlexTv": "Autentica con Plex.tv",
|
||||
"NotificationsPushcutSettingsNotificationName": "Nome Notifica",
|
||||
"NotificationsTraktSettingsExpires": "Scadenze",
|
||||
"NotificationsTraktSettingsAuthenticateWithTrakt": "Autentica con Trakt",
|
||||
"NotificationsValidationUnableToConnectToApi": "Impossibile connettersi alle API di {service}. Connessione al server fallita: ({responseCode}) {exceptionMessage}",
|
||||
"InteractiveImportNoFilesFound": "Nessun video trovato nella castella selezionata",
|
||||
"Or": "o",
|
||||
"ManageLists": "Gestisci Liste",
|
||||
"OriginalLanguage": "Lingua Originale",
|
||||
"OverrideGrabNoQuality": "Qualità deve essere selezionata",
|
||||
"XmlRpcPath": "Percorso XML RPC",
|
||||
"WouldYouLikeToRestoreBackup": "Vuoi ripristinare il backup '{name}'?",
|
||||
"PendingDownloadClientUnavailable": "In Attesa - Client di Download in attesa",
|
||||
"PreviouslyInstalled": "Precedentemente Installato",
|
||||
"MissingLoadError": "Errore caricando elementi mancanti",
|
||||
"MonitorSelected": "Monitora Selezionati",
|
||||
"Period": "Periodo",
|
||||
"RemoveFailedDownloads": "Rimuovi Download Falliti",
|
||||
"RemoveMultipleFromDownloadClientHint": "Rimuovi i download e i file dal client di download",
|
||||
"RemoveSelectedItemQueueMessageText": "Sei sicuro di voler rimuovere 1 elemento dalla coda?",
|
||||
"TagDetails": "Dettagli Etichetta - {label}",
|
||||
"BranchUpdate": "Branca da usare per aggiornare {appName}",
|
||||
"DefaultNotFoundMessage": "Ti devi essere perso, non c'è nulla da vedere qui.",
|
||||
"DeleteIndexerMessageText": "Sicuro di voler eliminare l'indicizzatore '{name}'?",
|
||||
"Socks5": "Socks5 (Supporto TOR)",
|
||||
"DeleteEpisodeFileMessage": "Sei sicuro di volere eliminare '{path}'?",
|
||||
"NotificationsKodiSettingsCleanLibraryHelpText": "Pulisci libreria dopo l'aggiornamento",
|
||||
"PreferProtocol": "Preferisci {preferredProtocol}",
|
||||
"RetryingDownloadOn": "Riprovando il download il {date} alle {time}",
|
||||
"DeleteNotificationMessageText": "Sei sicuro di voler eliminare la notifica '{name}'?",
|
||||
"RemoveCompletedDownloads": "Rimuovi Download Completati",
|
||||
"DownloadClientFloodSettingsAdditionalTags": "Tag addizionali",
|
||||
"DelayingDownloadUntil": "Ritardare il download fino al {date} alle {time}",
|
||||
"DeleteDownloadClientMessageText": "Sei sicuro di voler eliminare il client di download '{name}'?",
|
||||
"NoHistoryFound": "Nessun storico trovato",
|
||||
"OneMinute": "1 Minuto",
|
||||
"OptionalName": "Nome opzionale",
|
||||
"DeleteSelectedIndexers": "Elimina Indicizzatore/i",
|
||||
"Branch": "Branca",
|
||||
"Debug": "Debug",
|
||||
"Never": "Mai",
|
||||
"UsenetDelayTime": "Ritardo Usenet: {usenetDelay}",
|
||||
"OrganizeModalHeader": "Organizza & Rinomina",
|
||||
"Parse": "Analizza",
|
||||
"RemoveFromDownloadClient": "Rimuovi dal client di download",
|
||||
"RemoveQueueItemConfirmation": "Sei sicuro di voler rimuovere '{sourceTitle}' dalla coda?",
|
||||
"NoIndexersFound": "Nessun indicizzatore trovato",
|
||||
"DeleteImportListMessageText": "Sei sicuro di volere eliminare la lista '{name}'?",
|
||||
"DeleteDelayProfile": "Elimina Profilo di Ritardo",
|
||||
"DeleteTagMessageText": "Sei sicuro di voler eliminare l'etichetta '{label}'?",
|
||||
"MinutesSixty": "60 Minuti: {sixty}",
|
||||
"NotificationsCustomScriptSettingsName": "Script personalizzato",
|
||||
"NotificationsCustomScriptValidationFileDoesNotExist": "Cartella non esiste",
|
||||
"Database": "Database",
|
||||
"NotificationsPushoverSettingsExpire": "Scadenza",
|
||||
"NotificationsSettingsWebhookMethod": "Metodo",
|
||||
"NotificationsSynologyValidationInvalidOs": "Deve essere un Synology",
|
||||
"NotificationsTraktSettingsRefreshToken": "Refresh Token",
|
||||
"CouldNotFindResults": "Nessun risultato trovato per '{term}'",
|
||||
"IndexerSettingsApiPath": "Percorso API",
|
||||
"AutoTaggingSpecificationMaximumYear": "Anno Massimo",
|
||||
"AutoTaggingSpecificationGenre": "Genere/i",
|
||||
"AutoTaggingSpecificationMinimumYear": "Anno Minimo",
|
||||
"AutoTaggingSpecificationOriginalLanguage": "Lingua",
|
||||
"AutoTaggingSpecificationQualityProfile": "Profilo Qualità",
|
||||
"AutoTaggingSpecificationRootFolder": "Cartella Radice",
|
||||
"AutoTaggingSpecificationStatus": "Stato",
|
||||
"CustomFormatsSpecificationLanguage": "Linguaggio",
|
||||
"CustomFormatsSpecificationMaximumSize": "Dimensione Massima",
|
||||
"CustomFormatsSpecificationMinimumSize": "Dimensione Minima",
|
||||
"DelayProfile": "Profilo di Ritardo",
|
||||
"DeleteBackupMessageText": "Sei sicuro di voler cancellare il backup '{name}'?",
|
||||
"DeleteDelayProfileMessageText": "Sei sicuro di volere eliminare questo profilo di ritardo?",
|
||||
"NotificationsTelegramSettingsSendSilentlyHelpText": "Invia il messaggio silenziosamente. L'utente riceverà una notifica senza suono",
|
||||
"NotificationsPushoverSettingsRetry": "Riprova",
|
||||
"NotificationsSettingsWebhookUrl": "URL Webhook",
|
||||
"PackageVersionInfo": "{packageVersion} di {packageAuthor}",
|
||||
"RemoveQueueItem": "Rimuovi - {sourceTitle}",
|
||||
"ParseModalErrorParsing": "Errore durante l'analisi, per favore prova di nuovo.",
|
||||
"OverrideGrabNoLanguage": "Almeno una lingua deve essere selezionata",
|
||||
"PasswordConfirmation": "Conferma Password",
|
||||
"RemoveSelectedItemsQueueMessageText": "Sei sicuro di voler rimuovere {selectedCount} elementi dalla coda?",
|
||||
"ConnectionLostToBackend": "{appName} ha perso la connessione al backend e dovrà essere ricaricato per ripristinare la funzionalità.",
|
||||
"CountIndexersSelected": "{count} indicizzatore(i) selezionato(i)",
|
||||
"CountDownloadClientsSelected": "{count} client di download selezionato/i",
|
||||
"PrioritySettings": "Priorità: {priority}",
|
||||
"OverrideAndAddToDownloadQueue": "Sovrascrivi e aggiungi alla coda di download",
|
||||
"NotificationsSettingsUseSslHelpText": "Connetti a {serviceName} tramite HTTPS indece di HTTP",
|
||||
"OrganizeRelativePaths": "Tutti i percorsi sono relativi a: `{path}`",
|
||||
"CurrentlyInstalled": "Attualmente Installato",
|
||||
"NotificationsEmailSettingsName": "Email",
|
||||
"NotificationsNtfySettingsServerUrl": "URL Server",
|
||||
"NotificationsPushoverSettingsSound": "Suono",
|
||||
"NotificationsSignalValidationSslRequired": "SSL sembra essere richiesto",
|
||||
"TorrentDelayTime": "Ritardo torrent: {torrentDelay}",
|
||||
"NotificationsTwitterSettingsMention": "Menziona",
|
||||
"NotificationsPushoverSettingsDevices": "Dispositivi",
|
||||
"NotificationsTelegramSettingsSendSilently": "Invia Silenziosamente",
|
||||
"DatabaseMigration": "Migrazione Database",
|
||||
"AutoTaggingSpecificationTag": "Etichetta",
|
||||
"CustomFormatUnknownConditionOption": "Opzione sconosciuta '{key}' per la condizione '{implementation}'",
|
||||
"CustomFormatsSpecificationResolution": "Risoluzione",
|
||||
"CustomFormatsSpecificationSource": "Fonte",
|
||||
"BlocklistAndSearch": "Lista dei Blocchi e Ricerca",
|
||||
"NotificationsEmbySettingsSendNotifications": "Invia Notifiche",
|
||||
"IndexerHDBitsSettingsMediumsHelpText": "Se non specificato, saranno utilizzate tutte le opzioni.",
|
||||
"DeleteQualityProfile": "Elimina Profilo Qualità",
|
||||
"DeleteSelectedEpisodeFiles": "Elimina i File degli Episodi Selezionati",
|
||||
"DeleteEpisodesFiles": "Elimina i File di {episodeFileCount} Episodi",
|
||||
"CustomFilter": "Filtro Personalizzato",
|
||||
"NotificationsTelegramSettingsIncludeAppName": "Includi {appName} nel Titolo",
|
||||
"IndexerHDBitsSettingsCodecsHelpText": "Se non specificato, saranno utilizzate tutte le opzioni.",
|
||||
"NotificationsGotifySettingsAppToken": "App Token",
|
||||
"InfoUrl": "URL Info",
|
||||
"ConnectionLostReconnect": "{appName} cercherà di connettersi automaticamente, oppure clicca su ricarica qui sotto.",
|
||||
"ListWillRefreshEveryInterval": "Le liste verranno aggiornate ogni {refreshInterval}",
|
||||
"NotificationsNtfySettingsServerUrlHelpText": "Lascia vuoto per usare il server pubblico {url}",
|
||||
"NotificationsTwitterSettingsMentionHelpText": "Menziona questo utente nei tweet inviati",
|
||||
"NotificationsValidationUnableToSendTestMessageApiResponse": "Impossibile inviare messaggio di prova. Risposta dalle API: {error}",
|
||||
"RemoveFromDownloadClientHint": "Rimuovi il download e i file dal client di download",
|
||||
"DayOfWeekAt": "{day} alle {time}",
|
||||
"DeleteRootFolder": "Elimina Cartella Radice",
|
||||
"DeleteRootFolderMessageText": "Sei sicuro di volere eliminare la cartella radice '{path}'?",
|
||||
"ManageIndexers": "Gestisci Indicizzatori",
|
||||
"MissingNoItems": "Nessun elemento mancante",
|
||||
"NotificationsKodiSettingsCleanLibrary": "Pulisci Libreria",
|
||||
"NotificationsNtfySettingsUsernameHelpText": "Nome utente opzionale",
|
||||
"NotificationsSettingsUpdateLibrary": "Aggiorna Libreria",
|
||||
"NotificationsSlackSettingsChannel": "Canale",
|
||||
"NotificationsSlackSettingsIcon": "Icona",
|
||||
"NotificationsTelegramSettingsBotToken": "Token Bot",
|
||||
"NotificationsTwitterSettingsAccessToken": "Access Token",
|
||||
"NotificationsTwitterSettingsConnectToTwitter": "Connetti a Twitter / X",
|
||||
"NotificationsTwitterSettingsDirectMessage": "Messaggio Diretto",
|
||||
"NotificationsValidationInvalidUsernamePassword": "Nome Utente o password non validi",
|
||||
"NotificationsValidationUnableToConnect": "Impossibile connettersi: {exceptionMessage}",
|
||||
"NotificationsValidationUnableToConnectToService": "Impossibile connettersi a {serviceName}",
|
||||
"NotificationsValidationUnableToSendTestMessage": "Impossibile inviare messaggio di prova: {exceptionMessage}",
|
||||
"ThemeHelpText": "Cambia il Tema dell'interfaccia dell’applicazione, il Tema 'Auto' userà il suo Tema di Sistema per impostare la modalità Chiara o Scura. Ispirato da Theme.Park",
|
||||
"Torrents": "Torrents"
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
"Calendar": "Calendário",
|
||||
"Connect": "Conectar",
|
||||
"CustomFormats": "Formatos personalizados",
|
||||
"CutoffUnmet": "Limite não alcançado",
|
||||
"CutoffUnmet": "Corte Não Alcançado",
|
||||
"DownloadClients": "Clientes de download",
|
||||
"Events": "Eventos",
|
||||
"General": "Geral",
|
||||
@@ -494,7 +494,7 @@
|
||||
"CustomFormatsSettings": "Configurações de Formatos Personalizados",
|
||||
"CustomFormatsSettingsSummary": "Configurações e Formatos Personalizados",
|
||||
"DailyEpisodeFormat": "Formato do episódio diário",
|
||||
"Cutoff": "Limite",
|
||||
"Cutoff": "Corte",
|
||||
"Dash": "Traço",
|
||||
"Dates": "Datas",
|
||||
"Debug": "Depuração",
|
||||
@@ -2078,5 +2078,6 @@
|
||||
"TodayAt": "Hoje às {time}",
|
||||
"TomorrowAt": "Amanhã às {time}",
|
||||
"HasUnmonitoredSeason": "Tem Temporada Não Monitorada",
|
||||
"YesterdayAt": "Ontem às {time}"
|
||||
"YesterdayAt": "Ontem às {time}",
|
||||
"UnableToImportAutomatically": "Não foi possível importar automaticamente"
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"AppUpdated": "{appName} Güncellendi",
|
||||
"ApplicationURL": "Uygulama URL'si",
|
||||
"ApplyTagsHelpTextAdd": "Ekle: Etiketleri mevcut etiket listesine ekleyin",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Seçilen indeksleyicilere etiketler nasıl uygulanır?",
|
||||
"ApplyTagsHelpTextHowToApplyIndexers": "Seçilen indeksleyicilere etiketler nasıl uygulanır",
|
||||
"ApplyTagsHelpTextRemove": "Kaldır: Girilen etiketleri kaldırın",
|
||||
"AuthenticationRequiredPasswordHelpTextWarning": "Yeni şifre girin",
|
||||
"AuthenticationRequiredUsernameHelpTextWarning": "Yeni kullanıcı adınızı girin",
|
||||
@@ -88,8 +88,8 @@
|
||||
"CustomFormatUnknownConditionOption": "'{implementation}' koşulu için bilinmeyen seçenek '{key}'",
|
||||
"AutoTagging": "Otomatik Etiketleme",
|
||||
"AutoTaggingNegateHelpText": "İşaretlenirse, {implementationName} koşulu eşleştiğinde otomatik etiketleme kuralı uygulanmayacaktır.",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Seçilen indirme istemcilerine etiketler nasıl uygulanır?",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Seçilen içe aktarma listelerine etiketler nasıl uygulanır?",
|
||||
"ApplyTagsHelpTextHowToApplyDownloadClients": "Seçilen indirme istemcilerine etiketler nasıl uygulanır",
|
||||
"ApplyTagsHelpTextHowToApplyImportLists": "Seçilen içe aktarma listelerine etiketler nasıl uygulanır",
|
||||
"AuthenticationRequiredHelpText": "İstekler için Kimlik doğrulamanın gereklilik ayarını değiştirin. Riskleri anlamadığınız sürece değiştirmeyin.",
|
||||
"AutoTaggingLoadError": "Otomatik etiketleme yüklenemiyor",
|
||||
"BypassDelayIfAboveCustomFormatScore": "Özel Format Koşullarının Üstündeyse Baypas Et",
|
||||
@@ -837,5 +837,15 @@
|
||||
"UpdateAutomaticallyHelpText": "Güncelleştirmeleri otomatik olarak indirip yükleyin. Sistem: Güncellemeler'den yükleme yapmaya devam edebileceksiniz",
|
||||
"Wanted": "Arananlar",
|
||||
"Cutoff": "Kesinti",
|
||||
"Required": "Gerekli"
|
||||
"Required": "Gerekli",
|
||||
"AirsTbaOn": "Daha sonra duyurulacak {networkLabel}'de",
|
||||
"AllFiles": "Tüm dosyalar",
|
||||
"AllSeriesAreHiddenByTheAppliedFilter": "Tüm sonuçlar, uygulanan filtre tarafından gizlenir",
|
||||
"Always": "Her zaman",
|
||||
"AirsDateAtTimeOn": "{date} saat {time} {networkLabel}'de",
|
||||
"AllResultsAreHiddenByTheAppliedFilter": "Tüm sonuçlar, uygulanan filtre tarafından gizlenir",
|
||||
"AllSeriesInRootFolderHaveBeenImported": "{path} içerisindeki tüm diziler içeri aktarıldı",
|
||||
"AlternateTitles": "Alternatif Başlıklar",
|
||||
"AnEpisodeIsDownloading": "Bir bölüm indiriliyor",
|
||||
"UnableToImportAutomatically": "Otomatikman İçe Aktarılamıyor"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -12,6 +13,7 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
{
|
||||
string GetAuthToken(string clientIdentifier, int pinId);
|
||||
bool Ping(string clientIdentifier, string authToken);
|
||||
List<PlexTvResource> GetResources(string clientIdentifier, string authToken);
|
||||
}
|
||||
|
||||
public class PlexTvProxy : IPlexTvProxy
|
||||
@@ -62,6 +64,33 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<PlexTvResource> GetResources(string clientIdentifier, string authToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Allows us to tell plex.tv that we're still active and tokens should not be expired.
|
||||
|
||||
var request = BuildRequest(clientIdentifier);
|
||||
|
||||
request.ResourceUrl = "/api/v2/resources";
|
||||
request.AddQueryParam("includeHttps", 1);
|
||||
request.AddQueryParam("clientID", clientIdentifier);
|
||||
request.AddQueryParam("X-Plex-Token", authToken);
|
||||
|
||||
if (Json.TryDeserialize<List<PlexTvResource>>(ProcessRequest(request), out var response))
|
||||
{
|
||||
return response;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Catch all exceptions and log at trace, this information could be interesting in debugging, but expired tokens will be handled elsewhere.
|
||||
_logger.Trace(e, "Unable to ping plex.tv");
|
||||
}
|
||||
|
||||
return new List<PlexTvResource>();
|
||||
}
|
||||
|
||||
private HttpRequestBuilder BuildRequest(string clientIdentifier)
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder("https://plex.tv")
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
{
|
||||
public class PlexTvResource
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool Owned { get; set; }
|
||||
|
||||
public List<PlexTvResourceConnection> Connections { get; set; }
|
||||
|
||||
[JsonProperty("provides")]
|
||||
public string ProvidesRaw { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<string> Provides => ProvidesRaw.Split(",").ToList();
|
||||
}
|
||||
|
||||
public class PlexTvResourceConnection
|
||||
{
|
||||
public string Uri { get; set; }
|
||||
public string Protocol { get; set; }
|
||||
public string Address { get; set; }
|
||||
public int Port { get; set; }
|
||||
public bool Local { get; set; }
|
||||
public string Host => Uri.IsNullOrWhiteSpace() ? Address : new Uri(Uri).Host;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using NzbDrone.Common.Cache;
|
||||
@@ -14,6 +15,7 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
PlexTvSignInUrlResponse GetSignInUrl(string callbackUrl, int pinId, string pinCode);
|
||||
string GetAuthToken(int pinId);
|
||||
void Ping(string authToken);
|
||||
List<PlexTvResource> GetServers(string authToken);
|
||||
HttpRequest GetWatchlist(string authToken, int pageSize, int pageOffset);
|
||||
}
|
||||
|
||||
@@ -93,6 +95,16 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
_cache.Get(authToken, () => _proxy.Ping(_configService.PlexClientIdentifier, authToken), TimeSpan.FromHours(24));
|
||||
}
|
||||
|
||||
public List<PlexTvResource> GetServers(string authToken)
|
||||
{
|
||||
Ping(authToken);
|
||||
|
||||
var clientIdentifier = _configService.PlexClientIdentifier;
|
||||
var resources = _proxy.GetResources(clientIdentifier, authToken);
|
||||
|
||||
return resources.Where(r => r.Owned && r.Provides.Contains("server")).ToList();
|
||||
}
|
||||
|
||||
public HttpRequest GetWatchlist(string authToken, int pageSize, int pageOffset)
|
||||
{
|
||||
Ping(authToken);
|
||||
|
||||
@@ -5,6 +5,7 @@ using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Notifications.Plex.PlexTv;
|
||||
@@ -193,6 +194,79 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
};
|
||||
}
|
||||
|
||||
if (action == "servers")
|
||||
{
|
||||
Settings.Validate().Filter("AuthToken").ThrowOnError();
|
||||
|
||||
if (Settings.AuthToken.IsNullOrWhiteSpace())
|
||||
{
|
||||
return new { };
|
||||
}
|
||||
|
||||
var servers = _plexTvService.GetServers(Settings.AuthToken);
|
||||
var options = servers.SelectMany(s =>
|
||||
{
|
||||
var result = new List<FieldSelectStringOption>();
|
||||
|
||||
// result.Add(new FieldSelectStringOption
|
||||
// {
|
||||
// Value = s.Name,
|
||||
// Name = s.Name,
|
||||
// IsDisabled = true
|
||||
// });
|
||||
|
||||
s.Connections.ForEach(c =>
|
||||
{
|
||||
var isSecure = c.Protocol == "https";
|
||||
var additionalProperties = new Dictionary<string, object>();
|
||||
var hints = new List<string>();
|
||||
|
||||
additionalProperties.Add("host", c.Host);
|
||||
additionalProperties.Add("port", c.Port);
|
||||
additionalProperties.Add("useSsl", isSecure);
|
||||
hints.Add(c.Local ? "Local" : "Remote");
|
||||
|
||||
if (isSecure)
|
||||
{
|
||||
hints.Add("Secure");
|
||||
}
|
||||
|
||||
result.Add(new FieldSelectStringOption
|
||||
{
|
||||
Value = c.Uri,
|
||||
Name = $"{s.Name} ({c.Host})",
|
||||
Hint = string.Join(", ", hints),
|
||||
AdditionalProperties = additionalProperties
|
||||
});
|
||||
|
||||
if (isSecure)
|
||||
{
|
||||
var uri = $"http://{c.Address}:{c.Port}";
|
||||
var insecureAdditionalProperties = new Dictionary<string, object>();
|
||||
|
||||
insecureAdditionalProperties.Add("host", c.Address);
|
||||
insecureAdditionalProperties.Add("port", c.Port);
|
||||
insecureAdditionalProperties.Add("useSsl", false);
|
||||
|
||||
result.Add(new FieldSelectStringOption
|
||||
{
|
||||
Value = uri,
|
||||
Name = $"{s.Name} ({c.Address})",
|
||||
Hint = c.Local ? "Local" : "Remote",
|
||||
AdditionalProperties = insecureAdditionalProperties
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
return new
|
||||
{
|
||||
options
|
||||
};
|
||||
}
|
||||
|
||||
return new { };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
@@ -22,40 +23,45 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
|
||||
public PlexServerSettings()
|
||||
{
|
||||
Host = "";
|
||||
Port = 32400;
|
||||
UpdateLibrary = true;
|
||||
SignIn = "startOAuth";
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "Host")]
|
||||
[JsonIgnore]
|
||||
[FieldDefinition(0, Label = "NotificationsPlexSettingsServer", Type = FieldType.Select, SelectOptionsProviderAction = "servers", HelpText = "NotificationsPlexSettingsServerHelpText")]
|
||||
public string Server { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Host")]
|
||||
public string Host { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Port")]
|
||||
[FieldDefinition(2, Label = "Port")]
|
||||
public int Port { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "NotificationsSettingsUseSslHelpText")]
|
||||
[FieldDefinition(3, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "NotificationsSettingsUseSslHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "UseSsl", "serviceName", "Plex")]
|
||||
public bool UseSsl { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "ConnectionSettingsUrlBaseHelpText")]
|
||||
[FieldDefinition(4, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "ConnectionSettingsUrlBaseHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "UrlBase", "connectionName", "Plex")]
|
||||
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/plex")]
|
||||
public string UrlBase { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "NotificationsPlexSettingsAuthToken", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey, Advanced = true)]
|
||||
[FieldDefinition(5, Label = "NotificationsPlexSettingsAuthToken", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey, Advanced = true)]
|
||||
public string AuthToken { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "NotificationsPlexSettingsAuthenticateWithPlexTv", Type = FieldType.OAuth)]
|
||||
[FieldDefinition(6, Label = "NotificationsPlexSettingsAuthenticateWithPlexTv", Type = FieldType.OAuth)]
|
||||
public string SignIn { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "NotificationsSettingsUpdateLibrary", Type = FieldType.Checkbox)]
|
||||
[FieldDefinition(7, Label = "NotificationsSettingsUpdateLibrary", Type = FieldType.Checkbox)]
|
||||
public bool UpdateLibrary { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "NotificationsSettingsUpdateMapPathsFrom", Type = FieldType.Textbox, Advanced = true, HelpText = "NotificationsSettingsUpdateMapPathsFromHelpText")]
|
||||
[FieldDefinition(8, Label = "NotificationsSettingsUpdateMapPathsFrom", Type = FieldType.Textbox, Advanced = true, HelpText = "NotificationsSettingsUpdateMapPathsFromHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "NotificationsSettingsUpdateMapPathsFrom", "serviceName", "Plex")]
|
||||
public string MapFrom { get; set; }
|
||||
|
||||
[FieldDefinition(8, Label = "NotificationsSettingsUpdateMapPathsTo", Type = FieldType.Textbox, Advanced = true, HelpText = "NotificationsSettingsUpdateMapPathsToHelpText")]
|
||||
[FieldDefinition(9, Label = "NotificationsSettingsUpdateMapPathsTo", Type = FieldType.Textbox, Advanced = true, HelpText = "NotificationsSettingsUpdateMapPathsToHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "NotificationsSettingsUpdateMapPathsTo", "serviceName", "Plex")]
|
||||
public string MapTo { get; set; }
|
||||
|
||||
|
||||
@@ -126,6 +126,10 @@ namespace NzbDrone.Core.Parser
|
||||
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)(?:(?<!\b[0]\d+))(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+|[-]))[. ]-[. ](?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+|[-]))(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>[(\[]\w{8}[)\]])?(?:$|\.mkv)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
// Anime - [SubGroup] Title with trailing number S## (Full season)
|
||||
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)[-_. ]+(?:S(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))).+?(?:$|\.mkv)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
|
||||
// Anime - [SubGroup] Title Absolute Episode Number
|
||||
new Regex(@"^\[(?<subgroup>.+?)\][-_. ]?(?<title>.+?)[-_. ]+\(?(?:[-_. ]?#?(?<absoluteepisode>\d{2,3}(\.\d{1,2})?(?!\d+|-[a-z]+)))+\)?(?:[-_. ]+(?<special>special|ova|ovd))?.*?(?<hash>[(\[]\w{8}[)\]])?(?:$|\.mkv)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||
@@ -510,7 +514,7 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
// Valid TLDs http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
||||
|
||||
private static readonly RegexReplace WebsitePrefixRegex = new RegexReplace(@"^(?:\[\s*)?(?:www\.)?[-a-z0-9-]{1,256}\.(?<!Naruto-Kun\.)(?:[a-z]{2,6}\.[a-z]{2,6}|xn--[a-z0-9-]{4,}|[a-z]{2,})\b(?:\s*\]|[ -]{2,})[ -]*",
|
||||
private static readonly RegexReplace WebsitePrefixRegex = new RegexReplace(@"^(?:(?:\[|\()\s*)?(?:www\.)?[-a-z0-9-]{1,256}\.(?<!Naruto-Kun\.)(?:[a-z]{2,6}\.[a-z]{2,6}|xn--[a-z0-9-]{4,}|[a-z]{2,})\b(?:\s*(?:\]|\))|[ -]{2,})[ -]*",
|
||||
string.Empty,
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
@@ -1205,7 +1209,7 @@ namespace NzbDrone.Core.Parser
|
||||
}
|
||||
}
|
||||
|
||||
if (lastSeasonEpisodeStringIndex != releaseTitle.Length)
|
||||
if (lastSeasonEpisodeStringIndex < releaseTitle.Length)
|
||||
{
|
||||
result.ReleaseTokens = releaseTitle.Substring(lastSeasonEpisodeStringIndex);
|
||||
}
|
||||
@@ -1285,7 +1289,7 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
private static int ParseNumber(string value)
|
||||
{
|
||||
var normalized = value.Normalize(NormalizationForm.FormKC);
|
||||
var normalized = ConvertToNumerals(value.Normalize(NormalizationForm.FormKC));
|
||||
|
||||
if (int.TryParse(normalized, out var number))
|
||||
{
|
||||
@@ -1304,7 +1308,7 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
private static decimal ParseDecimal(string value)
|
||||
{
|
||||
var normalized = value.Normalize(NormalizationForm.FormKC);
|
||||
var normalized = ConvertToNumerals(value.Normalize(NormalizationForm.FormKC));
|
||||
|
||||
if (decimal.TryParse(normalized, NumberStyles.Float, CultureInfo.InvariantCulture, out var number))
|
||||
{
|
||||
@@ -1313,5 +1317,24 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
throw new FormatException(string.Format("{0} isn't a number", value));
|
||||
}
|
||||
|
||||
private static string ConvertToNumerals(string input)
|
||||
{
|
||||
var result = new StringBuilder(input.Length);
|
||||
|
||||
foreach (var c in input.ToCharArray())
|
||||
{
|
||||
if (char.IsNumber(c))
|
||||
{
|
||||
result.Append(char.GetNumericValue(c));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.Queue
|
||||
public class QueueService : IQueueService, IHandle<TrackedDownloadRefreshedEvent>
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private static List<Queue> _queue = new List<Queue>();
|
||||
private static List<Queue> _queue = new ();
|
||||
|
||||
public QueueService(IEventAggregator eventAggregator)
|
||||
{
|
||||
@@ -96,8 +96,11 @@ namespace NzbDrone.Core.Queue
|
||||
|
||||
public void Handle(TrackedDownloadRefreshedEvent message)
|
||||
{
|
||||
_queue = message.TrackedDownloads.OrderBy(c => c.DownloadItem.RemainingTime).SelectMany(MapQueue)
|
||||
.ToList();
|
||||
_queue = message.TrackedDownloads
|
||||
.Where(t => t.IsTrackable)
|
||||
.OrderBy(c => c.DownloadItem.RemainingTime)
|
||||
.SelectMany(MapQueue)
|
||||
.ToList();
|
||||
|
||||
_eventAggregator.PublishEvent(new QueueUpdatedEvent());
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ namespace Sonarr.Api.V3.Episodes
|
||||
public bool UnverifiedSceneNumbering { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
public DateTime? GrabDate { get; set; }
|
||||
public string SeriesTitle { get; set; }
|
||||
public SeriesResource Series { get; set; }
|
||||
|
||||
public List<MediaCover> Images { get; set; }
|
||||
@@ -79,7 +78,6 @@ namespace Sonarr.Api.V3.Episodes
|
||||
SceneEpisodeNumber = model.SceneEpisodeNumber,
|
||||
SceneSeasonNumber = model.SceneSeasonNumber,
|
||||
UnverifiedSceneNumbering = model.UnverifiedSceneNumbering,
|
||||
SeriesTitle = model.SeriesTitle,
|
||||
|
||||
// Series = model.Series.MapToResource(),
|
||||
};
|
||||
|
||||
@@ -8483,10 +8483,6 @@
|
||||
"format": "date-time",
|
||||
"nullable": true
|
||||
},
|
||||
"seriesTitle": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"series": {
|
||||
"$ref": "#/components/schemas/SeriesResource"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user