Compare commits

..

No commits in common. "develop" and "v2.10.0.4574" have entirely different histories.

162 changed files with 678 additions and 2311 deletions

View file

@ -60,7 +60,6 @@ body:
- Master
- Develop
- Nightly
- Plugins (experimental)
- Other (This issue will be closed)
validations:
required: true

28
.gitignore vendored
View file

@ -158,12 +158,34 @@ Thumbs.db
/tools/Addins/*
packages.config.md5sum
# Common IntelliJ Platform excludes
# User specific
**/.idea/**/workspace.xml
**/.idea/**/tasks.xml
**/.idea/shelf/*
**/.idea/dictionaries
**/.idea/.idea.Radarr.Posix
**/.idea/.idea.Radarr.Windows
# Sensitive or high-churn files
**/.idea/**/dataSources/
**/.idea/**/dataSources.ids
**/.idea/**/dataSources.xml
**/.idea/**/dataSources.local.xml
**/.idea/**/sqlDataSources.xml
**/.idea/**/dynamic.xml
# Rider
# Rider auto-generates .iml files, and contentModel.xml
**/.idea/**/*.iml
**/.idea/**/contentModel.xml
**/.idea/**/modules.xml
# ignore node_modules symlink
node_modules
node_modules.nosync
# API doc generation
.config/
# Ignore Jetbrains IntelliJ Workspace Directories
.idea/

View file

@ -9,9 +9,6 @@
Lidarr is a music collection manager for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new tracks from your favorite artists and will grab, sort and rename them. It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available.
> [!WARNING]
> NOTICE - The Lidarr Metadata Server is recovering and rebuilding the cache which is impacting adding artists, library imports, etc. Please follow [GHI 5498](https://github.com/Lidarr/Lidarr/issues/5498) or see Discord for details.
## Major Features Include:
* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.

View file

@ -9,7 +9,7 @@ variables:
testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '2.13.3'
majorVersion: '2.10.0'
minorVersion: $[counter('minorVersion', 1076)]
lidarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(lidarrVersion)'
@ -19,7 +19,7 @@ variables:
nodeVersion: '20.X'
innoVersion: '6.2.0'
windowsImage: 'windows-2022'
linuxImage: 'ubuntu-22.04'
linuxImage: 'ubuntu-20.04'
macImage: 'macOS-13'
trigger:

View file

@ -188,7 +188,7 @@ module.exports = (env) => {
loose: true,
debug: false,
useBuiltIns: 'entry',
corejs: '3.41'
corejs: '3.39'
}
]
]

View file

