parent
aceaaa10e1
commit
311cd66fcd
|
@ -8,6 +8,7 @@ import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import EpisodeDetailsModal from 'Episode/EpisodeDetailsModal';
|
import EpisodeDetailsModal from 'Episode/EpisodeDetailsModal';
|
||||||
import episodeEntities from 'Episode/episodeEntities';
|
import episodeEntities from 'Episode/episodeEntities';
|
||||||
|
import getFinaleTypeName from 'Episode/getFinaleTypeName';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import formatTime from 'Utilities/Date/formatTime';
|
import formatTime from 'Utilities/Date/formatTime';
|
||||||
import padNumber from 'Utilities/Number/padNumber';
|
import padNumber from 'Utilities/Number/padNumber';
|
||||||
|
@ -52,6 +53,7 @@ class AgendaEvent extends Component {
|
||||||
airDateUtc,
|
airDateUtc,
|
||||||
monitored,
|
monitored,
|
||||||
unverifiedSceneNumbering,
|
unverifiedSceneNumbering,
|
||||||
|
finaleType,
|
||||||
hasFile,
|
hasFile,
|
||||||
grabbed,
|
grabbed,
|
||||||
queueItem,
|
queueItem,
|
||||||
|
@ -71,8 +73,6 @@ class AgendaEvent extends Component {
|
||||||
const isMonitored = series.monitored && monitored;
|
const isMonitored = series.monitored && monitored;
|
||||||
const statusStyle = getStatusStyle(hasFile, downloading, startTime, endTime, isMonitored);
|
const statusStyle = getStatusStyle(hasFile, downloading, startTime, endTime, isMonitored);
|
||||||
const missingAbsoluteNumber = series.seriesType === 'anime' && seasonNumber > 0 && !absoluteEpisodeNumber;
|
const missingAbsoluteNumber = series.seriesType === 'anime' && seasonNumber > 0 && !absoluteEpisodeNumber;
|
||||||
const season = series.seasons.find((s) => s.seasonNumber === seasonNumber);
|
|
||||||
const seasonStatistics = season?.statistics || {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.event}>
|
<div className={styles.event}>
|
||||||
|
@ -189,15 +189,14 @@ class AgendaEvent extends Component {
|
||||||
|
|
||||||
{
|
{
|
||||||
showFinaleIcon &&
|
showFinaleIcon &&
|
||||||
episodeNumber !== 1 &&
|
finaleType ?
|
||||||
seasonNumber > 0 &&
|
|
||||||
episodeNumber === seasonStatistics.totalEpisodeCount &&
|
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.statusIcon}
|
className={styles.statusIcon}
|
||||||
name={icons.INFO}
|
name={icons.INFO}
|
||||||
kind={kinds.WARNING}
|
kind={kinds.WARNING}
|
||||||
title={series.status === 'ended' ? translate('SeriesFinale') : translate('SeasonFinale')}
|
title={getFinaleTypeName(finaleType)}
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -238,6 +237,7 @@ AgendaEvent.propTypes = {
|
||||||
airDateUtc: PropTypes.string.isRequired,
|
airDateUtc: PropTypes.string.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
unverifiedSceneNumbering: PropTypes.bool,
|
unverifiedSceneNumbering: PropTypes.bool,
|
||||||
|
finaleType: PropTypes.string,
|
||||||
hasFile: PropTypes.bool.isRequired,
|
hasFile: PropTypes.bool.isRequired,
|
||||||
grabbed: PropTypes.bool,
|
grabbed: PropTypes.bool,
|
||||||
queueItem: PropTypes.object,
|
queueItem: PropTypes.object,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import EpisodeDetailsModal from 'Episode/EpisodeDetailsModal';
|
import EpisodeDetailsModal from 'Episode/EpisodeDetailsModal';
|
||||||
import episodeEntities from 'Episode/episodeEntities';
|
import episodeEntities from 'Episode/episodeEntities';
|
||||||
|
import getFinaleTypeName from 'Episode/getFinaleTypeName';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import formatTime from 'Utilities/Date/formatTime';
|
import formatTime from 'Utilities/Date/formatTime';
|
||||||
import padNumber from 'Utilities/Number/padNumber';
|
import padNumber from 'Utilities/Number/padNumber';
|
||||||
|
@ -57,6 +58,7 @@ class CalendarEvent extends Component {
|
||||||
airDateUtc,
|
airDateUtc,
|
||||||
monitored,
|
monitored,
|
||||||
unverifiedSceneNumbering,
|
unverifiedSceneNumbering,
|
||||||
|
finaleType,
|
||||||
hasFile,
|
hasFile,
|
||||||
grabbed,
|
grabbed,
|
||||||
queueItem,
|
queueItem,
|
||||||
|
@ -79,8 +81,6 @@ class CalendarEvent extends Component {
|
||||||
const isMonitored = series.monitored && monitored;
|
const isMonitored = series.monitored && monitored;
|
||||||
const statusStyle = getStatusStyle(hasFile, isDownloading, startTime, endTime, isMonitored);
|
const statusStyle = getStatusStyle(hasFile, isDownloading, startTime, endTime, isMonitored);
|
||||||
const missingAbsoluteNumber = series.seriesType === 'anime' && seasonNumber > 0 && !absoluteEpisodeNumber;
|
const missingAbsoluteNumber = series.seriesType === 'anime' && seasonNumber > 0 && !absoluteEpisodeNumber;
|
||||||
const season = series.seasons.find((s) => s.seasonNumber === seasonNumber);
|
|
||||||
const seasonStatistics = season?.statistics || {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -170,14 +170,12 @@ class CalendarEvent extends Component {
|
||||||
|
|
||||||
{
|
{
|
||||||
showFinaleIcon &&
|
showFinaleIcon &&
|
||||||
episodeNumber !== 1 &&
|
finaleType ?
|
||||||
seasonNumber > 0 &&
|
|
||||||
episodeNumber === seasonStatistics.totalEpisodeCount ?
|
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.statusIcon}
|
className={styles.statusIcon}
|
||||||
name={icons.INFO}
|
name={icons.INFO}
|
||||||
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||||
title={series.status === 'ended' ? translate('SeriesFinale') : translate('SeasonFinale')}
|
title={getFinaleTypeName(finaleType)}
|
||||||
/> :
|
/> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
@ -247,6 +245,7 @@ CalendarEvent.propTypes = {
|
||||||
airDateUtc: PropTypes.string.isRequired,
|
airDateUtc: PropTypes.string.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
unverifiedSceneNumbering: PropTypes.bool,
|
unverifiedSceneNumbering: PropTypes.bool,
|
||||||
|
finaleType: PropTypes.string,
|
||||||
hasFile: PropTypes.bool.isRequired,
|
hasFile: PropTypes.bool.isRequired,
|
||||||
grabbed: PropTypes.bool,
|
grabbed: PropTypes.bool,
|
||||||
queueItem: PropTypes.object,
|
queueItem: PropTypes.object,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import CalendarEventConnector from 'Calendar/Events/CalendarEventConnector';
|
||||||
import getStatusStyle from 'Calendar/getStatusStyle';
|
import getStatusStyle from 'Calendar/getStatusStyle';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
|
import getFinaleTypeName from 'Episode/getFinaleTypeName';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import formatTime from 'Utilities/Date/formatTime';
|
import formatTime from 'Utilities/Date/formatTime';
|
||||||
import padNumber from 'Utilities/Number/padNumber';
|
import padNumber from 'Utilities/Number/padNumber';
|
||||||
|
@ -175,15 +176,13 @@ class CalendarEventGroup extends Component {
|
||||||
|
|
||||||
{
|
{
|
||||||
showFinaleIcon &&
|
showFinaleIcon &&
|
||||||
lastEpisode.episodeNumber !== 1 &&
|
lastEpisode.finaleType ?
|
||||||
seasonNumber > 0 &&
|
|
||||||
lastEpisode.episodeNumber === series.seasons.find((season) => season.seasonNumber === seasonNumber).statistics.totalEpisodeCount &&
|
|
||||||
<Icon
|
<Icon
|
||||||
containerClassName={styles.statusIcon}
|
containerClassName={styles.statusIcon}
|
||||||
name={icons.INFO}
|
name={icons.INFO}
|
||||||
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
kind={fullColorEvents ? kinds.DEFAULT : kinds.WARNING}
|
||||||
title={series.status === 'ended' ? translate('SeriesFinale') : translate('SeasonFinale')}
|
title={getFinaleTypeName(lastEpisode.finaleType)}
|
||||||
/>
|
/> : null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
composes: link from '~Components/Link/Link.css';
|
composes: link from '~Components/Link/Link.css';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// This file is automatically generated.
|
// This file is automatically generated.
|
||||||
// Please do not change this file!
|
// Please do not change this file!
|
||||||
interface CssExports {
|
interface CssExports {
|
||||||
|
'container': string;
|
||||||
'link': string;
|
'link': string;
|
||||||
}
|
}
|
||||||
export const cssExports: CssExports;
|
export const cssExports: CssExports;
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import Link from 'Components/Link/Link';
|
|
||||||
import EpisodeDetailsModal from 'Episode/EpisodeDetailsModal';
|
|
||||||
import styles from './EpisodeTitleLink.css';
|
|
||||||
|
|
||||||
class EpisodeTitleLink extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isDetailsModalOpen: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onLinkPress = () => {
|
|
||||||
this.setState({ isDetailsModalOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onModalClose = () => {
|
|
||||||
this.setState({ isDetailsModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
episodeTitle,
|
|
||||||
...otherProps
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Link
|
|
||||||
className={styles.link}
|
|
||||||
onPress={this.onLinkPress}
|
|
||||||
>
|
|
||||||
{episodeTitle}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<EpisodeDetailsModal
|
|
||||||
isOpen={this.state.isDetailsModalOpen}
|
|
||||||
episodeTitle={episodeTitle}
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={this.onModalClose}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EpisodeTitleLink.propTypes = {
|
|
||||||
episodeTitle: PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
EpisodeTitleLink.defaultProps = {
|
|
||||||
showSeriesButton: false
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EpisodeTitleLink;
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import Link from 'Components/Link/Link';
|
||||||
|
import EpisodeDetailsModal from 'Episode/EpisodeDetailsModal';
|
||||||
|
import FinaleType from './FinaleType';
|
||||||
|
import styles from './EpisodeTitleLink.css';
|
||||||
|
|
||||||
|
interface EpisodeTitleLinkProps {
|
||||||
|
episodeTitle: string;
|
||||||
|
finaleType?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function EpisodeTitleLink(props: EpisodeTitleLinkProps) {
|
||||||
|
const { episodeTitle, finaleType, ...otherProps } = props;
|
||||||
|
const [isDetailsModalOpen, setIsDetailsModalOpen] = useState(false);
|
||||||
|
const handleLinkPress = useCallback(() => {
|
||||||
|
setIsDetailsModalOpen(true);
|
||||||
|
}, [setIsDetailsModalOpen]);
|
||||||
|
const handleModalClose = useCallback(() => {
|
||||||
|
setIsDetailsModalOpen(false);
|
||||||
|
}, [setIsDetailsModalOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Link className={styles.link} onPress={handleLinkPress}>
|
||||||
|
{episodeTitle}
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{finaleType ? <FinaleType finaleType={finaleType} /> : null}
|
||||||
|
|
||||||
|
<EpisodeDetailsModal
|
||||||
|
isOpen={isDetailsModalOpen}
|
||||||
|
episodeTitle={episodeTitle}
|
||||||
|
{...otherProps}
|
||||||
|
onModalClose={handleModalClose}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
EpisodeTitleLink.propTypes = {
|
||||||
|
episodeTitle: PropTypes.string.isRequired,
|
||||||
|
finaleType: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EpisodeTitleLink;
|
|
@ -0,0 +1,5 @@
|
||||||
|
.label {
|
||||||
|
composes: label from '~Components/Label.css';
|
||||||
|
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'label': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import Label from 'Components/Label';
|
||||||
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import getFinaleTypeName from './getFinaleTypeName';
|
||||||
|
import styles from './FinaleType.css';
|
||||||
|
|
||||||
|
interface SeriesStatusCellProps {
|
||||||
|
finaleType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FinaleType(props: SeriesStatusCellProps) {
|
||||||
|
const { finaleType } = props;
|
||||||
|
|
||||||
|
const finaleText = useMemo(() => {
|
||||||
|
return getFinaleTypeName(finaleType);
|
||||||
|
}, [finaleType]);
|
||||||
|
|
||||||
|
if (finaleType == null || finaleText == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Label className={styles.label} kind={kinds.INFO}>
|
||||||
|
{finaleText}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FinaleType;
|
|
@ -0,0 +1,14 @@
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
|
export default function getFinaleTypeName(finaleType?: string): string | null {
|
||||||
|
switch (finaleType) {
|
||||||
|
case 'series':
|
||||||
|
return translate('SeriesFinale');
|
||||||
|
case 'season':
|
||||||
|
return translate('SeasonFinale');
|
||||||
|
case 'midseason':
|
||||||
|
return translate('MidseasonFinale');
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,6 +64,7 @@ class EpisodeRow extends Component {
|
||||||
sceneAbsoluteEpisodeNumber,
|
sceneAbsoluteEpisodeNumber,
|
||||||
airDateUtc,
|
airDateUtc,
|
||||||
runtime,
|
runtime,
|
||||||
|
finaleType,
|
||||||
title,
|
title,
|
||||||
useSceneNumbering,
|
useSceneNumbering,
|
||||||
unverifiedSceneNumbering,
|
unverifiedSceneNumbering,
|
||||||
|
@ -141,6 +142,7 @@ class EpisodeRow extends Component {
|
||||||
episodeId={id}
|
episodeId={id}
|
||||||
seriesId={seriesId}
|
seriesId={seriesId}
|
||||||
episodeTitle={title}
|
episodeTitle={title}
|
||||||
|
finaleType={finaleType}
|
||||||
showOpenSeriesButton={false}
|
showOpenSeriesButton={false}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
|
@ -366,6 +368,7 @@ EpisodeRow.propTypes = {
|
||||||
sceneAbsoluteEpisodeNumber: PropTypes.number,
|
sceneAbsoluteEpisodeNumber: PropTypes.number,
|
||||||
airDateUtc: PropTypes.string,
|
airDateUtc: PropTypes.string,
|
||||||
runtime: PropTypes.number,
|
runtime: PropTypes.number,
|
||||||
|
finaleType: PropTypes.string,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
isSaving: PropTypes.bool,
|
isSaving: PropTypes.bool,
|
||||||
useSceneNumbering: PropTypes.bool,
|
useSceneNumbering: PropTypes.bool,
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(196)]
|
||||||
|
public class add_finale_type : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("Episodes").AddColumn("FinaleType").AsString().Nullable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -693,6 +693,7 @@
|
||||||
"MetadataSource": "Metadata Source",
|
"MetadataSource": "Metadata Source",
|
||||||
"MetadataSourceSettings": "Metadata Source Settings",
|
"MetadataSourceSettings": "Metadata Source Settings",
|
||||||
"MetadataSourceSettingsSummary": "Information on where Sonarr gets series and episode information",
|
"MetadataSourceSettingsSummary": "Information on where Sonarr gets series and episode information",
|
||||||
|
"MidseasonFinale": "Midseason Finale",
|
||||||
"Min": "Min",
|
"Min": "Min",
|
||||||
"MinimumAge": "Minimum Age",
|
"MinimumAge": "Minimum Age",
|
||||||
"MinimumAgeHelpText": "Usenet only: Minimum age in minutes of NZBs before they are grabbed. Use this to give new releases time to propagate to your usenet provider.",
|
"MinimumAgeHelpText": "Usenet only: Minimum age in minutes of NZBs before they are grabbed. Use this to give new releases time to propagate to your usenet provider.",
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
||||||
public string AirDate { get; set; }
|
public string AirDate { get; set; }
|
||||||
public DateTime? AirDateUtc { get; set; }
|
public DateTime? AirDateUtc { get; set; }
|
||||||
public int Runtime { get; set; }
|
public int Runtime { get; set; }
|
||||||
|
public string FinaleType { get; set; }
|
||||||
public RatingResource Rating { get; set; }
|
public RatingResource Rating { get; set; }
|
||||||
public string Overview { get; set; }
|
public string Overview { get; set; }
|
||||||
public string Image { get; set; }
|
public string Image { get; set; }
|
||||||
|
|
|
@ -261,6 +261,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
episode.AirDate = oracleEpisode.AirDate;
|
episode.AirDate = oracleEpisode.AirDate;
|
||||||
episode.AirDateUtc = oracleEpisode.AirDateUtc;
|
episode.AirDateUtc = oracleEpisode.AirDateUtc;
|
||||||
episode.Runtime = oracleEpisode.Runtime;
|
episode.Runtime = oracleEpisode.Runtime;
|
||||||
|
episode.FinaleType = oracleEpisode.FinaleType;
|
||||||
|
|
||||||
episode.Ratings = MapRatings(oracleEpisode.Rating);
|
episode.Ratings = MapRatings(oracleEpisode.Rating);
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ namespace NzbDrone.Core.Tv
|
||||||
public List<MediaCover.MediaCover> Images { get; set; }
|
public List<MediaCover.MediaCover> Images { get; set; }
|
||||||
public DateTime? LastSearchTime { get; set; }
|
public DateTime? LastSearchTime { get; set; }
|
||||||
public int Runtime { get; set; }
|
public int Runtime { get; set; }
|
||||||
|
public string FinaleType { get; set; }
|
||||||
|
|
||||||
public string SeriesTitle { get; private set; }
|
public string SeriesTitle { get; private set; }
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,10 @@ namespace NzbDrone.Core.Tv
|
||||||
dupeFreeRemoteEpisodes = MapAbsoluteEpisodeNumbers(dupeFreeRemoteEpisodes);
|
dupeFreeRemoteEpisodes = MapAbsoluteEpisodeNumbers(dupeFreeRemoteEpisodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var episode in OrderEpisodes(series, dupeFreeRemoteEpisodes))
|
var orderedEpisodes = OrderEpisodes(series, dupeFreeRemoteEpisodes).ToList();
|
||||||
|
var episodesPerSeason = orderedEpisodes.GroupBy(s => s.SeasonNumber).ToDictionary(g => g.Key, g => g.Count());
|
||||||
|
|
||||||
|
foreach (var episode in orderedEpisodes)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -76,9 +79,16 @@ namespace NzbDrone.Core.Tv
|
||||||
episodeToUpdate.AirDate = episode.AirDate;
|
episodeToUpdate.AirDate = episode.AirDate;
|
||||||
episodeToUpdate.AirDateUtc = episode.AirDateUtc;
|
episodeToUpdate.AirDateUtc = episode.AirDateUtc;
|
||||||
episodeToUpdate.Runtime = episode.Runtime;
|
episodeToUpdate.Runtime = episode.Runtime;
|
||||||
|
episodeToUpdate.FinaleType = episode.FinaleType;
|
||||||
episodeToUpdate.Ratings = episode.Ratings;
|
episodeToUpdate.Ratings = episode.Ratings;
|
||||||
episodeToUpdate.Images = episode.Images;
|
episodeToUpdate.Images = episode.Images;
|
||||||
|
|
||||||
|
// TheTVDB has a severe lack of season/series finales, this helps smooth out that limitation so they can be displayed in the UI
|
||||||
|
if (episodeToUpdate.FinaleType == null && episodeToUpdate.SeasonNumber > 0 && episodeToUpdate.EpisodeNumber > 1 && episodeToUpdate.EpisodeNumber == episodesPerSeason[episodeToUpdate.SeasonNumber])
|
||||||
|
{
|
||||||
|
episodeToUpdate.FinaleType = series.Status == SeriesStatusType.Ended ? "series" : "season";
|
||||||
|
}
|
||||||
|
|
||||||
successCount++;
|
successCount++;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
|
@ -21,6 +21,7 @@ namespace Sonarr.Api.V3.Episodes
|
||||||
public string AirDate { get; set; }
|
public string AirDate { get; set; }
|
||||||
public DateTime? AirDateUtc { get; set; }
|
public DateTime? AirDateUtc { get; set; }
|
||||||
public int Runtime { get; set; }
|
public int Runtime { get; set; }
|
||||||
|
public string FinaleType { get; set; }
|
||||||
public string Overview { get; set; }
|
public string Overview { get; set; }
|
||||||
public EpisodeFileResource EpisodeFile { get; set; }
|
public EpisodeFileResource EpisodeFile { get; set; }
|
||||||
public bool HasFile { get; set; }
|
public bool HasFile { get; set; }
|
||||||
|
@ -64,6 +65,7 @@ namespace Sonarr.Api.V3.Episodes
|
||||||
AirDate = model.AirDate,
|
AirDate = model.AirDate,
|
||||||
AirDateUtc = model.AirDateUtc,
|
AirDateUtc = model.AirDateUtc,
|
||||||
Runtime = model.Runtime,
|
Runtime = model.Runtime,
|
||||||
|
FinaleType = model.FinaleType,
|
||||||
Overview = model.Overview,
|
Overview = model.Overview,
|
||||||
|
|
||||||
// EpisodeFile
|
// EpisodeFile
|
||||||
|
|
Loading…
Reference in New Issue