mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-10-29 23:12:39 +01:00
Fixed: Various issues with unknown items in queue
This commit is contained in:
parent
7e33261ccc
commit
21a92b62fd
@ -5,7 +5,7 @@ import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import selectAll from 'Utilities/Table/selectAll';
|
||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { align, icons } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
@ -16,6 +16,7 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
|
||||
import QueueOptionsConnector from './QueueOptionsConnector';
|
||||
import QueueRowConnector from './QueueRowConnector';
|
||||
@ -43,16 +44,18 @@ class Queue extends Component {
|
||||
// before episodes start fetching or when episodes start fetching.
|
||||
|
||||
if (
|
||||
(
|
||||
this.props.isFetching &&
|
||||
nextProps.isPopulated &&
|
||||
hasDifferentItems(this.props.items, nextProps.items)
|
||||
) ||
|
||||
(!this.props.isEpisodesFetching && nextProps.isEpisodesFetching)
|
||||
this.props.isFetching &&
|
||||
nextProps.isPopulated &&
|
||||
hasDifferentItems(this.props.items, nextProps.items) &&
|
||||
nextProps.items.some((e) => e.episodeId)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.props.isEpisodesFetching && nextProps.isEpisodesFetching) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -139,7 +142,7 @@ class Queue extends Component {
|
||||
} = this.state;
|
||||
|
||||
const isRefreshing = isFetching || isEpisodesFetching || isCheckForFinishedDownloadExecuting;
|
||||
const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length);
|
||||
const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length || items.every((e) => !e.episodeId));
|
||||
const hasError = error || episodesError;
|
||||
const selectedCount = this.getSelectedIds().length;
|
||||
const disableSelectedActions = selectedCount === 0;
|
||||
@ -173,6 +176,21 @@ class Queue extends Component {
|
||||
onPress={this.onRemoveSelectedPress}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
||||
<PageToolbarSection
|
||||
alignContent={align.RIGHT}
|
||||
>
|
||||
<TableOptionsModalWrapper
|
||||
columns={columns}
|
||||
{...otherProps}
|
||||
optionsComponent={QueueOptionsConnector}
|
||||
>
|
||||
<PageToolbarButton
|
||||
label="Options"
|
||||
iconName={icons.TABLE}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
</PageToolbarSection>
|
||||
</PageToolbar>
|
||||
|
||||
<PageContentBodyConnector>
|
||||
|
@ -10,6 +10,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
|
||||
import EpisodeLanguage from 'Episode/EpisodeLanguage';
|
||||
import EpisodeQuality from 'Episode/EpisodeQuality';
|
||||
import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber';
|
||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||
@ -71,6 +72,7 @@ class QueueRow extends Component {
|
||||
errorMessage,
|
||||
series,
|
||||
episode,
|
||||
language,
|
||||
quality,
|
||||
protocol,
|
||||
indexer,
|
||||
@ -204,6 +206,16 @@ class QueueRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'language') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<EpisodeLanguage
|
||||
language={language}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'quality') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
@ -340,6 +352,7 @@ QueueRow.propTypes = {
|
||||
errorMessage: PropTypes.string,
|
||||
series: PropTypes.object,
|
||||
episode: PropTypes.object,
|
||||
language: PropTypes.object.isRequired,
|
||||
quality: PropTypes.object.isRequired,
|
||||
protocol: PropTypes.string.isRequired,
|
||||
indexer: PropTypes.string,
|
||||
|
@ -1,10 +1,10 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import { icons, scrollDirections } from 'Helpers/Props';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Scroller from 'Components/Scroller/Scroller';
|
||||
import TableOptionsModal from 'Components/Table/TableOptions/TableOptionsModal';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import TableHeader from './TableHeader';
|
||||
import TableHeaderCell from './TableHeaderCell';
|
||||
import TableSelectAllHeaderCell from './TableSelectAllHeaderCell';
|
||||
@ -25,119 +25,88 @@ function getTableHeaderCellProps(props) {
|
||||
}, {});
|
||||
}
|
||||
|
||||
class Table extends Component {
|
||||
function Table(props) {
|
||||
const {
|
||||
className,
|
||||
selectAll,
|
||||
columns,
|
||||
optionsComponent,
|
||||
pageSize,
|
||||
canModifyColumns,
|
||||
children,
|
||||
onSortPress,
|
||||
onTableOptionChange,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
return (
|
||||
<Scroller
|
||||
className={styles.tableContainer}
|
||||
scrollDirection={scrollDirections.HORIZONTAL}
|
||||
>
|
||||
<table className={className}>
|
||||
<TableHeader>
|
||||
{
|
||||
selectAll &&
|
||||
<TableSelectAllHeaderCell {...otherProps} />
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
this.state = {
|
||||
isTableOptionsModalOpen: false
|
||||
};
|
||||
}
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onTableOptionsPress = () => {
|
||||
this.setState({ isTableOptionsModalOpen: true });
|
||||
}
|
||||
|
||||
onTableOptionsModalClose = () => {
|
||||
this.setState({ isTableOptionsModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
className,
|
||||
selectAll,
|
||||
columns,
|
||||
optionsComponent,
|
||||
pageSize,
|
||||
canModifyColumns,
|
||||
children,
|
||||
onSortPress,
|
||||
onTableOptionChange,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Scroller
|
||||
className={styles.tableContainer}
|
||||
scrollDirection={scrollDirections.HORIZONTAL}
|
||||
>
|
||||
<table className={className}>
|
||||
<TableHeader>
|
||||
{
|
||||
selectAll &&
|
||||
<TableSelectAllHeaderCell {...otherProps} />
|
||||
}
|
||||
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((name === 'actions' || name === 'details') && onTableOptionChange) {
|
||||
return (
|
||||
<TableHeaderCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
name={name}
|
||||
isSortable={false}
|
||||
{...otherProps}
|
||||
if (
|
||||
(name === 'actions' || name === 'details') &&
|
||||
onTableOptionChange
|
||||
) {
|
||||
return (
|
||||
<TableHeaderCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
name={name}
|
||||
isSortable={false}
|
||||
{...otherProps}
|
||||
>
|
||||
<TableOptionsModalWrapper
|
||||
columns={columns}
|
||||
optionsComponent={optionsComponent}
|
||||
pageSize={pageSize}
|
||||
canModifyColumns={canModifyColumns}
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
>
|
||||
<IconButton
|
||||
name={icons.ADVANCED_SETTINGS}
|
||||
onPress={this.onTableOptionsPress}
|
||||
/>
|
||||
</TableHeaderCell>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableHeaderCell
|
||||
key={column.name}
|
||||
onSortPress={onSortPress}
|
||||
{...getTableHeaderCellProps(otherProps)}
|
||||
{...column}
|
||||
>
|
||||
{column.label}
|
||||
</TableOptionsModalWrapper>
|
||||
</TableHeaderCell>
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
!!onTableOptionChange &&
|
||||
<TableOptionsModal
|
||||
isOpen={this.state.isTableOptionsModalOpen}
|
||||
columns={columns}
|
||||
optionsComponent={optionsComponent}
|
||||
pageSize={pageSize}
|
||||
canModifyColumns={canModifyColumns}
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
onModalClose={this.onTableOptionsModalClose}
|
||||
/>
|
||||
}
|
||||
return (
|
||||
<TableHeaderCell
|
||||
key={column.name}
|
||||
onSortPress={onSortPress}
|
||||
{...getTableHeaderCellProps(otherProps)}
|
||||
{...column}
|
||||
>
|
||||
{column.label}
|
||||
</TableHeaderCell>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
</TableHeader>
|
||||
{children}
|
||||
</table>
|
||||
</Scroller>
|
||||
);
|
||||
}
|
||||
</TableHeader>
|
||||
{children}
|
||||
</table>
|
||||
</Scroller>
|
||||
);
|
||||
}
|
||||
|
||||
Table.propTypes = {
|
||||
|
@ -0,0 +1,61 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import TableOptionsModal from './TableOptionsModal';
|
||||
|
||||
class TableOptionsModalWrapper extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isTableOptionsModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onTableOptionsPress = () => {
|
||||
this.setState({ isTableOptionsModalOpen: true });
|
||||
}
|
||||
|
||||
onTableOptionsModalClose = () => {
|
||||
this.setState({ isTableOptionsModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
columns,
|
||||
children,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{
|
||||
React.cloneElement(children, { onPress: this.onTableOptionsPress })
|
||||
}
|
||||
|
||||
<TableOptionsModal
|
||||
{...otherProps}
|
||||
isOpen={this.state.isTableOptionsModalOpen}
|
||||
columns={columns}
|
||||
onModalClose={this.onTableOptionsModalClose}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TableOptionsModalWrapper.propTypes = {
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
children: PropTypes.node.isRequired
|
||||
};
|
||||
|
||||
export default TableOptionsModalWrapper;
|
@ -86,6 +86,7 @@ import {
|
||||
faStop as fasStop,
|
||||
faSync as fasSync,
|
||||
faTags as fasTags,
|
||||
faTable as fasTable,
|
||||
faTh as fasTh,
|
||||
faThList as fasThList,
|
||||
faTrashAlt as fasTrashAlt,
|
||||
@ -188,6 +189,7 @@ export const SORT_DESCENDING = fasSortDown;
|
||||
export const SPINNER = fasSpinner;
|
||||
export const SUBTRACT = fasMinus;
|
||||
export const SYSTEM = fasLaptop;
|
||||
export const TABLE = fasTable;
|
||||
export const TAGS = fasTags;
|
||||
export const TBA = fasQuestionCircle;
|
||||
export const TEST = fasVial;
|
||||
|
@ -1,108 +1,78 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||
import TableOptionsModal from 'Components/Table/TableOptions/TableOptionsModal';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import SeriesIndexTableOptionsConnector from './SeriesIndexTableOptionsConnector';
|
||||
import styles from './SeriesIndexHeader.css';
|
||||
|
||||
class SeriesIndexHeader extends Component {
|
||||
function SeriesIndexHeader(props) {
|
||||
const {
|
||||
showBanners,
|
||||
columns,
|
||||
onTableOptionChange,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
return (
|
||||
<VirtualTableHeader>
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
isSortable,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isTableOptionsModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onTableOptionsPress = () => {
|
||||
this.setState({ isTableOptionsModalOpen: true });
|
||||
}
|
||||
|
||||
onTableOptionsModalClose = () => {
|
||||
this.setState({ isTableOptionsModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
showBanners,
|
||||
columns,
|
||||
onTableOptionChange,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<VirtualTableHeader>
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
label,
|
||||
isSortable,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'actions') {
|
||||
return (
|
||||
<VirtualTableHeaderCell
|
||||
key={name}
|
||||
className={styles[name]}
|
||||
name={name}
|
||||
isSortable={false}
|
||||
{...otherProps}
|
||||
>
|
||||
<IconButton
|
||||
name={icons.ADVANCED_SETTINGS}
|
||||
onPress={this.onTableOptionsPress}
|
||||
/>
|
||||
</VirtualTableHeaderCell>
|
||||
);
|
||||
}
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'actions') {
|
||||
return (
|
||||
<VirtualTableHeaderCell
|
||||
key={name}
|
||||
className={classNames(
|
||||
styles[name],
|
||||
name === 'sortTitle' && showBanners && styles.banner
|
||||
)}
|
||||
className={styles[name]}
|
||||
name={name}
|
||||
isSortable={isSortable}
|
||||
isSortable={false}
|
||||
{...otherProps}
|
||||
>
|
||||
{label}
|
||||
|
||||
<TableOptionsModalWrapper
|
||||
columns={columns}
|
||||
optionsComponent={SeriesIndexTableOptionsConnector}
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
>
|
||||
<IconButton
|
||||
name={icons.ADVANCED_SETTINGS}
|
||||
/>
|
||||
</TableOptionsModalWrapper>
|
||||
</VirtualTableHeaderCell>
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
<TableOptionsModal
|
||||
isOpen={this.state.isTableOptionsModalOpen}
|
||||
columns={columns}
|
||||
optionsComponent={SeriesIndexTableOptionsConnector}
|
||||
onTableOptionChange={onTableOptionChange}
|
||||
onModalClose={this.onTableOptionsModalClose}
|
||||
/>
|
||||
</VirtualTableHeader>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<VirtualTableHeaderCell
|
||||
key={name}
|
||||
className={classNames(
|
||||
styles[name],
|
||||
name === 'sortTitle' && showBanners && styles.banner
|
||||
)}
|
||||
name={name}
|
||||
isSortable={isSortable}
|
||||
{...otherProps}
|
||||
>
|
||||
{label}
|
||||
</VirtualTableHeaderCell>
|
||||
);
|
||||
})
|
||||
}
|
||||
</VirtualTableHeader>
|
||||
);
|
||||
}
|
||||
|
||||
SeriesIndexHeader.propTypes = {
|
||||
|
@ -84,6 +84,12 @@ export const defaultState = {
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'language',
|
||||
label: 'Language',
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: 'Quality',
|
||||
|
@ -10,7 +10,11 @@ function createQueueItemSelector() {
|
||||
}
|
||||
|
||||
return details.find((item) => {
|
||||
return item.episode.id === episodeId;
|
||||
if (item.episode) {
|
||||
return item.episode.id === episodeId;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
@ -124,11 +124,10 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||
}
|
||||
}
|
||||
|
||||
// Track it so it can be displayed in the queue even though we can't determine which serires it is for
|
||||
if (trackedDownload.RemoteEpisode == null)
|
||||
{
|
||||
_logger.Trace("No Episode found for download '{0}', not tracking.", trackedDownload.DownloadItem.Title);
|
||||
|
||||
return null;
|
||||
_logger.Trace("No Episode found for download '{0}'", trackedDownload.DownloadItem.Title);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Lifecycle;
|
||||
@ -16,6 +16,7 @@ namespace NzbDrone.Core.Profiles.Languages
|
||||
List<LanguageProfile> All();
|
||||
LanguageProfile Get(int id);
|
||||
bool Exists(int id);
|
||||
LanguageProfile GetDefaultProfile(string name, Language cutoff = null, params Language[] allowed);
|
||||
}
|
||||
|
||||
public class LanguageProfileService : ILanguageProfileService, IHandle<ApplicationStartedEvent>
|
||||
@ -66,6 +67,25 @@ namespace NzbDrone.Core.Profiles.Languages
|
||||
return _profileRepository.Exists(id);
|
||||
}
|
||||
|
||||
public LanguageProfile GetDefaultProfile(string name, Language cutoff = null, params Language[] allowed)
|
||||
{
|
||||
var orderedLanguages = Language.All
|
||||
.Where(l => l != Language.Unknown)
|
||||
.OrderByDescending(l => l.Name)
|
||||
.ToList();
|
||||
|
||||
orderedLanguages.Insert(0, Language.Unknown);
|
||||
|
||||
var languages = orderedLanguages.Select(v => new LanguageProfileItem { Language = v, Allowed = false })
|
||||
.ToList();
|
||||
|
||||
return new LanguageProfile
|
||||
{
|
||||
Cutoff = Language.Unknown,
|
||||
Languages = languages
|
||||
};
|
||||
}
|
||||
|
||||
private LanguageProfile AddDefaultProfile(string name, Language cutoff, params Language[] allowed)
|
||||
{
|
||||
var languages = Language.All
|
||||
@ -92,4 +112,4 @@ namespace NzbDrone.Core.Profiles.Languages
|
||||
AddDefaultProfile("English", Language.English, Language.English);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
@ -13,6 +14,7 @@ namespace NzbDrone.Core.Queue
|
||||
{
|
||||
public Series Series { get; set; }
|
||||
public Episode Episode { get; set; }
|
||||
public Language Language { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public decimal Size { get; set; }
|
||||
public string Title { get; set; }
|
||||
|
@ -3,7 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Crypto;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Queue
|
||||
@ -42,7 +44,7 @@ namespace NzbDrone.Core.Queue
|
||||
|
||||
private IEnumerable<Queue> MapQueue(TrackedDownload trackedDownload)
|
||||
{
|
||||
if (trackedDownload.RemoteEpisode.Episodes != null && trackedDownload.RemoteEpisode.Episodes.Any())
|
||||
if (trackedDownload.RemoteEpisode?.Episodes != null && trackedDownload.RemoteEpisode.Episodes.Any())
|
||||
{
|
||||
foreach (var episode in trackedDownload.RemoteEpisode.Episodes)
|
||||
{
|
||||
@ -59,9 +61,10 @@ namespace NzbDrone.Core.Queue
|
||||
{
|
||||
var queue = new Queue
|
||||
{
|
||||
Series = trackedDownload.RemoteEpisode.Series,
|
||||
Series = trackedDownload.RemoteEpisode?.Series,
|
||||
Episode = episode,
|
||||
Quality = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Quality,
|
||||
Language = trackedDownload.RemoteEpisode?.ParsedEpisodeInfo.Language ?? Language.Unknown,
|
||||
Quality = trackedDownload.RemoteEpisode?.ParsedEpisodeInfo.Quality ?? new QualityModel(Quality.Unknown),
|
||||
Title = Parser.Parser.RemoveFileExtension(trackedDownload.DownloadItem.Title),
|
||||
Size = trackedDownload.DownloadItem.TotalSize,
|
||||
Sizeleft = trackedDownload.DownloadItem.RemainingSize,
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Profiles.Languages;
|
||||
using Sonarr.Http;
|
||||
|
||||
@ -6,32 +5,19 @@ namespace Sonarr.Api.V3.Profiles.Language
|
||||
{
|
||||
public class LanguageProfileSchemaModule : SonarrRestModule<LanguageProfileResource>
|
||||
{
|
||||
private readonly LanguageProfileService _languageProfileService;
|
||||
|
||||
public LanguageProfileSchemaModule()
|
||||
public LanguageProfileSchemaModule(LanguageProfileService languageProfileService)
|
||||
: base("/languageprofile/schema")
|
||||
{
|
||||
_languageProfileService = languageProfileService;
|
||||
GetResourceSingle = GetAll;
|
||||
}
|
||||
|
||||
private LanguageProfileResource GetAll()
|
||||
{
|
||||
var orderedLanguages = NzbDrone.Core.Languages.Language.All
|
||||
.Where(l => l != NzbDrone.Core.Languages.Language.Unknown)
|
||||
.OrderByDescending(l => l.Name)
|
||||
.ToList();
|
||||
|
||||
orderedLanguages.Insert(0, NzbDrone.Core.Languages.Language.Unknown);
|
||||
|
||||
var languages = orderedLanguages.Select(v => new LanguageProfileItem {Language = v, Allowed = false})
|
||||
.ToList();
|
||||
|
||||
var profile = new LanguageProfile
|
||||
{
|
||||
Cutoff = NzbDrone.Core.Languages.Language.Unknown,
|
||||
Languages = languages
|
||||
};
|
||||
|
||||
var profile = _languageProfileService.GetDefaultProfile(string.Empty);
|
||||
return profile.ToResource();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.Download.Pending;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Profiles.Languages;
|
||||
using NzbDrone.Core.Profiles.Qualities;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Queue;
|
||||
using NzbDrone.SignalR;
|
||||
using Sonarr.Http;
|
||||
@ -18,18 +21,23 @@ namespace Sonarr.Api.V3.Queue
|
||||
{
|
||||
private readonly IQueueService _queueService;
|
||||
private readonly IPendingReleaseService _pendingReleaseService;
|
||||
private readonly IConfigService _configService;
|
||||
|
||||
private readonly LanguageComparer LANGUAGE_COMPARER;
|
||||
private readonly QualityModelComparer QUALITY_COMPARER;
|
||||
|
||||
public QueueModule(IBroadcastSignalRMessage broadcastSignalRMessage,
|
||||
IQueueService queueService,
|
||||
IPendingReleaseService pendingReleaseService,
|
||||
IConfigService configService)
|
||||
ILanguageProfileService languageProfileService,
|
||||
QualityProfileService qualityProfileService)
|
||||
: base(broadcastSignalRMessage)
|
||||
{
|
||||
_queueService = queueService;
|
||||
_pendingReleaseService = pendingReleaseService;
|
||||
_configService = configService;
|
||||
GetResourcePaged = GetQueue;
|
||||
|
||||
LANGUAGE_COMPARER = new LanguageComparer(languageProfileService.GetDefaultProfile(string.Empty));
|
||||
QUALITY_COMPARER = new QualityModelComparer(qualityProfileService.GetDefaultProfile(string.Empty));
|
||||
}
|
||||
|
||||
private PagingResource<QueueResource> GetQueue(PagingResource<QueueResource> pagingResource)
|
||||
@ -55,38 +63,60 @@ namespace Sonarr.Api.V3.Queue
|
||||
|
||||
if (pagingSpec.SortKey == "episode")
|
||||
{
|
||||
ordered = ascending ? fullQueue.OrderBy(q => q.Episode.SeasonNumber).ThenBy(q => q.Episode.EpisodeNumber) :
|
||||
fullQueue.OrderByDescending(q => q.Episode.SeasonNumber).ThenByDescending(q => q.Episode.EpisodeNumber);
|
||||
ordered = ascending
|
||||
? fullQueue.OrderBy(q => q.Episode?.SeasonNumber).ThenBy(q => q.Episode?.EpisodeNumber)
|
||||
: fullQueue.OrderByDescending(q => q.Episode?.SeasonNumber)
|
||||
.ThenByDescending(q => q.Episode?.EpisodeNumber);
|
||||
}
|
||||
|
||||
else if (pagingSpec.SortKey == "timeleft")
|
||||
{
|
||||
ordered = ascending ? fullQueue.OrderBy(q => q.Timeleft, new TimeleftComparer()) :
|
||||
fullQueue.OrderByDescending(q => q.Timeleft, new TimeleftComparer());
|
||||
ordered = ascending
|
||||
? fullQueue.OrderBy(q => q.Timeleft, new TimeleftComparer())
|
||||
: fullQueue.OrderByDescending(q => q.Timeleft, new TimeleftComparer());
|
||||
}
|
||||
|
||||
else if (pagingSpec.SortKey == "estimatedCompletionTime")
|
||||
{
|
||||
ordered = ascending ? fullQueue.OrderBy(q => q.EstimatedCompletionTime, new EstimatedCompletionTimeComparer()) :
|
||||
fullQueue.OrderByDescending(q => q.EstimatedCompletionTime, new EstimatedCompletionTimeComparer());
|
||||
ordered = ascending
|
||||
? fullQueue.OrderBy(q => q.EstimatedCompletionTime, new EstimatedCompletionTimeComparer())
|
||||
: fullQueue.OrderByDescending(q => q.EstimatedCompletionTime,
|
||||
new EstimatedCompletionTimeComparer());
|
||||
}
|
||||
|
||||
else if (pagingSpec.SortKey == "protocol")
|
||||
{
|
||||
ordered = ascending ? fullQueue.OrderBy(q => q.Protocol) :
|
||||
fullQueue.OrderByDescending(q => q.Protocol);
|
||||
ordered = ascending
|
||||
? fullQueue.OrderBy(q => q.Protocol)
|
||||
: fullQueue.OrderByDescending(q => q.Protocol);
|
||||
}
|
||||
|
||||
else if (pagingSpec.SortKey == "indexer")
|
||||
{
|
||||
ordered = ascending ? fullQueue.OrderBy(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase) :
|
||||
fullQueue.OrderByDescending(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase);
|
||||
ordered = ascending
|
||||
? fullQueue.OrderBy(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase)
|
||||
: fullQueue.OrderByDescending(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
else if (pagingSpec.SortKey == "downloadClient")
|
||||
{
|
||||
ordered = ascending ? fullQueue.OrderBy(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase) :
|
||||
fullQueue.OrderByDescending(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase);
|
||||
ordered = ascending
|
||||
? fullQueue.OrderBy(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase)
|
||||
: fullQueue.OrderByDescending(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
else if (pagingSpec.SortKey == "language")
|
||||
{
|
||||
ordered = ascending
|
||||
? fullQueue.OrderBy(q => q.Language, LANGUAGE_COMPARER)
|
||||
: fullQueue.OrderByDescending(q => q.Language, LANGUAGE_COMPARER);
|
||||
}
|
||||
|
||||
else if (pagingSpec.SortKey == "quality")
|
||||
{
|
||||
ordered = ascending
|
||||
? fullQueue.OrderBy(q => q.Quality, QUALITY_COMPARER)
|
||||
: fullQueue.OrderByDescending(q => q.Quality, QUALITY_COMPARER);
|
||||
}
|
||||
|
||||
else
|
||||
@ -113,13 +143,15 @@ namespace Sonarr.Api.V3.Queue
|
||||
switch (pagingSpec.SortKey)
|
||||
{
|
||||
case "series.sortTitle":
|
||||
return q => q.Series.SortTitle;
|
||||
return q => q.Series?.SortTitle;
|
||||
case "episode":
|
||||
return q => q.Episode;
|
||||
case "episode.airDateUtc":
|
||||
return q => q.Episode.AirDateUtc;
|
||||
case "episode.title":
|
||||
return q => q.Episode.Title;
|
||||
case "language":
|
||||
return q => q.Language;
|
||||
case "quality":
|
||||
return q => q.Quality;
|
||||
case "progress":
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using Sonarr.Api.V3.Episodes;
|
||||
using Sonarr.Api.V3.Series;
|
||||
@ -16,6 +17,7 @@ namespace Sonarr.Api.V3.Queue
|
||||
public int? EpisodeId { get; set; }
|
||||
public SeriesResource Series { get; set; }
|
||||
public EpisodeResource Episode { get; set; }
|
||||
public Language Language { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public decimal Size { get; set; }
|
||||
public string Title { get; set; }
|
||||
@ -45,6 +47,7 @@ namespace Sonarr.Api.V3.Queue
|
||||
EpisodeId = model.Episode?.Id,
|
||||
Series = includeSeries && model.Series != null ? model.Series.ToResource() : null,
|
||||
Episode = includeEpisode && model.Episode != null ? model.Episode.ToResource() : null,
|
||||
Language = model.Language,
|
||||
Quality = model.Quality,
|
||||
Size = model.Size,
|
||||
Title = model.Title,
|
||||
|
Loading…
Reference in New Issue
Block a user