@ -172,8 +172,7 @@ function HistoryDetails(props) {
if (eventType === 'downloadFailed') {
const {
message,
indexer
message
} = data;
return (
@ -193,14 +192,6 @@ function HistoryDetails(props) {
null
}
{
indexer ? (
<DescriptionListItem
title={translate('Indexer')}
data={indexer}
/>
) : null}
{
message ?
<DescriptionListItem

View file

@ -57,40 +57,30 @@ function QueueStatusCell(props) {
if (status === 'paused') {
iconName = icons.PAUSED;
title = translate('Paused');
title = 'Paused';
}
if (status === 'queued') {
iconName = icons.QUEUED;
title = translate('Queued');
title = 'Queued';
}
if (status === 'completed') {
iconName = icons.DOWNLOADED;
title = translate('Downloaded');
if (trackedDownloadState === 'importBlocked') {
title += ` - ${translate('UnableToImportAutomatically')}`;
iconKind = kinds.WARNING;
}
if (trackedDownloadState === 'importFailed') {
title += ` - ${translate('ImportFailed', { sourceTitle })}`;
iconKind = kinds.WARNING;
}
title = 'Downloaded';
if (trackedDownloadState === 'importPending') {
title += ` - ${translate('WaitingToImport')}`;
title += ' - Waiting to Import';
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'importing') {
title += ` - ${translate('Importing')}`;
title += ' - Importing';
iconKind = kinds.PURPLE;
}
if (trackedDownloadState === 'failedPending') {
title += ` - ${translate('WaitingToProcess')}`;
title += ' - Waiting to Process';
iconKind = kinds.DANGER;
}
}
@ -101,38 +91,36 @@ function QueueStatusCell(props) {
if (status === 'delay') {
iconName = icons.PENDING;
title = translate('Pending');
title = 'Pending';
}
if (status === 'downloadClientUnavailable') {
iconName = icons.PENDING;
iconKind = kinds.WARNING;
title = translate('PendingDownloadClientUnavailable');
title = 'Pending - Download client is unavailable';
}
if (status === 'failed') {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = translate('DownloadFailed');
title = 'Download failed';
}
if (status === 'warning') {
iconName = icons.DOWNLOADING;
iconKind = kinds.WARNING;
const warningMessage =
errorMessage || translate('CheckDownloadClientForDetails');
title = translate('DownloadWarning', { warningMessage });
title = `Download warning: ${errorMessage || 'check download client for more details'}`;
}
if (hasError) {
if (status === 'completed') {
iconName = icons.DOWNLOAD;
iconKind = kinds.DANGER;
title = translate('ImportFailed', { sourceTitle });
title = `Import failed: ${sourceTitle}`;
} else {
iconName = icons.DOWNLOADING;
iconKind = kinds.DANGER;
title = translate('DownloadFailed');
title = 'Download failed';
}
}

View file

@ -8,17 +8,17 @@ function ArtistMonitorNewItemsOptionsPopoverContent() {
<DescriptionList>
<DescriptionListItem
title={translate('AllAlbums')}
data={translate('MonitorAllAlbums')}
data="Monitor all new albums"
/>
<DescriptionListItem
title={translate('NewAlbums')}
data={translate('MonitorNewAlbumsData')}
data="Monitor new albums released after the newest existing album"
/>
<DescriptionListItem
title={translate('None')}
data={translate('MonitorNoAlbumsData')}
data="Don't monitor any new albums"
/>
</DescriptionList>
);

View file

@ -15,7 +15,7 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import Popover from 'Components/Tooltip/Popover';
import { icons, inputTypes, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import styles from './EditArtistModalContent.css';
@ -93,7 +93,7 @@ class EditArtistModalContent extends Component {
<ModalBody>
<Form {...otherProps}>
<FormGroup size={sizes.MEDIUM}>
<FormGroup>
<FormLabel>
{translate('Monitored')}
</FormLabel>
@ -107,10 +107,9 @@ class EditArtistModalContent extends Component {
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormGroup>
<FormLabel>
{translate('MonitorNewItems')}
<Popover
anchor={
<Icon
@ -133,7 +132,7 @@ class EditArtistModalContent extends Component {
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormGroup>
<FormLabel>
{translate('QualityProfile')}
</FormLabel>
@ -147,10 +146,10 @@ class EditArtistModalContent extends Component {
</FormGroup>
{
showMetadataProfile ?
<FormGroup size={sizes.MEDIUM}>
showMetadataProfile &&
<FormGroup>
<FormLabel>
{translate('MetadataProfile')}
Metadata Profile
<Popover
anchor={
@ -174,11 +173,10 @@ class EditArtistModalContent extends Component {
{...metadataProfileId}
onChange={onInputChange}
/>
</FormGroup> :
null
</FormGroup>
}
<FormGroup size={sizes.MEDIUM}>
<FormGroup>
<FormLabel>
{translate('Path')}
</FormLabel>
@ -191,7 +189,7 @@ class EditArtistModalContent extends Component {
/>
</FormGroup>
<FormGroup size={sizes.MEDIUM}>
<FormGroup>
<FormLabel>
{translate('Tags')}
</FormLabel>
@ -211,7 +209,7 @@ class EditArtistModalContent extends Component {
kind={kinds.DANGER}
onPress={onDeleteArtistPress}
>
{translate('Delete')}
Delete
</Button>
<Button

View file

@ -25,7 +25,7 @@ const EVENT_TYPE_OPTIONS = [
{
id: 7,
get name() {
return translate('ImportCompleteFailed');
return translate('ImportFailed');
},
},
{

View file

@ -20,8 +20,6 @@ import HintedSelectInputSelectedValue from './HintedSelectInputSelectedValue';
import TextInput from './TextInput';
import styles from './EnhancedSelectInput.css';
const MINIMUM_DISTANCE_FROM_EDGE = 10;
function isArrowKey(keyCode) {
return keyCode === keyCodes.UP_ARROW || keyCode === keyCodes.DOWN_ARROW;
}
@ -139,9 +137,18 @@ class EnhancedSelectInput extends Component {
// Listeners
onComputeMaxHeight = (data) => {
const {
top,
bottom
} = data.offsets.reference;
const windowHeight = window.innerHeight;
data.styles.maxHeight = windowHeight - MINIMUM_DISTANCE_FROM_EDGE;
if ((/^botton/).test(data.placement)) {
data.styles.maxHeight = windowHeight - bottom;
} else {
data.styles.maxHeight = top;
}
return data;
};
@ -450,10 +457,6 @@ class EnhancedSelectInput extends Component {
order: 851,
enabled: true,
fn: this.onComputeMaxHeight
},
preventOverflow: {
enabled: true,
boundariesElement: 'viewport'
}
}}
>

View file

@ -83,6 +83,13 @@
}
@media only screen and (max-width: $breakpointMedium) {
.modal.small,
.modal.medium {
width: 90%;
}
}
@media only screen and (max-width: $breakpointSmall) {
.modalContainer {
position: fixed;
}

View file

@ -172,7 +172,7 @@ class SignalRConnector extends Component {
const status = resource.status;
// Both successful and failed commands need to be
// completed, otherwise they spin until they time out.
// completed, otherwise they spin until they timeout.
if (status === 'completed' || status === 'failed') {
this.props.dispatchFinishCommand(resource);
@ -224,58 +224,10 @@ class SignalRConnector extends Component {
repopulatePage('trackFileUpdated');
};
handleDownloadclient = ({ action, resource }) => {
const section = 'settings.downloadClients';
if (action === 'created' || action === 'updated') {
this.props.dispatchUpdateItem({ section, ...resource });
} else if (action === 'deleted') {
this.props.dispatchRemoveItem({ section, id: resource.id });
}
};
handleHealth = () => {
this.props.dispatchFetchHealth();
};
handleImportlist = ({ action, resource }) => {
const section = 'settings.importLists';
if (action === 'created' || action === 'updated') {
this.props.dispatchUpdateItem({ section, ...resource });
} else if (action === 'deleted') {
this.props.dispatchRemoveItem({ section, id: resource.id });
}
};
handleIndexer = ({ action, resource }) => {
const section = 'settings.indexers';
if (action === 'created' || action === 'updated') {
this.props.dispatchUpdateItem({ section, ...resource });
} else if (action === 'deleted') {
this.props.dispatchRemoveItem({ section, id: resource.id });
}
};
handleMetadata = ({ action, resource }) => {
const section = 'settings.metadata';
if (action === 'updated') {
this.props.dispatchUpdateItem({ section, ...resource });
}
};
handleNotification = ({ action, resource }) => {
const section = 'settings.notifications';
if (action === 'created' || action === 'updated') {
this.props.dispatchUpdateItem({ section, ...resource });
} else if (action === 'deleted') {
this.props.dispatchRemoveItem({ section, id: resource.id });
}
};
handleArtist = (body) => {
const action = body.action;
const section = 'artist';

View file

@ -4,7 +4,7 @@
line-height: 1.52857143;
}
@media only screen and (max-width: $breakpointMedium) {
@media only screen and (max-width: $breakpointSmall) {
.cell {
white-space: nowrap;
}

View file

@ -7,7 +7,7 @@
white-space: nowrap;
}
@media only screen and (max-width: $breakpointMedium) {
@media only screen and (max-width: $breakpointSmall) {
.cell {
white-space: nowrap;
}

View file

@ -10,7 +10,7 @@
border-collapse: collapse;
}
@media only screen and (max-width: $breakpointMedium) {
@media only screen and (max-width: $breakpointSmall) {
.tableContainer {
min-width: 100%;
width: fit-content;

View file

@ -9,7 +9,7 @@
margin-left: 10px;
}
@media only screen and (max-width: $breakpointMedium) {
@media only screen and (max-width: $breakpointSmall) {
.headerCell {
white-space: nowrap;
}

View file

@ -60,7 +60,7 @@
height: 25px;
}
@media only screen and (max-width: $breakpointMedium) {
@media only screen and (max-width: $breakpointSmall) {
.pager {
flex-wrap: wrap;
}

View file

@ -9,7 +9,7 @@
margin-left: 10px;
}
@media only screen and (max-width: $breakpointMedium) {
@media only screen and (max-width: $breakpointSmall) {
.headerCell {
white-space: nowrap;
}

View file

@ -10,11 +10,11 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import Column from 'Components/Table/Column';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import {
bulkDeleteCustomFormats,
bulkEditCustomFormats,
@ -34,7 +34,7 @@ type OnSelectedChangeCallback = React.ComponentProps<
typeof ManageCustomFormatsModalRow
>['onSelectedChange'];
const COLUMNS: Column[] = [
const COLUMNS = [
{
name: 'name',
label: () => translate('Name'),
@ -56,6 +56,8 @@ const COLUMNS: Column[] = [
interface ManageCustomFormatsModalContentProps {
onModalClose(): void;
sortKey?: string;
sortDirection?: SortDirection;
}
function ManageCustomFormatsModalContent(

View file

@ -10,11 +10,11 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import Column from 'Components/Table/Column';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import {
bulkDeleteDownloadClients,
bulkEditDownloadClients,
@ -35,7 +35,7 @@ type OnSelectedChangeCallback = React.ComponentProps<
typeof ManageDownloadClientsModalRow
>['onSelectedChange'];
const COLUMNS: Column[] = [
const COLUMNS = [
{
name: 'name',
label: () => translate('Name'),
@ -82,6 +82,8 @@ const COLUMNS: Column[] = [
interface ManageDownloadClientsModalContentProps {
onModalClose(): void;
sortKey?: string;
sortDirection?: SortDirection;
}
function ManageDownloadClientsModalContent(

View file

@ -10,11 +10,11 @@ import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import Column from 'Components/Table/Column';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import useSelectState from 'Helpers/Hooks/useSelectState';
import { kinds } from 'Helpers/Props';
import SortDirection from 'Helpers/Props/SortDirection';
import {
bulkDeleteIndexers,
bulkEditIndexers,
@ -35,7 +35,7 @@ type OnSelectedChangeCallback = React.ComponentProps<
typeof ManageIndexersModalRow
>['onSelectedChange'];
const COLUMNS: Column[] = [
const COLUMNS = [
{
name: 'name',
label: () => translate('Name'),
@ -82,6 +82,8 @@ const COLUMNS: Column[] = [
interface ManageIndexersModalContentProps {
onModalClose(): void;
sortKey?: string;
sortDirection?: SortDirection;
}
function ManageIndexersModalContent(props: ManageIndexersModalContentProps) {

View file

@ -94,9 +94,9 @@ class RootFolder extends Component {
<ConfirmModal
isOpen={this.state.isDeleteRootFolderModalOpen}
kind={kinds.DANGER}
title={translate('RemoveRootFolder')}
message={translate('RemoveRootFolderArtistsMessageText', { name })}
confirmLabel={translate('Remove')}
title={translate('DeleteRootFolder')}
message={translate('DeleteRootFolderMessageText', { name })}
confirmLabel={translate('Delete')}
onConfirm={this.onConfirmDeleteRootFolder}
onCancel={this.onDeleteRootFolderModalClose}
/>

View file

@ -24,19 +24,19 @@
height: 20px;
}
.track {
.bar {
top: 9px;
margin: 0 5px;
height: 3px;
background-color: var(--sliderAccentColor);
box-shadow: 0 0 0 #000;
&:nth-child(3n + 1) {
&:nth-child(3n+1) {
background-color: #ddd;
}
}
.thumb {
.handle {
top: 1px;
z-index: 0 !important;
width: 18px;

View file

@ -1,6 +1,8 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'bar': string;
'handle': string;
'kilobitsPerSecond': string;
'quality': string;
'qualityDefinition': string;
@ -8,9 +10,7 @@ interface CssExports {
'sizeLimit': string;
'sizes': string;
'slider': string;
'thumb': string;
'title': string;
'track': string;
}
export const cssExports: CssExports;
export default cssExports;

View file

@ -55,27 +55,6 @@ class QualityDefinition extends Component {
};
}
//
// Control
trackRenderer(props, state) {
return (
<div
{...props}
className={styles.track}
/>
);
}
thumbRenderer(props, state) {
return (
<div
{...props}
className={styles.thumb}
/>
);
}
//
// Listeners
@ -195,7 +174,6 @@ class QualityDefinition extends Component {
<div className={styles.sizeLimit}>
<ReactSlider
className={styles.slider}
min={slider.min}
max={slider.max}
step={slider.step}
@ -204,9 +182,9 @@ class QualityDefinition extends Component {
withTracks={true}
allowCross={false}
snapDragDisabled={true}
pearling={true}
renderThumb={this.thumbRenderer}
renderTrack={this.trackRenderer}
className={styles.slider}
trackClassName={styles.bar}
thumbClassName={styles.handle}
onChange={this.onSliderChange}
onAfterChange={this.onAfterSliderChange}
/>

View file

@ -151,7 +151,7 @@ export const defaultState = {
{
name: 'genres',
label: () => translate('Genres'),
isSortable: true,
isSortable: false,
isVisible: false
},
{

View file

@ -150,7 +150,7 @@ export const defaultState = {
},
{
key: 'importFailed',
label: () => translate('ImportCompleteFailed'),
label: () => translate('ImportFailed'),
filters: [
{
key: 'eventType',

View file

@ -1,8 +1,8 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Alert from 'Components/Alert';
import Link from 'Components/Link/Link';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
@ -77,16 +77,15 @@ class LogFiles extends Component {
<PageContentBody>
<Alert>
<div>
{translate('LogFilesLocation', {
location
})}
Log files are located in: {location}
</div>
{currentLogView === 'Log Files' ? (
<div>
<InlineMarkdown data={translate('TheLogLevelDefault')} />
</div>
) : null}
{
currentLogView === 'Log Files' &&
<div>
The log level defaults to 'Info' and can be changed in <Link to="/settings/general">General Settings</Link>
</div>
}
</Alert>
{

View file

@ -270,7 +270,7 @@ function Updates() {
{generalSettingsError ? (
<Alert kind={kinds.DANGER}>
{translate('FailedToFetchSettings')}
{translate('FailedToUpdateSettings')}
</Alert>
) : null}

View file

@ -109,7 +109,7 @@
"babel-loader": "9.2.1",
"babel-plugin-inline-classnames": "2.0.1",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
"core-js": "3.41.0",
"core-js": "3.39.0",
"css-loader": "6.7.3",
"css-modules-typescript-loader": "4.0.1",
"eslint": "8.57.1",

View file

@ -1,7 +1,5 @@
using FluentValidation;
using Lidarr.Http;
using NzbDrone.Core.Download;
using NzbDrone.SignalR;
namespace Lidarr.Api.V1.DownloadClient
{
@ -11,10 +9,9 @@ namespace Lidarr.Api.V1.DownloadClient
public static readonly DownloadClientResourceMapper ResourceMapper = new ();
public static readonly DownloadClientBulkResourceMapper BulkResourceMapper = new ();
public DownloadClientController(IBroadcastSignalRMessage signalRBroadcaster, IDownloadClientFactory downloadClientFactory)
: base(signalRBroadcaster, downloadClientFactory, "downloadclient", ResourceMapper, BulkResourceMapper)
public DownloadClientController(IDownloadClientFactory downloadClientFactory)
: base(downloadClientFactory, "downloadclient", ResourceMapper, BulkResourceMapper)
{
SharedValidator.RuleFor(c => c.Priority).InclusiveBetween(1, 50);
}
}
}

View file

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Lidarr.Http.REST;
using NzbDrone.Common.Http;
using NzbDrone.Core.HealthCheck;
namespace Lidarr.Api.V1.Health
@ -10,7 +11,7 @@ namespace Lidarr.Api.V1.Health
public string Source { get; set; }
public HealthCheckResult Type { get; set; }
public string Message { get; set; }
public string WikiUrl { get; set; }
public HttpUri WikiUrl { get; set; }
}
public static class HealthResourceMapper
@ -28,7 +29,7 @@ namespace Lidarr.Api.V1.Health
Source = model.Source.Name,
Type = model.Type,
Message = model.Message,
WikiUrl = model.WikiUrl.FullUri
WikiUrl = model.WikiUrl
};
}

View file

@ -3,7 +3,6 @@ using Lidarr.Http;
using NzbDrone.Core.ImportLists;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
using NzbDrone.SignalR;
namespace Lidarr.Api.V1.ImportLists
{
@ -13,12 +12,11 @@ namespace Lidarr.Api.V1.ImportLists
public static readonly ImportListResourceMapper ResourceMapper = new ();
public static readonly ImportListBulkResourceMapper BulkResourceMapper = new ();
public ImportListController(IBroadcastSignalRMessage signalRBroadcaster,
IImportListFactory importListFactory,
RootFolderExistsValidator rootFolderExistsValidator,
QualityProfileExistsValidator qualityProfileExistsValidator,
MetadataProfileExistsValidator metadataProfileExistsValidator)
: base(signalRBroadcaster, importListFactory, "importlist", ResourceMapper, BulkResourceMapper)
public ImportListController(IImportListFactory importListFactory,
RootFolderExistsValidator rootFolderExistsValidator,
QualityProfileExistsValidator qualityProfileExistsValidator,
MetadataProfileExistsValidator metadataProfileExistsValidator)
: base(importListFactory, "importlist", ResourceMapper, BulkResourceMapper)
{
SharedValidator.RuleFor(c => c.RootFolderPath).Cascade(CascadeMode.Stop)
.IsValidPath()

View file

@ -1,8 +1,6 @@
using FluentValidation;
using Lidarr.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Validation;
using NzbDrone.SignalR;
namespace Lidarr.Api.V1.Indexers
{
@ -12,12 +10,9 @@ namespace Lidarr.Api.V1.Indexers
public static readonly IndexerResourceMapper ResourceMapper = new ();
public static readonly IndexerBulkResourceMapper BulkResourceMapper = new ();
public IndexerController(IBroadcastSignalRMessage signalRBroadcaster,
IndexerFactory indexerFactory,
DownloadClientExistsValidator downloadClientExistsValidator)
: base(signalRBroadcaster, indexerFactory, "indexer", ResourceMapper, BulkResourceMapper)
public IndexerController(IndexerFactory indexerFactory, DownloadClientExistsValidator downloadClientExistsValidator)
: base(indexerFactory, "indexer", ResourceMapper, BulkResourceMapper)
{
SharedValidator.RuleFor(c => c.Priority).InclusiveBetween(1, 50);
SharedValidator.RuleFor(c => c.DownloadClientId).SetValidator(downloadClientExistsValidator);
}
}

View file

@ -13,7 +13,7 @@
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
<PackageReference Include="FluentValidation" Version="9.5.4" />
<PackageReference Include="Ical.Net" Version="4.3.1" />
<PackageReference Include="NLog" Version="5.4.0" />
<PackageReference Include="NLog" Version="5.3.4" />
<PackageReference Include="System.IO.Abstractions" Version="17.0.24" />
</ItemGroup>
</Project>

View file

@ -2,7 +2,6 @@ using System;
using Lidarr.Http;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Extras.Metadata;
using NzbDrone.SignalR;
namespace Lidarr.Api.V1.Metadata
{
@ -12,8 +11,8 @@ namespace Lidarr.Api.V1.Metadata
public static readonly MetadataResourceMapper ResourceMapper = new ();
public static readonly MetadataBulkResourceMapper BulkResourceMapper = new ();
public MetadataController(IBroadcastSignalRMessage signalRBroadcaster, IMetadataFactory metadataFactory)
: base(signalRBroadcaster, metadataFactory, "metadata", ResourceMapper, BulkResourceMapper)
public MetadataController(IMetadataFactory metadataFactory)
: base(metadataFactory, "metadata", ResourceMapper, BulkResourceMapper)
{
}

View file

@ -2,7 +2,6 @@ using System;
using Lidarr.Http;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Core.Notifications;
using NzbDrone.SignalR;
namespace Lidarr.Api.V1.Notifications
{
@ -12,8 +11,8 @@ namespace Lidarr.Api.V1.Notifications
public static readonly NotificationResourceMapper ResourceMapper = new ();
public static readonly NotificationBulkResourceMapper BulkResourceMapper = new ();
public NotificationController(IBroadcastSignalRMessage signalRBroadcaster, NotificationFactory notificationFactory)
: base(signalRBroadcaster, notificationFactory, "notification", ResourceMapper, BulkResourceMapper)
public NotificationController(NotificationFactory notificationFactory)
: base(notificationFactory, "notification", ResourceMapper, BulkResourceMapper)
{
}

View file

@ -7,19 +7,12 @@ using Lidarr.Http.REST.Attributes;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.ThingiProvider.Events;
using NzbDrone.Core.Validation;
using NzbDrone.SignalR;
namespace Lidarr.Api.V1
{
public abstract class ProviderControllerBase<TProviderResource, TBulkProviderResource, TProvider, TProviderDefinition> : RestControllerWithSignalR<TProviderResource, TProviderDefinition>,
IHandle<ProviderAddedEvent<TProvider>>,
IHandle<ProviderUpdatedEvent<TProvider>>,
IHandle<ProviderDeletedEvent<TProvider>>
public abstract class ProviderControllerBase<TProviderResource, TBulkProviderResource, TProvider, TProviderDefinition> : RestController<TProviderResource>
where TProviderDefinition : ProviderDefinition, new()
where TProvider : IProvider
where TProviderResource : ProviderResource<TProviderResource>, new()
@ -29,13 +22,11 @@ namespace Lidarr.Api.V1
private readonly ProviderResourceMapper<TProviderResource, TProviderDefinition> _resourceMapper;
private readonly ProviderBulkResourceMapper<TBulkProviderResource, TProviderDefinition> _bulkResourceMapper;
protected ProviderControllerBase(IBroadcastSignalRMessage signalRBroadcaster,
IProviderFactory<TProvider,
protected ProviderControllerBase(IProviderFactory<TProvider,
TProviderDefinition> providerFactory,
string resource,
ProviderResourceMapper<TProviderResource, TProviderDefinition> resourceMapper,
ProviderBulkResourceMapper<TBulkProviderResource, TProviderDefinition> bulkResourceMapper)
: base(signalRBroadcaster)
{
_providerFactory = providerFactory;
_resourceMapper = resourceMapper;
@ -270,24 +261,6 @@ namespace Lidarr.Api.V1
return Content(data.ToJson(), "application/json");
}
[NonAction]
public virtual void Handle(ProviderAddedEvent<TProvider> message)
{
BroadcastResourceChange(ModelAction.Created, message.Definition.Id);
}
[NonAction]
public virtual void Handle(ProviderUpdatedEvent<TProvider> message)
{
BroadcastResourceChange(ModelAction.Updated, message.Definition.Id);
}
[NonAction]
public virtual void Handle(ProviderDeletedEvent<TProvider> message)
{
BroadcastResourceChange(ModelAction.Deleted, message.ProviderId);
}
protected virtual void Validate(TProviderDefinition definition, bool includeWarnings)
{
var validationResult = definition.Settings.Validate();

View file

@ -302,7 +302,7 @@ namespace Lidarr.Api.V1.Queue
if (blocklist)
{
_failedDownloadService.MarkAsFailed(trackedDownload, skipRedownload);
_failedDownloadService.MarkAsFailed(trackedDownload.DownloadItem.DownloadId, skipRedownload);
}
if (!removeFromClient && !blocklist && !changeCategory)

View file

@ -4,7 +4,6 @@ using Lidarr.Http;
using Lidarr.Http.REST;
using Lidarr.Http.REST.Attributes;
using Microsoft.AspNetCore.Mvc;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation.Paths;
@ -22,28 +21,17 @@ namespace Lidarr.Api.V1.RemotePathMappings
_remotePathMappingService = remotePathMappingService;
SharedValidator.RuleFor(c => c.Host)
.NotEmpty();
.NotEmpty();
// We cannot use IsValidPath here, because it's a remote path, possibly other OS.
SharedValidator.RuleFor(c => c.RemotePath)
.NotEmpty();
SharedValidator.RuleFor(c => c.RemotePath)
.Must(remotePath => remotePath.IsNotNullOrWhiteSpace() && !remotePath.StartsWith(" "))
.WithMessage("Remote Path '{PropertyValue}' must not start with a space");
SharedValidator.RuleFor(c => c.RemotePath)
.Must(remotePath => remotePath.IsNotNullOrWhiteSpace() && !remotePath.EndsWith(" "))
.WithMessage("Remote Path '{PropertyValue}' must not end with a space");
.NotEmpty();
SharedValidator.RuleFor(c => c.LocalPath)
.Cascade(CascadeMode.Stop)
.IsValidPath()
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator)
.SetValidator(new SystemFolderValidator())
.NotEqual("/")
.WithMessage("Cannot be set to '/'");
.Cascade(CascadeMode.Stop)
.IsValidPath()
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator);
}
public override RemotePathMappingResource GetResourceById(int id)
@ -53,7 +41,7 @@ namespace Lidarr.Api.V1.RemotePathMappings
[RestPostById]
[Consumes("application/json")]
public ActionResult<RemotePathMappingResource> CreateMapping([FromBody] RemotePathMappingResource resource)
public ActionResult<RemotePathMappingResource> CreateMapping(RemotePathMappingResource resource)
{
var model = resource.ToModel();
@ -74,7 +62,7 @@ namespace Lidarr.Api.V1.RemotePathMappings
}
[RestPutById]
public ActionResult<RemotePathMappingResource> UpdateMapping([FromBody] RemotePathMappingResource resource)
public ActionResult<RemotePathMappingResource> UpdateMapping(RemotePathMappingResource resource)
{
var mapping = resource.ToModel();

View file

@ -92,7 +92,7 @@ namespace Lidarr.Api.V1.System.Backup
}
[HttpPost("restore/upload")]
[RequestFormLimits(MultipartBodyLengthLimit = 5000000000)]
[RequestFormLimits(MultipartBodyLengthLimit = 1000000000)]
public object UploadAndRestore()
{
var files = Request.Form.Files;

View file

@ -1,5 +1,4 @@
using System.Collections.Generic;
using FluentValidation;
using Lidarr.Http;
using Lidarr.Http.REST;
using Lidarr.Http.REST.Attributes;
@ -24,8 +23,6 @@ namespace Lidarr.Api.V1.Tags
: base(signalRBroadcaster)
{
_tagService = tagService;
SharedValidator.RuleFor(c => c.Label).NotEmpty();
}
public override TagResource GetResourceById(int id)

View file

@ -9808,8 +9808,7 @@
"nullable": true
},
"wikiUrl": {
"type": "string",
"nullable": true
"$ref": "#/components/schemas/HttpUri"
}
},
"additionalProperties": false
@ -10063,6 +10062,48 @@
},
"additionalProperties": false
},
"HttpUri": {
"type": "object",
"properties": {
"fullUri": {
"type": "string",
"nullable": true,
"readOnly": true
},
"scheme": {
"type": "string",
"nullable": true,
"readOnly": true
},
"host": {
"type": "string",
"nullable": true,
"readOnly": true
},
"port": {
"type": "integer",
"format": "int32",
"nullable": true,
"readOnly": true
},
"path": {
"type": "string",
"nullable": true,
"readOnly": true
},
"query": {
"type": "string",
"nullable": true,
"readOnly": true
},
"fragment": {
"type": "string",
"nullable": true,
"readOnly": true
}
},
"additionalProperties": false
},
"ImportListBulkResource": {
"type": "object",
"properties": {
@ -12881,7 +12922,6 @@
"downloading",
"downloadFailed",
"downloadFailedPending",
"importBlocked",
"importPending",
"importing",
"importFailed",

View file

@ -1,14 +1,9 @@
using System.Collections.Generic;
using System.IO;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
@ -21,15 +16,11 @@ namespace Lidarr.Http.Authentication
{
private readonly IAuthenticationService _authService;
private readonly IConfigFileProvider _configFileProvider;
private readonly IAppFolderInfo _appFolderInfo;
private readonly Logger _logger;
public AuthenticationController(IAuthenticationService authService, IConfigFileProvider configFileProvider, IAppFolderInfo appFolderInfo, Logger logger)
public AuthenticationController(IAuthenticationService authService, IConfigFileProvider configFileProvider)
{
_authService = authService;
_configFileProvider = configFileProvider;
_appFolderInfo = appFolderInfo;
_logger = logger;
}
[HttpPost("login")]
@ -54,23 +45,7 @@ namespace Lidarr.Http.Authentication
IsPersistent = resource.RememberMe == "on"
};
try
{
await HttpContext.SignInAsync(AuthenticationType.Forms.ToString(), new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookies", "user", "identifier")), authProperties);
}
catch (CryptographicException e)
{
if (e.InnerException is XmlException)
{
_logger.Error(e, "Failed to authenticate user due to corrupt XML. Please remove all XML files from {0} and restart Lidarr", Path.Combine(_appFolderInfo.AppDataFolder, "asp"));
}
else
{
_logger.Error(e, "Failed to authenticate user. {0}", e.Message);
}
return Unauthorized();
}
await HttpContext.SignInAsync(AuthenticationType.Forms.ToString(), new ClaimsPrincipal(new ClaimsIdentity(claims, "Cookies", "user", "identifier")), authProperties);
if (returnUrl.IsNullOrWhiteSpace() || !Url.IsLocalUrl(returnUrl))
{

View file

@ -77,7 +77,7 @@ namespace Lidarr.Http.Authentication
private void LogSuccess(HttpRequest context, string username)
{
_authLogger.Debug("Auth-Success ip {0} username '{1}'", context.GetRemoteIP(), username);
_authLogger.Info("Auth-Success ip {0} username '{1}'", context.GetRemoteIP(), username);
}
private void LogLogout(HttpRequest context, string username)

View file

@ -5,7 +5,7 @@
<ItemGroup>
<PackageReference Include="FluentValidation" Version="9.5.4" />
<PackageReference Include="ImpromptuInterface" Version="7.0.1" />
<PackageReference Include="NLog" Version="5.4.0" />
<PackageReference Include="NLog" Version="5.3.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Core\Lidarr.Core.csproj" />

View file

@ -40,16 +40,15 @@ namespace NzbDrone.Automation.Test
var service = ChromeDriverService.CreateDefaultService();
// Timeout as windows automation tests seem to take alot longer to get going
driver = new ChromeDriver(service, options, TimeSpan.FromMinutes(3));
driver = new ChromeDriver(service, options, new TimeSpan(0, 3, 0));
driver.Manage().Window.Size = new System.Drawing.Size(1920, 1080);
driver.Manage().Window.FullScreen();
_runner = new NzbDroneRunner(LogManager.GetCurrentClassLogger(), null);
_runner.KillAll();
_runner.Start(true);
driver.Navigate().GoToUrl("http://localhost:8686");
driver.Url = "http://localhost:8686";
var page = new PageBase(driver);
page.WaitForNoSpinner();
@ -69,7 +68,7 @@ namespace NzbDrone.Automation.Test
{
try
{
var image = (driver as ITakesScreenshot).GetScreenshot();
var image = ((ITakesScreenshot)driver).GetScreenshot();
image.SaveAsFile($"./{name}_test_screenshot.png", ScreenshotImageFormat.Png);
}
catch (Exception ex)

View file

@ -4,7 +4,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Selenium.Support" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="134.0.6998.16500" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="111.0.5563.6400" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NzbDrone.Test.Common\Lidarr.Test.Common.csproj" />

View file

@ -1,17 +1,19 @@
using System;
using System.Threading;
using OpenQA.Selenium;
using OpenQA.Selenium.Remote;
using OpenQA.Selenium.Support.UI;
namespace NzbDrone.Automation.Test.PageModel
{
public class PageBase
{
private readonly IWebDriver _driver;
private readonly RemoteWebDriver _driver;
public PageBase(IWebDriver driver)
public PageBase(RemoteWebDriver driver)
{
_driver = driver;
driver.Manage().Window.Maximize();
}
public IWebElement FindByClass(string className, int timeout = 5)

View file

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Threading;
using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.EnvironmentInfo;
@ -307,26 +306,9 @@ namespace NzbDrone.Common.Disk
{
Ensure.That(path, () => path).IsValidPath(PathValidationType.CurrentOs);
var files = GetFiles(path, recursive).ToList();
var files = GetFiles(path, recursive);
files.ForEach(RemoveReadOnly);
var attempts = 0;
while (attempts < 3 && files.Any())
{
EmptyFolder(path);
if (GetFiles(path, recursive).Any())
{
// Wait for IO operations to complete after emptying the folder since they aren't always
// instantly removed and it can lead to false positives that files are still present.
Thread.Sleep(3000);
}
attempts++;
files = GetFiles(path, recursive).ToList();
}
files.ToList().ForEach(RemoveReadOnly);
_fileSystem.Directory.Delete(path, recursive);
}

View file

@ -141,7 +141,7 @@ namespace NzbDrone.Common.Http.Dispatchers
}
catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
{
throw new WebException("Http request timed out", ex, WebExceptionStatus.Timeout, null);
throw new WebException("Http request timed out", ex.InnerException, WebExceptionStatus.Timeout, null);
}
}

View file

@ -6,17 +6,17 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
<PackageReference Include="IPAddressRange" Version="6.2.0" />
<PackageReference Include="IPAddressRange" Version="6.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.4.0" />
<PackageReference Include="NLog" Version="5.3.4" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.2" />
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.3" />
<PackageReference Include="NLog.Layouts.ClefJsonLayout" Version="1.0.2" />
<PackageReference Include="Sentry" Version="4.0.2" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="System.IO.Abstractions" Version="17.0.24" />
<PackageReference Include="System.Text.Json" Version="6.0.10" />
<PackageReference Include="System.ValueTuple" Version="4.6.1" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.1" />

View file

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
@ -7,7 +6,7 @@ namespace NzbDrone.Common
{
public class PathEqualityComparer : IEqualityComparer<string>
{
public static readonly PathEqualityComparer Instance = new ();
public static readonly PathEqualityComparer Instance = new PathEqualityComparer();
private PathEqualityComparer()
{
@ -20,19 +19,12 @@ namespace NzbDrone.Common
public int GetHashCode(string obj)
{
try
if (OsInfo.IsWindows)
{
if (OsInfo.IsWindows)
{
return obj.CleanFilePath().Normalize().ToLower().GetHashCode();
}
return obj.CleanFilePath().Normalize().ToLower().GetHashCode();
}
return obj.CleanFilePath().Normalize().GetHashCode();
}
catch (ArgumentException ex)
{
throw new ArgumentException($"Invalid path: {obj}", ex);
}
return obj.CleanFilePath().Normalize().GetHashCode();
}
}
}

View file

@ -6,7 +6,6 @@ using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Model;
@ -118,9 +117,7 @@ namespace NzbDrone.Common.Processes
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
RedirectStandardInput = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
RedirectStandardInput = true
};
if (environmentVariables != null)

View file

@ -103,7 +103,6 @@ namespace NzbDrone.Core.Test.DiskSpace
[TestCase("/var/lib/docker")]
[TestCase("/some/place/docker/aufs")]
[TestCase("/etc/network")]
[TestCase("/Volumes/.timemachine/ABC123456-A1BC-12A3B45678C9/2025-05-13-181401.backup")]
public void should_not_check_diskspace_for_irrelevant_mounts(string path)
{
var mount = new Mock<IMount>();

View file

@ -183,8 +183,6 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
{
GivenArtistMatch();
var tracks = Builder<Track>.CreateListOfSize(3).BuildList();
_trackedDownload.RemoteAlbum.Albums = new List<Album>
{
CreateAlbum(1, 3)
@ -194,9 +192,9 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic(), Tracks = new List<Track> { tracks[0] } })),
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic(), Tracks = new List<Track> { tracks[1] } })),
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic(), Tracks = new List<Track> { tracks[2] } })),
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }), "Test Failure")
});
@ -292,9 +290,6 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
[Test]
public void should_mark_as_imported_if_all_tracks_were_imported()
{
var track1 = new Track { Id = 1 };
var track2 = new Track { Id = 2 };
_trackedDownload.RemoteAlbum.Albums = new List<Album>
{
CreateAlbum(1, 2)
@ -306,11 +301,11 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
{
new ImportResult(
new ImportDecision<LocalTrack>(
new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic(), Tracks = new List<Track> { track1 } })),
new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() })),
new ImportResult(
new ImportDecision<LocalTrack>(
new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic(), Tracks = new List<Track> { track2 } }))
new LocalTrack { Path = @"C:\TestPath\Droned.S01E02.mkv".AsOsAgnostic() }))
});
Subject.Import(_trackedDownload);
@ -372,13 +367,11 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests
{
GivenABadlyNamedDownload();
var track1 = new Track { Id = 1 };
Mocker.GetMock<IDownloadedTracksImportService>()
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<ImportMode>(), It.IsAny<Artist>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic(), Tracks = new List<Track> { track1 } }))
new ImportResult(new ImportDecision<LocalTrack>(new LocalTrack { Path = @"C:\TestPath\Droned.S01E01.mkv".AsOsAgnostic() }))
});
Mocker.GetMock<IArtistService>()

View file

@ -80,7 +80,7 @@ namespace NzbDrone.Core.Test.ImportListTests
}
[Test]
[Ignore("Pending mapping fixes", Until = "2025-10-20 00:00:00Z")]
[Ignore("Pending mapping fixes", Until = "2025-04-20 00:00:00Z")]
public void map_artist_should_work()
{
UseRealHttp();
@ -159,7 +159,7 @@ namespace NzbDrone.Core.Test.ImportListTests
}
[Test]
[Ignore("Pending mapping fixes", Until = "2025-10-20 00:00:00Z")]
[Ignore("Pending mapping fixes", Until = "2025-04-20 00:00:00Z")]
public void map_album_should_work()
{
UseRealHttp();

View file

@ -14,7 +14,6 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.MetadataSource.SkyHook
{
[TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-09-01 00:00:00Z")]
public class SkyHookProxyFixture : CoreTest<SkyHookProxy>
{
private MetadataProfile _metadataProfile;

View file

@ -12,7 +12,6 @@ using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MetadataSource.SkyHook
{
[TestFixture]
[Ignore("Waiting for metadata to be back again", Until = "2025-09-01 00:00:00Z")]
public class SkyHookProxySearchFixture : CoreTest<SkyHookProxy>
{
[SetUp]

View file

@ -64,7 +64,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
}
[TestCase("Florence + the Machine", "Florence + the Machine")]
[TestCase("Beyoncé X10", "Beyonce X10")]
[TestCase("Beyoncé X10", "Beyoncé X10")]
[TestCase("Girlfriends' Guide to Divorce", "Girlfriends Guide to Divorce")]
[TestCase("Rule #23: Never Lie to the Kids", "Rule #23 Never Lie to the Kids")]
[TestCase("Anne Hathaway/Florence + The Machine", "Anne Hathaway Florence + The Machine")]
@ -81,7 +81,6 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
[TestCase("[a] title", "a title")]
[TestCase("backslash \\ backlash", "backslash backlash")]
[TestCase("I'm the Boss", "Im the Boss")]
[TestCase("Joker: Folie à deux", "Joker Folie a deux")]
public void should_get_expected_title_back(string name, string expected)
{
_artist.Name = name;

View file

@ -129,9 +129,6 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("Green Day - Father Of All [FLAC (M4A) 24-bit Lossless]", null, 0, 0)]
[TestCase("Green_Day-Father_Of_All_FLAC_M4A_24_bit_Lossless", null, 0, 0)]
[TestCase("Green.Day-Father.Of.All.FLAC.M4A.24.bit.Lossless", null, 0, 0)]
[TestCase("Linkin Park - Studio Collection 2000-2012 (2013) [WEB FLAC24-44.1]", null, 0, 0)]
[TestCase("Linkin Park - Studio Collection 2000-2012 (2013) [WEB FLAC24bit]", null, 0, 0)]
[TestCase("Linkin Park - Studio Collection 2000-2012 (2013) [WEB FLAC24-bit]", null, 0, 0)]
public void should_parse_flac_24bit_quality(string title, string desc, int bitrate, int sampleSize)
{
ParseAndVerifyQuality(title, desc, bitrate, Quality.FLAC_24, sampleSize);

View file

@ -185,9 +185,7 @@ namespace NzbDrone.Core.Blocklisting
Indexer = message.Data.GetValueOrDefault("indexer"),
Protocol = (DownloadProtocol)Convert.ToInt32(message.Data.GetValueOrDefault("protocol")),
Message = message.Message,
TorrentInfoHash = message.TrackedDownload?.Protocol == DownloadProtocol.Torrent
? message.TrackedDownload.DownloadItem.DownloadId
: message.Data.GetValueOrDefault("torrentInfoHash", null)
TorrentInfoHash = message.Data.GetValueOrDefault("torrentInfoHash")
};
if (Enum.TryParse(message.Data.GetValueOrDefault("indexerFlags"), true, out IndexerFlags flags))

View file

@ -263,21 +263,7 @@ namespace NzbDrone.Core.Configuration
}
public string UiFolder => BuildInfo.IsDebug ? Path.Combine("..", "UI") : "UI";
public string InstanceName
{
get
{
var instanceName = _appOptions.InstanceName ?? GetValue("InstanceName", BuildInfo.AppName);
if (instanceName.Contains(BuildInfo.AppName, StringComparison.OrdinalIgnoreCase))
{
return instanceName;
}
return BuildInfo.AppName;
}
}
public string InstanceName => _appOptions.InstanceName ?? GetValue("InstanceName", BuildInfo.AppName);
public bool UpdateAutomatically => _updateOptions.Automatically ?? GetValueBoolean("UpdateAutomatically", OsInfo.IsWindows, false);

View file

@ -11,7 +11,6 @@ namespace NzbDrone.Core.CustomFormats
{
RuleFor(c => c.Min).GreaterThanOrEqualTo(0);
RuleFor(c => c.Max).GreaterThan(c => c.Min);
RuleFor(c => c.Max).LessThanOrEqualTo(double.MaxValue);
}
}

View file

@ -252,7 +252,7 @@ namespace NzbDrone.Core.Datastore
protected void Delete(SqlBuilder builder)
{
var sql = builder.AddDeleteTemplate(typeof(TModel));
var sql = builder.AddDeleteTemplate(typeof(TModel)).LogQuery();
using (var conn = _database.OpenConnection())
{

View file

@ -21,7 +21,7 @@ namespace NzbDrone.Core.DiskSpace
private readonly IRootFolderService _rootFolderService;
private readonly Logger _logger;
private static readonly Regex _regexSpecialDrive = new Regex(@"^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)|/\.timemachine", RegexOptions.Compiled);
private static readonly Regex _regexSpecialDrive = new Regex("^/var/lib/(docker|rancher|kubelet)(/|$)|^/(boot|etc)(/|$)|/docker(/var)?/aufs(/|$)", RegexOptions.Compiled);
public DiskSpaceService(IDiskProvider diskProvider,
IRootFolderService rootFolderService,
@ -38,10 +38,7 @@ namespace NzbDrone.Core.DiskSpace
var optionalRootFolders = GetFixedDisksRootPaths().Except(importantRootFolders).Distinct().ToList();
var diskSpace = GetDiskSpace(importantRootFolders)
.Concat(GetDiskSpace(optionalRootFolders, true))
.OrderBy(d => d.Path, StringComparer.OrdinalIgnoreCase)
.ToList();
var diskSpace = GetDiskSpace(importantRootFolders).Concat(GetDiskSpace(optionalRootFolders, true)).ToList();
return diskSpace;
}
@ -57,7 +54,7 @@ namespace NzbDrone.Core.DiskSpace
private IEnumerable<string> GetFixedDisksRootPaths()
{
return _diskProvider.GetMounts()
.Where(d => d.DriveType is DriveType.Fixed or DriveType.Network)
.Where(d => d.DriveType == DriveType.Fixed)
.Where(d => !_regexSpecialDrive.IsMatch(d.RootDirectory))
.Select(d => d.RootDirectory);
}

View file

@ -25,11 +25,6 @@ namespace NzbDrone.Core.Download.Aggregation
public RemoteAlbum Augment(RemoteAlbum remoteAlbum)
{
if (remoteAlbum == null)
{
return null;
}
foreach (var augmenter in _augmenters)
{
try

View file

@ -9,7 +9,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -29,10 +28,9 @@ namespace NzbDrone.Core.Download.Clients.Aria2
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
}

View file

@ -9,7 +9,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
@ -31,10 +30,9 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_scanWatchFolder = scanWatchFolder;

View file

@ -7,7 +7,6 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -26,9 +25,8 @@ namespace NzbDrone.Core.Download.Clients.Blackhole
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
ILocalizationService localizationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
{
_scanWatchFolder = scanWatchFolder;

View file

@ -9,7 +9,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -28,10 +27,9 @@ namespace NzbDrone.Core.Download.Clients.Deluge
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
}

View file

@ -11,7 +11,6 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -38,10 +37,9 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_dsInfoProxy = dsInfoProxy;
_dsTaskProxySelector = dsTaskProxySelector;

View file

@ -9,7 +9,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.DownloadStation.Proxies;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
@ -35,9 +34,8 @@ namespace NzbDrone.Core.Download.Clients.DownloadStation
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
ILocalizationService localizationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
{
_dsInfoProxy = dsInfoProxy;
_dsTaskProxySelector = dsTaskProxySelector;

View file

@ -9,7 +9,6 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Flood.Models;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -29,10 +28,9 @@ namespace NzbDrone.Core.Download.Clients.Flood
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
_downloadSeedConfigProvider = downloadSeedConfigProvider;

View file

@ -9,7 +9,6 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.FreeboxDownload.Responses;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -26,10 +25,9 @@ namespace NzbDrone.Core.Download.Clients.FreeboxDownload
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
}

View file

@ -8,7 +8,6 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Hadouken.Models;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -26,10 +25,9 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
}

View file

@ -8,7 +8,6 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@ -25,9 +24,8 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
ILocalizationService localizationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
{
_proxy = proxy;
}

View file

@ -10,7 +10,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@ -29,9 +28,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
ILocalizationService localizationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
{
_proxy = proxy;
}

View file

@ -9,7 +9,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -24,9 +23,8 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
Logger logger)
: base(configService, diskProvider, remotePathMappingService, localizationService, logger)
: base(configService, diskProvider, remotePathMappingService, logger)
{
_httpClient = httpClient;
}

View file

@ -10,7 +10,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -36,10 +35,9 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ICacheManager cacheManager,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxySelector = proxySelector;

View file

@ -10,7 +10,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation;
@ -27,9 +26,8 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
ILocalizationService localizationService,
Logger logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, localizationService, logger)
: base(httpClient, configService, diskProvider, remotePathMappingService, nzbValidationService, logger)
{
_proxy = proxy;
}

View file

@ -8,7 +8,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.RemotePathMappings;
@ -25,10 +24,9 @@ namespace NzbDrone.Core.Download.Clients.Transmission
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
}

View file

@ -9,7 +9,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -29,10 +28,9 @@ namespace NzbDrone.Core.Download.Clients.Transmission
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
}
@ -103,11 +101,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
if (!torrent.ErrorString.IsNullOrWhiteSpace())
{
item.Status = DownloadItemStatus.Warning;
item.Message = _localizationService.GetLocalizedString("DownloadClientItemErrorMessage", new Dictionary<string, object>
{
{ "clientName", Name },
{ "message", torrent.ErrorString }
});
item.Message = torrent.ErrorString;
}
else if (torrent.TotalSize == 0)
{

View file

@ -5,7 +5,6 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.Transmission;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.RemotePathMappings;
@ -24,10 +23,9 @@ namespace NzbDrone.Core.Download.Clients.Vuze
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(proxy, torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
}

View file

@ -12,7 +12,6 @@ using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Download.Clients.rTorrent;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -36,10 +35,9 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
IRemotePathMappingService remotePathMappingService,
IDownloadSeedConfigProvider downloadSeedConfigProvider,
IRTorrentDirectoryValidator rTorrentDirectoryValidator,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;
_rTorrentDirectoryValidator = rTorrentDirectoryValidator;

View file

@ -10,7 +10,6 @@ using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -30,10 +29,9 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, localizationService, blocklistService, logger)
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, blocklistService, logger)
{
_proxy = proxy;

View file

@ -63,8 +63,8 @@ namespace NzbDrone.Core.Download
SetImportItem(trackedDownload);
// 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)
// Only process tracked downloads that are still downloading
if (trackedDownload.State != TrackedDownloadState.Downloading)
{
return;
}
@ -93,9 +93,7 @@ namespace NzbDrone.Core.Download
if (artist == null)
{
trackedDownload.Warn("Artist name mismatch, automatic import is not possible. Check the download troubleshooting entry on the wiki for common causes.");
SetStateToImportBlocked(trackedDownload);
trackedDownload.Warn("Artist name mismatch, automatic import is not possible.");
return;
}
}
@ -115,8 +113,6 @@ namespace NzbDrone.Core.Download
if (trackedDownload.RemoteAlbum == null)
{
trackedDownload.Warn("Unable to parse download, automatic import is not possible.");
SetStateToImportBlocked(trackedDownload);
return;
}
@ -153,11 +149,14 @@ namespace NzbDrone.Core.Download
var statusMessages = new List<TrackedDownloadStatusMessage>
{
new TrackedDownloadStatusMessage("One or more tracks expected in this release were not imported or missing from the release", new List<string>())
new TrackedDownloadStatusMessage("One or more albums expected in this release were not imported or missing", new List<string>())
};
if (importResults.Any(c => c.Result != ImportResultType.Imported))
{
// Mark as failed to prevent further attempts at processing
trackedDownload.State = TrackedDownloadState.ImportFailed;
statusMessages.AddRange(
importResults
.Where(v => v.Result != ImportResultType.Imported && v.ImportDecision.Item != null)
@ -166,34 +165,22 @@ namespace NzbDrone.Core.Download
new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.Item.Path),
v.Errors)));
// Mark as failed to prevent further attempts at processing
trackedDownload.State = TrackedDownloadState.ImportFailed;
if (statusMessages.Any())
{
trackedDownload.Warn(statusMessages.ToArray());
}
// Publish event to notify album was imported incomplete
// Publish event to notify Album was imported incompelte
_eventAggregator.PublishEvent(new AlbumImportIncompleteEvent(trackedDownload));
return;
}
if (statusMessages.Any())
{
trackedDownload.Warn(statusMessages.ToArray());
SetStateToImportBlocked(trackedDownload);
}
}
public bool VerifyImport(TrackedDownload trackedDownload, List<ImportResult> importResults)
{
var allTracksImported =
(importResults.Any() && importResults.All(c => c.Result == ImportResultType.Imported)) ||
importResults.Where(c => c.Result == ImportResultType.Imported)
.SelectMany(c => c.ImportDecision.Item.Tracks)
.Count() >= Math.Max(1, trackedDownload.RemoteAlbum.Albums.Sum(x => x.AlbumReleases.Value.Where(y => y.Monitored).Sum(z => z.TrackCount)));
var allTracksImported = importResults.All(c => c.Result == ImportResultType.Imported) ||
importResults.Count(c => c.Result == ImportResultType.Imported) >=
Math.Max(1, trackedDownload.RemoteAlbum.Albums.Sum(x => x.AlbumReleases.Value.Where(y => y.Monitored).Sum(z => z.TrackCount)));
if (allTracksImported)
{
@ -204,10 +191,6 @@ namespace NzbDrone.Core.Download
return true;
}
var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId)
.OrderByDescending(h => h.Date)
.ToList();
// Double check if all episodes were imported by checking the history if at least one
// file was imported. This will allow the decision engine to reject already imported
// episode files and still mark the download complete when all files are imported.
@ -216,14 +199,19 @@ namespace NzbDrone.Core.Download
// and an episode is removed, but later comes back with a different ID then Sonarr will treat it as incomplete.
// Since imports should be relatively fast and these types of data changes are infrequent this should be quite
// safe, but commenting for future benefit.
var atLeastOneTrackImported = importResults.Any(c => c.Result == ImportResultType.Imported);
var atLeastOneEpisodeImported = importResults.Any(c => c.Result == ImportResultType.Imported);
var historyItems = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId)
.OrderByDescending(h => h.Date)
.ToList();
var allTracksImportedInHistory = _trackedDownloadAlreadyImported.IsImported(trackedDownload, historyItems);
if (allTracksImportedInHistory)
{
// Log different error messages depending on the circumstances, but treat both as fully imported, because that's the reality.
// The second message shouldn't be logged in most cases, but continued reporting would indicate an ongoing issue.
if (atLeastOneTrackImported)
if (atLeastOneEpisodeImported)
{
_logger.Debug("All albums were imported in history for {0}", trackedDownload.DownloadItem.Title);
}
@ -245,15 +233,10 @@ namespace NzbDrone.Core.Download
return true;
}
_logger.Debug("Not all albums have been imported for the release '{0}'", trackedDownload.DownloadItem.Title);
_logger.Debug("Not all albums have been imported for {0}", trackedDownload.DownloadItem.Title);
return false;
}
private void SetStateToImportBlocked(TrackedDownload trackedDownload)
{
trackedDownload.State = TrackedDownloadState.ImportBlocked;
}
private void SetImportItem(TrackedDownload trackedDownload)
{
trackedDownload.ImportItem = _provideImportItemService.ProvideImportItem(trackedDownload.DownloadItem, trackedDownload.ImportItem);

View file

@ -8,7 +8,6 @@ using NzbDrone.Common.Disk;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.ThingiProvider;
@ -24,7 +23,6 @@ namespace NzbDrone.Core.Download
protected readonly IConfigService _configService;
protected readonly IDiskProvider _diskProvider;
protected readonly IRemotePathMappingService _remotePathMappingService;
protected readonly ILocalizationService _localizationService;
protected readonly Logger _logger;
protected ResiliencePipeline<HttpResponse> RetryStrategy => new ResiliencePipelineBuilder<HttpResponse>()
@ -79,13 +77,11 @@ namespace NzbDrone.Core.Download
protected DownloadClientBase(IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
Logger logger)
{
_configService = configService;
_diskProvider = diskProvider;
_remotePathMappingService = remotePathMappingService;
_localizationService = localizationService;
_logger = logger;
}

View file

@ -12,7 +12,7 @@ namespace NzbDrone.Core.Download
public interface IFailedDownloadService
{
void MarkAsFailed(int historyId, bool skipRedownload = false);
void MarkAsFailed(TrackedDownload trackedDownload, bool skipRedownload = false);
void MarkAsFailed(string downloadId, bool skipRedownload = false);
void Check(TrackedDownload trackedDownload);
void ProcessFailed(TrackedDownload trackedDownload);
}
@ -20,12 +20,15 @@ namespace NzbDrone.Core.Download
public class FailedDownloadService : IFailedDownloadService
{
private readonly IHistoryService _historyService;
private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IEventAggregator _eventAggregator;
public FailedDownloadService(IHistoryService historyService,
ITrackedDownloadService trackedDownloadService,
IEventAggregator eventAggregator)
{
_historyService = historyService;
_trackedDownloadService = trackedDownloadService;
_eventAggregator = eventAggregator;
}
@ -34,10 +37,9 @@ namespace NzbDrone.Core.Download
var history = _historyService.Get(historyId);
var downloadId = history.DownloadId;
if (downloadId.IsNullOrWhiteSpace())
{
PublishDownloadFailedEvent(history, new List<int> { history.AlbumId }, "Manually marked as failed", skipRedownload: skipRedownload);
PublishDownloadFailedEvent(new List<EntityHistory> { history }, "Manually marked as failed", skipRedownload: skipRedownload);
return;
}
@ -51,26 +53,28 @@ namespace NzbDrone.Core.Download
}
// Add any other history items for the download ID then filter out any duplicate history items.
grabbedHistory.AddRange(GetGrabbedHistory(downloadId));
grabbedHistory.AddRange(_historyService.Find(downloadId, EntityHistoryEventType.Grabbed));
grabbedHistory = grabbedHistory.DistinctBy(h => h.Id).ToList();
PublishDownloadFailedEvent(history, GetAlbumIds(grabbedHistory), "Manually marked as failed");
PublishDownloadFailedEvent(grabbedHistory, "Manually marked as failed");
}
public void MarkAsFailed(TrackedDownload trackedDownload, bool skipRedownload = false)
public void MarkAsFailed(string downloadId, bool skipRedownload = false)
{
var history = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
var history = _historyService.Find(downloadId, EntityHistoryEventType.Grabbed);
if (history.Any())
{
PublishDownloadFailedEvent(history.First(), GetAlbumIds(history), "Manually marked as failed", trackedDownload, skipRedownload: skipRedownload);
var trackedDownload = _trackedDownloadService.Find(downloadId);
PublishDownloadFailedEvent(history, "Manually marked as failed", trackedDownload, skipRedownload: skipRedownload);
}
}
public void Check(TrackedDownload trackedDownload)
{
// 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)
// Only process tracked downloads that are still downloading
if (trackedDownload.State != TrackedDownloadState.Downloading)
{
return;
}
@ -78,7 +82,9 @@ namespace NzbDrone.Core.Download
if (trackedDownload.DownloadItem.IsEncrypted ||
trackedDownload.DownloadItem.Status == DownloadItemStatus.Failed)
{
var grabbedItems = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
var grabbedItems = _historyService
.Find(trackedDownload.DownloadItem.DownloadId, EntityHistoryEventType.Grabbed)
.ToList();
if (grabbedItems.Empty())
{
@ -97,7 +103,9 @@ namespace NzbDrone.Core.Download
return;
}
var grabbedItems = GetGrabbedHistory(trackedDownload.DownloadItem.DownloadId);
var grabbedItems = _historyService
.Find(trackedDownload.DownloadItem.DownloadId, EntityHistoryEventType.Grabbed)
.ToList();
if (grabbedItems.Empty())
{
@ -116,17 +124,18 @@ namespace NzbDrone.Core.Download
}
trackedDownload.State = TrackedDownloadState.DownloadFailed;
PublishDownloadFailedEvent(grabbedItems.First(), GetAlbumIds(grabbedItems), failure, trackedDownload);
PublishDownloadFailedEvent(grabbedItems, failure, trackedDownload);
}
private void PublishDownloadFailedEvent(EntityHistory historyItem, List<int> albumIds, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
private void PublishDownloadFailedEvent(List<EntityHistory> historyItems, string message, TrackedDownload trackedDownload = null, bool skipRedownload = false)
{
var historyItem = historyItems.Last();
Enum.TryParse(historyItem.Data.GetValueOrDefault(EntityHistory.RELEASE_SOURCE, ReleaseSourceType.Unknown.ToString()), out ReleaseSourceType releaseSource);
var downloadFailedEvent = new DownloadFailedEvent
{
ArtistId = historyItem.ArtistId,
AlbumIds = albumIds,
AlbumIds = historyItems.Select(h => h.AlbumId).Distinct().ToList(),
Quality = historyItem.Quality,
SourceTitle = historyItem.SourceTitle,
DownloadClient = historyItem.Data.GetValueOrDefault(EntityHistory.DOWNLOAD_CLIENT),
@ -135,23 +144,10 @@ namespace NzbDrone.Core.Download
Data = historyItem.Data,
TrackedDownload = trackedDownload,
SkipRedownload = skipRedownload,
ReleaseSource = releaseSource,
ReleaseSource = releaseSource
};
_eventAggregator.PublishEvent(downloadFailedEvent);
}
private List<int> GetAlbumIds(List<EntityHistory> historyItems)
{
return historyItems.Select(h => h.AlbumId).Distinct().ToList();
}
private List<EntityHistory> GetGrabbedHistory(string downloadId)
{
// Sort by date so items are always in the same order
return _historyService.Find(downloadId, EntityHistoryEventType.Grabbed)
.OrderByDescending(h => h.Date)
.ToList();
}
}
}

View file

@ -1,4 +1,3 @@
using System;
using System.IO;
using System.Linq;
using System.Xml;
@ -16,52 +15,38 @@ namespace NzbDrone.Core.Download
{
public void Validate(string filename, byte[] fileContent)
{
try
{
var reader = new StreamReader(new MemoryStream(fileContent));
var reader = new StreamReader(new MemoryStream(fileContent));
using (var xmlTextReader = XmlReader.Create(reader,
new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, IgnoreComments = true }))
using (var xmlTextReader = XmlReader.Create(reader, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, IgnoreComments = true }))
{
var xDoc = XDocument.Load(xmlTextReader);
var nzb = xDoc.Root;
if (nzb == null)
{
var xDoc = XDocument.Load(xmlTextReader);
var nzb = xDoc.Root;
if (nzb == null)
{
throw new InvalidNzbException("Invalid NZB: No Root element [{0}]", filename);
}
// nZEDb has an bug in their error reporting code spitting out invalid http status codes
if (nzb.Name.LocalName.Equals("error") &&
nzb.TryGetAttributeValue("code", out var code) &&
nzb.TryGetAttributeValue("description", out var description))
{
throw new InvalidNzbException("Invalid NZB: Contains indexer error: {0} - {1}", code, description);
}
if (!nzb.Name.LocalName.Equals("nzb"))
{
throw new InvalidNzbException(
"Invalid NZB: Unexpected root element. Expected 'nzb' found '{0}' [{1}]", nzb.Name.LocalName, filename);
}
var ns = nzb.Name.Namespace;
var files = nzb.Elements(ns + "file").ToList();
if (files.Empty())
{
throw new InvalidNzbException("Invalid NZB: No files [{0}]", filename);
}
throw new InvalidNzbException("Invalid NZB: No Root element [{0}]", filename);
}
// nZEDb has an bug in their error reporting code spitting out invalid http status codes
if (nzb.Name.LocalName.Equals("error") &&
nzb.TryGetAttributeValue("code", out var code) &&
nzb.TryGetAttributeValue("description", out var description))
{
throw new InvalidNzbException("Invalid NZB: Contains indexer error: {0} - {1}", code, description);
}
if (!nzb.Name.LocalName.Equals("nzb"))
{
throw new InvalidNzbException("Invalid NZB: Unexpected root element. Expected 'nzb' found '{0}' [{1}]", nzb.Name.LocalName, filename);
}
var ns = nzb.Name.Namespace;
var files = nzb.Elements(ns + "file").ToList();
if (files.Empty())
{
throw new InvalidNzbException("Invalid NZB: No files [{0}]", filename);
}
}
catch (InvalidNzbException)
{
// Throw the original exception
throw;
}
catch (Exception ex)
{
throw new InvalidNzbException("Invalid NZB: Unable to parse [{0}]", ex, filename);
}
}
}

View file

@ -10,7 +10,6 @@ using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Localization;
using NzbDrone.Core.MediaFiles.TorrentInfo;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
@ -31,10 +30,9 @@ namespace NzbDrone.Core.Download
IConfigService configService,
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
ILocalizationService localizationService,
IBlocklistService blocklistService,
Logger logger)
: base(configService, diskProvider, remotePathMappingService, localizationService, logger)
: base(configService, diskProvider, remotePathMappingService, logger)
{
_httpClient = httpClient;
_blocklistService = blocklistService;
@ -172,7 +170,7 @@ namespace NzbDrone.Core.Download
}
catch (HttpException ex)
{
if (ex.Response.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Gone)
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
{
_logger.Error(ex, "Downloading torrent file for album '{0}' failed since it no longer exists ({1})", remoteAlbum.Release.Title, torrentUrl);
throw new ReleaseUnavailableException(remoteAlbum.Release, "Downloading torrent failed", ex);

View file

@ -115,7 +115,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
{
var trackedDownload = _trackedDownloadService.TrackDownload((DownloadClientDefinition)downloadClient.Definition, downloadItem);
if (trackedDownload is { State: TrackedDownloadState.Downloading or TrackedDownloadState.ImportBlocked })
if (trackedDownload != null && trackedDownload.State == TrackedDownloadState.Downloading)
{
_failedDownloadService.Check(trackedDownload);
_completedDownloadService.Check(trackedDownload);

View file

@ -41,7 +41,6 @@ namespace NzbDrone.Core.Download.TrackedDownloads
Downloading,
DownloadFailed,
DownloadFailedPending,
ImportBlocked,
ImportPending,
Importing,
ImportFailed,

View file

@ -6,7 +6,6 @@ using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.CustomFormats;
using NzbDrone.Core.Download.Aggregation;
using NzbDrone.Core.Download.History;
using NzbDrone.Core.History;
using NzbDrone.Core.Messaging.Events;
@ -17,7 +16,7 @@ using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download.TrackedDownloads
{
public interface ITrackedDownloadService
public interface ITrackedDownloadService : IHandle<AlbumDeletedEvent>
{
TrackedDownload Find(string downloadId);
void StopTracking(string downloadId);
@ -27,17 +26,13 @@ namespace NzbDrone.Core.Download.TrackedDownloads
void UpdateTrackable(List<TrackedDownload> trackedDownloads);
}
public class TrackedDownloadService : ITrackedDownloadService,
IHandle<AlbumInfoRefreshedEvent>,
IHandle<AlbumDeletedEvent>,
IHandle<ArtistAddedEvent>,
IHandle<ArtistsDeletedEvent>
public class TrackedDownloadService : ITrackedDownloadService
{
private readonly IParsingService _parsingService;
private readonly IHistoryService _historyService;
private readonly IEventAggregator _eventAggregator;
private readonly IDownloadHistoryService _downloadHistoryService;
private readonly IRemoteAlbumAggregationService _aggregationService;
private readonly ITrackedDownloadAlreadyImported _trackedDownloadAlreadyImported;
private readonly ICustomFormatCalculationService _formatCalculator;
private readonly Logger _logger;
private readonly ICached<TrackedDownload> _cache;
@ -48,7 +43,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
ICustomFormatCalculationService formatCalculator,
IEventAggregator eventAggregator,
IDownloadHistoryService downloadHistoryService,
IRemoteAlbumAggregationService aggregationService,
ITrackedDownloadAlreadyImported trackedDownloadAlreadyImported,
Logger logger)
{
_parsingService = parsingService;
@ -56,8 +51,8 @@ namespace NzbDrone.Core.Download.TrackedDownloads
_cache = cacheManager.GetCache<TrackedDownload>(GetType());
_formatCalculator = formatCalculator;
_eventAggregator = eventAggregator;
_trackedDownloadAlreadyImported = trackedDownloadAlreadyImported;
_downloadHistoryService = downloadHistoryService;
_aggregationService = aggregationService;
_logger = logger;
}
@ -71,8 +66,6 @@ namespace NzbDrone.Core.Download.TrackedDownloads
var parsedAlbumInfo = Parser.Parser.ParseAlbumTitle(trackedDownload.DownloadItem.Title);
trackedDownload.RemoteAlbum = parsedAlbumInfo == null ? null : _parsingService.Map(parsedAlbumInfo);
_aggregationService.Augment(trackedDownload.RemoteAlbum);
}
public void StopTracking(string downloadId)
@ -197,11 +190,9 @@ namespace NzbDrone.Core.Download.TrackedDownloads
}
}
// Calculate custom formats
if (trackedDownload.RemoteAlbum != null)
{
_aggregationService.Augment(trackedDownload.RemoteAlbum);
// Calculate custom formats
trackedDownload.RemoteAlbum.CustomFormats = _formatCalculator.ParseCustomFormat(trackedDownload.RemoteAlbum, downloadItem.TotalSize);
}
@ -273,6 +264,18 @@ namespace NzbDrone.Core.Download.TrackedDownloads
}
}
public void Handle(AlbumDeletedEvent message)
{
var cachedItems = _cache.Values.Where(x => x.RemoteAlbum != null && x.RemoteAlbum.Albums.Any(a => a.Id == message.Album.Id)).ToList();
if (cachedItems.Any())
{
cachedItems.ForEach(UpdateCachedItem);
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
}
}
public void Handle(AlbumInfoRefreshedEvent message)
{
var needsToUpdate = false;
@ -298,45 +301,12 @@ namespace NzbDrone.Core.Download.TrackedDownloads
}
}
public void Handle(AlbumDeletedEvent message)
{
var cachedItems = _cache.Values
.Where(t =>
t.RemoteAlbum?.Albums != null &&
t.RemoteAlbum.Albums.Any(a => a.Id == message.Album.Id || a.ForeignAlbumId == message.Album.ForeignAlbumId))
.ToList();
if (cachedItems.Any())
{
cachedItems.ForEach(UpdateCachedItem);
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
}
}
public void Handle(ArtistAddedEvent message)
{
var cachedItems = _cache.Values
.Where(t =>
t.RemoteAlbum?.Artist == null ||
message.Artist?.ForeignArtistId == t.RemoteAlbum.Artist.ForeignArtistId)
.ToList();
if (cachedItems.Any())
{
cachedItems.ForEach(UpdateCachedItem);
_eventAggregator.PublishEvent(new TrackedDownloadRefreshedEvent(GetTrackedDownloads()));
}
}
public void Handle(ArtistsDeletedEvent message)
{
var cachedItems = _cache.Values
.Where(t =>
t.RemoteAlbum?.Artist != null &&
message.Artists.Any(a => a.Id == t.RemoteAlbum.Artist.Id || a.ForeignArtistId == t.RemoteAlbum.Artist.ForeignArtistId))
.ToList();
var cachedItems = _cache.Values.Where(t =>
t.RemoteAlbum?.Artist != null &&
message.Artists.Select(a => a.Id).Contains(t.RemoteAlbum.Artist.Id))
.ToList();
if (cachedItems.Any())
{

View file

@ -6,7 +6,6 @@ using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Exceptions;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Localization;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RemotePathMappings;
@ -25,9 +24,8 @@ namespace NzbDrone.Core.Download
IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService,
IValidateNzbs nzbValidationService,
ILocalizationService localizationService,
Logger logger)
: base(configService, diskProvider, remotePathMappingService, localizationService, logger)
: base(configService, diskProvider, remotePathMappingService, logger)
{
_httpClient = httpClient;
_nzbValidationService = nzbValidationService;
@ -48,7 +46,6 @@ namespace NzbDrone.Core.Download
{
var request = indexer?.GetDownloadRequest(url) ?? new HttpRequest(url);
request.RateLimitKey = remoteAlbum?.Release?.IndexerId.ToString();
request.AllowAutoRedirect = true;
var response = await RetryStrategy
.ExecuteAsync(static async (state, _) => await state._httpClient.GetAsync(state.request), (_httpClient, request))
@ -60,7 +57,7 @@ namespace NzbDrone.Core.Download
}
catch (HttpException ex)
{
if (ex.Response.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Gone)
if (ex.Response.StatusCode == HttpStatusCode.NotFound)
{
_logger.Error(ex, "Downloading nzb file for album '{0}' failed since it no longer exists ({1})", remoteAlbum.Release.Title, url);
throw new ReleaseUnavailableException(remoteAlbum.Release, "Downloading nzb failed", ex);

View file

@ -10,9 +10,6 @@ namespace NzbDrone.Core.History
{
public const string DOWNLOAD_CLIENT = "downloadClient";
public const string RELEASE_SOURCE = "releaseSource";
public const string RELEASE_GROUP = "releaseGroup";
public const string SIZE = "size";
public const string INDEXER = "indexer";
public EntityHistory()
{

View file

@ -104,7 +104,6 @@ namespace NzbDrone.Core.History
var builder = Builder()
.Join<EntityHistory, Artist>((h, a) => h.ArtistId == a.Id)
.Join<EntityHistory, Album>((h, a) => h.AlbumId == a.Id)
.LeftJoin<EntityHistory, Track>((h, t) => h.TrackId == t.Id)
.Where<EntityHistory>(x => x.Date >= date);
if (eventType.HasValue)
@ -112,11 +111,10 @@ namespace NzbDrone.Core.History
builder.Where<EntityHistory>(h => h.EventType == eventType);
}
return _database.QueryJoined<EntityHistory, Artist, Album, Track>(builder, (history, artist, album, track) =>
return _database.QueryJoined<EntityHistory, Artist, Album>(builder, (history, artist, album) =>
{
history.Artist = artist;
history.Album = album;
history.Track = track;
return history;
}).OrderBy(h => h.Date).ToList();
}

View file

@ -157,7 +157,7 @@ namespace NzbDrone.Core.History
history.Data.Add("Age", message.Album.Release.Age.ToString());
history.Data.Add("AgeHours", message.Album.Release.AgeHours.ToString());
history.Data.Add("AgeMinutes", message.Album.Release.AgeMinutes.ToString());
history.Data.Add("PublishedDate", message.Album.Release.PublishDate.ToUniversalTime().ToString("s") + "Z");
history.Data.Add("PublishedDate", message.Album.Release.PublishDate.ToString("s") + "Z");
history.Data.Add("DownloadClient", message.DownloadClient);
history.Data.Add("Size", message.Album.Release.Size.ToString());
history.Data.Add("DownloadUrl", message.Album.Release.DownloadUrl);
@ -267,9 +267,7 @@ namespace NzbDrone.Core.History
history.Data.Add("DownloadClient", message.DownloadClient);
history.Data.Add("Message", message.Message);
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteAlbum?.ParsedAlbumInfo?.ReleaseGroup ?? message.Data.GetValueOrDefault(EntityHistory.RELEASE_GROUP));
history.Data.Add("Size", message.TrackedDownload?.DownloadItem.TotalSize.ToString() ?? message.Data.GetValueOrDefault(EntityHistory.SIZE));
history.Data.Add("Indexer", message.TrackedDownload?.RemoteAlbum?.Release?.Indexer ?? message.Data.GetValueOrDefault(EntityHistory.INDEXER));
history.Data.Add("Size", message.TrackedDownload?.DownloadItem.TotalSize.ToString());
_historyRepository.Insert(history);
}
@ -419,7 +417,6 @@ namespace NzbDrone.Core.History
history.Data.Add("Message", message.Message);
history.Data.Add("ReleaseGroup", message.TrackedDownload?.RemoteAlbum?.ParsedAlbumInfo?.ReleaseGroup);
history.Data.Add("Size", message.TrackedDownload?.DownloadItem.TotalSize.ToString());
history.Data.Add("Indexer", message.TrackedDownload?.RemoteAlbum?.Release?.Indexer);
historyToAdd.Add(history);
}

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.IndexerSearch.Definitions;
@ -45,11 +44,6 @@ namespace NzbDrone.Core.Indexers.FileList
private IEnumerable<IndexerRequest> GetRequest(string searchType, IEnumerable<int> categories, string parameters)
{
if (categories.Empty())
{
yield break;
}
var categoriesQuery = string.Join(",", categories.Distinct());
var baseUrl = string.Format("{0}/api.php?action={1}&category={2}{3}", Settings.BaseUrl.TrimEnd('/'), searchType, categoriesQuery, parameters);

Some files were not shown because too many files have changed in this diff Show more