mirror of https://github.com/Radarr/Radarr
New: Release Groups for movie table index
* New: Release Group for movie table index Co-authored-by: Qstick <qstick@gmail.com> * fixup! New: Release Group for movie table index --------- Co-authored-by: Qstick <qstick@gmail.com>
This commit is contained in:
parent
1932aec131
commit
35651ac59b
|
@ -74,11 +74,7 @@ CollectionMovieLabel.propTypes = {
|
||||||
|
|
||||||
CollectionMovieLabel.defaultProps = {
|
CollectionMovieLabel.defaultProps = {
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
statistics: {
|
statistics: {}
|
||||||
episodeFileCount: 0,
|
|
||||||
totalEpisodeCount: 0,
|
|
||||||
percentOfEpisodes: 0
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CollectionMovieLabel;
|
export default CollectionMovieLabel;
|
||||||
|
|
|
@ -50,12 +50,16 @@ class DeleteMovieModalContent extends Component {
|
||||||
title,
|
title,
|
||||||
path,
|
path,
|
||||||
hasFile,
|
hasFile,
|
||||||
|
statistics,
|
||||||
deleteOptions,
|
deleteOptions,
|
||||||
sizeOnDisk,
|
|
||||||
onModalClose,
|
onModalClose,
|
||||||
onDeleteOptionChange
|
onDeleteOptionChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
sizeOnDisk = 0
|
||||||
|
} = statistics;
|
||||||
|
|
||||||
const deleteFiles = this.state.deleteFiles;
|
const deleteFiles = this.state.deleteFiles;
|
||||||
const addImportExclusion = deleteOptions.addImportExclusion;
|
const addImportExclusion = deleteOptions.addImportExclusion;
|
||||||
|
|
||||||
|
@ -151,12 +155,16 @@ class DeleteMovieModalContent extends Component {
|
||||||
DeleteMovieModalContent.propTypes = {
|
DeleteMovieModalContent.propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
|
statistics: PropTypes.object.isRequired,
|
||||||
hasFile: PropTypes.bool.isRequired,
|
hasFile: PropTypes.bool.isRequired,
|
||||||
sizeOnDisk: PropTypes.number.isRequired,
|
|
||||||
deleteOptions: PropTypes.object.isRequired,
|
deleteOptions: PropTypes.object.isRequired,
|
||||||
onDeleteOptionChange: PropTypes.func.isRequired,
|
onDeleteOptionChange: PropTypes.func.isRequired,
|
||||||
onDeletePress: PropTypes.func.isRequired,
|
onDeletePress: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DeleteMovieModalContent.defaultProps = {
|
||||||
|
statistics: {}
|
||||||
|
};
|
||||||
|
|
||||||
export default DeleteMovieModalContent;
|
export default DeleteMovieModalContent;
|
||||||
|
|
|
@ -238,7 +238,7 @@ class MovieDetails extends Component {
|
||||||
certification,
|
certification,
|
||||||
ratings,
|
ratings,
|
||||||
path,
|
path,
|
||||||
sizeOnDisk,
|
statistics,
|
||||||
qualityProfileId,
|
qualityProfileId,
|
||||||
monitored,
|
monitored,
|
||||||
studio,
|
studio,
|
||||||
|
@ -267,6 +267,10 @@ class MovieDetails extends Component {
|
||||||
movieRuntimeFormat
|
movieRuntimeFormat
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
sizeOnDisk = 0
|
||||||
|
} = statistics;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOrganizeModalOpen,
|
isOrganizeModalOpen,
|
||||||
isEditMovieModalOpen,
|
isEditMovieModalOpen,
|
||||||
|
@ -734,7 +738,7 @@ MovieDetails.propTypes = {
|
||||||
certification: PropTypes.string,
|
certification: PropTypes.string,
|
||||||
ratings: PropTypes.object.isRequired,
|
ratings: PropTypes.object.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
sizeOnDisk: PropTypes.number.isRequired,
|
statistics: PropTypes.object.isRequired,
|
||||||
qualityProfileId: PropTypes.number.isRequired,
|
qualityProfileId: PropTypes.number.isRequired,
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
status: PropTypes.string.isRequired,
|
status: PropTypes.string.isRequired,
|
||||||
|
@ -773,9 +777,9 @@ MovieDetails.propTypes = {
|
||||||
|
|
||||||
MovieDetails.defaultProps = {
|
MovieDetails.defaultProps = {
|
||||||
genres: [],
|
genres: [],
|
||||||
|
statistics: {},
|
||||||
tags: [],
|
tags: [],
|
||||||
isSaving: false,
|
isSaving: false
|
||||||
sizeOnDisk: 0
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MovieDetails;
|
export default MovieDetails;
|
||||||
|
|
|
@ -17,13 +17,13 @@ function createUnoptimizedSelector() {
|
||||||
createClientSideCollectionSelector('movies', 'movieIndex'),
|
createClientSideCollectionSelector('movies', 'movieIndex'),
|
||||||
(movies: MoviesAppState) => {
|
(movies: MoviesAppState) => {
|
||||||
return movies.items.map((m) => {
|
return movies.items.map((m) => {
|
||||||
const { monitored, status, hasFile, sizeOnDisk } = m;
|
const { monitored, status, hasFile, statistics } = m;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
monitored,
|
monitored,
|
||||||
status,
|
status,
|
||||||
hasFile,
|
hasFile,
|
||||||
sizeOnDisk,
|
statistics,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -44,16 +44,20 @@ export default function MovieIndexFooter() {
|
||||||
let monitored = 0;
|
let monitored = 0;
|
||||||
let totalFileSize = 0;
|
let totalFileSize = 0;
|
||||||
|
|
||||||
movies.forEach((s) => {
|
movies.forEach((m) => {
|
||||||
if (s.hasFile) {
|
const { statistics = { sizeOnDisk: 0 } } = m;
|
||||||
|
|
||||||
|
const { sizeOnDisk = 0 } = statistics;
|
||||||
|
|
||||||
|
if (m.hasFile) {
|
||||||
movieFiles += 1;
|
movieFiles += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s.monitored) {
|
if (m.monitored) {
|
||||||
monitored++;
|
monitored++;
|
||||||
}
|
}
|
||||||
|
|
||||||
totalFileSize += s.sizeOnDisk;
|
totalFileSize += sizeOnDisk;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -13,6 +13,7 @@ import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
||||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||||
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
||||||
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
|
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
|
||||||
|
import { Statistics } from 'Movie/Movie';
|
||||||
import MoviePoster from 'Movie/MoviePoster';
|
import MoviePoster from 'Movie/MoviePoster';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import dimensions from 'Styles/Variables/dimensions';
|
import dimensions from 'Styles/Variables/dimensions';
|
||||||
|
@ -66,17 +67,19 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) {
|
||||||
status,
|
status,
|
||||||
path,
|
path,
|
||||||
overview,
|
overview,
|
||||||
|
statistics = {} as Statistics,
|
||||||
images,
|
images,
|
||||||
hasFile,
|
hasFile,
|
||||||
isAvailable,
|
isAvailable,
|
||||||
tmdbId,
|
tmdbId,
|
||||||
imdbId,
|
imdbId,
|
||||||
studio,
|
studio,
|
||||||
sizeOnDisk,
|
|
||||||
added,
|
added,
|
||||||
youTubeTrailerId,
|
youTubeTrailerId,
|
||||||
} = movie;
|
} = movie;
|
||||||
|
|
||||||
|
const { sizeOnDisk = 0 } = statistics;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
||||||
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);
|
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
||||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||||
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
||||||
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
|
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
|
||||||
|
import { Statistics } from 'Movie/Movie';
|
||||||
import MoviePoster from 'Movie/MoviePoster';
|
import MoviePoster from 'Movie/MoviePoster';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
|
@ -75,12 +76,14 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||||
path,
|
path,
|
||||||
movieFile,
|
movieFile,
|
||||||
ratings,
|
ratings,
|
||||||
sizeOnDisk,
|
statistics = {} as Statistics,
|
||||||
certification,
|
certification,
|
||||||
originalTitle,
|
originalTitle,
|
||||||
originalLanguage,
|
originalLanguage,
|
||||||
} = movie;
|
} = movie;
|
||||||
|
|
||||||
|
const { sizeOnDisk = 0 } = statistics;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [hasPosterError, setHasPosterError] = useState(false);
|
const [hasPosterError, setHasPosterError] = useState(false);
|
||||||
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
flex: 1 0 125px;
|
flex: 1 0 125px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.releaseGroups,
|
||||||
.inCinemas,
|
.inCinemas,
|
||||||
.physicalRelease,
|
.physicalRelease,
|
||||||
.digitalRelease,
|
.digitalRelease,
|
||||||
|
|
|
@ -20,6 +20,7 @@ interface CssExports {
|
||||||
'physicalRelease': string;
|
'physicalRelease': string;
|
||||||
'popularity': string;
|
'popularity': string;
|
||||||
'qualityProfileId': string;
|
'qualityProfileId': string;
|
||||||
|
'releaseGroups': string;
|
||||||
'rottenTomatoesRating': string;
|
'rottenTomatoesRating': string;
|
||||||
'runtime': string;
|
'runtime': string;
|
||||||
'sizeOnDisk': string;
|
'sizeOnDisk': string;
|
||||||
|
|
|
@ -19,6 +19,7 @@ import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
||||||
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
||||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||||
import createMovieIndexItemSelector from 'Movie/Index/createMovieIndexItemSelector';
|
import createMovieIndexItemSelector from 'Movie/Index/createMovieIndexItemSelector';
|
||||||
|
import { Statistics } from 'Movie/Movie';
|
||||||
import MoviePopularityIndex from 'Movie/MoviePopularityIndex';
|
import MoviePopularityIndex from 'Movie/MoviePopularityIndex';
|
||||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
|
@ -60,6 +61,7 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
||||||
originalLanguage,
|
originalLanguage,
|
||||||
originalTitle,
|
originalTitle,
|
||||||
added,
|
added,
|
||||||
|
statistics = {} as Statistics,
|
||||||
year,
|
year,
|
||||||
inCinemas,
|
inCinemas,
|
||||||
digitalRelease,
|
digitalRelease,
|
||||||
|
@ -67,7 +69,6 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
||||||
runtime,
|
runtime,
|
||||||
minimumAvailability,
|
minimumAvailability,
|
||||||
path,
|
path,
|
||||||
sizeOnDisk,
|
|
||||||
genres = [],
|
genres = [],
|
||||||
ratings,
|
ratings,
|
||||||
popularity,
|
popularity,
|
||||||
|
@ -82,6 +83,8 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
||||||
isSaving = false,
|
isSaving = false,
|
||||||
} = movie;
|
} = movie;
|
||||||
|
|
||||||
|
const { sizeOnDisk = 0, releaseGroups = [] } = statistics;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
||||||
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);
|
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);
|
||||||
|
@ -380,6 +383,20 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'releaseGroups') {
|
||||||
|
const joinedReleaseGroups = releaseGroups.join(', ');
|
||||||
|
const truncatedReleaseGroups =
|
||||||
|
releaseGroups.length > 3
|
||||||
|
? `${releaseGroups.slice(0, 3).join(', ')}...`
|
||||||
|
: joinedReleaseGroups;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||||
|
<span title={joinedReleaseGroups}>{truncatedReleaseGroups}</span>
|
||||||
|
</VirtualTableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'tags') {
|
if (name === 'tags') {
|
||||||
return (
|
return (
|
||||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
flex: 1 0 125px;
|
flex: 1 0 125px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.releaseGroups,
|
||||||
.inCinemas,
|
.inCinemas,
|
||||||
.physicalRelease,
|
.physicalRelease,
|
||||||
.digitalRelease,
|
.digitalRelease,
|
||||||
|
|
|
@ -17,6 +17,7 @@ interface CssExports {
|
||||||
'physicalRelease': string;
|
'physicalRelease': string;
|
||||||
'popularity': string;
|
'popularity': string;
|
||||||
'qualityProfileId': string;
|
'qualityProfileId': string;
|
||||||
|
'releaseGroups': string;
|
||||||
'rottenTomatoesRating': string;
|
'rottenTomatoesRating': string;
|
||||||
'runtime': string;
|
'runtime': string;
|
||||||
'sizeOnDisk': string;
|
'sizeOnDisk': string;
|
||||||
|
|
|
@ -12,6 +12,12 @@ export interface Collection {
|
||||||
title: string;
|
title: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Statistics {
|
||||||
|
movieFileCount: number;
|
||||||
|
releaseGroups: string[];
|
||||||
|
sizeOnDisk: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Ratings {
|
export interface Ratings {
|
||||||
imdb: object;
|
imdb: object;
|
||||||
tmdb: object;
|
tmdb: object;
|
||||||
|
@ -42,11 +48,11 @@ interface Movie extends ModelBase {
|
||||||
runtime: number;
|
runtime: number;
|
||||||
minimumAvailability: string;
|
minimumAvailability: string;
|
||||||
path: string;
|
path: string;
|
||||||
sizeOnDisk: number;
|
|
||||||
genres: string[];
|
genres: string[];
|
||||||
ratings: Ratings;
|
ratings: Ratings;
|
||||||
popularity: number;
|
popularity: number;
|
||||||
certification: string;
|
certification: string;
|
||||||
|
statistics: Statistics;
|
||||||
tags: number[];
|
tags: number[];
|
||||||
images: Image[];
|
images: Image[];
|
||||||
movieFile: MovieFile;
|
movieFile: MovieFile;
|
||||||
|
|
|
@ -128,6 +128,22 @@ export const filterPredicates = {
|
||||||
return predicate(originalLanguage ? originalLanguage.name : '', filterValue);
|
return predicate(originalLanguage ? originalLanguage.name : '', filterValue);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
releaseGroups: function(item, filterValue, type) {
|
||||||
|
const predicate = filterTypePredicates[type];
|
||||||
|
const { statistics = {} } = item;
|
||||||
|
const { releaseGroups = [] } = statistics;
|
||||||
|
|
||||||
|
return predicate(releaseGroups, filterValue);
|
||||||
|
},
|
||||||
|
|
||||||
|
sizeOnDisk: function(item, filterValue, type) {
|
||||||
|
const predicate = filterTypePredicates[type];
|
||||||
|
const { statistics = {} } = item;
|
||||||
|
const sizeOnDisk = statistics && statistics.sizeOnDisk ? statistics.sizeOnDisk : 0;
|
||||||
|
|
||||||
|
return predicate(sizeOnDisk, filterValue);
|
||||||
|
},
|
||||||
|
|
||||||
inCinemas: function(item, filterValue, type) {
|
inCinemas: function(item, filterValue, type) {
|
||||||
return dateFilterPredicate(item.inCinemas, filterValue, type);
|
return dateFilterPredicate(item.inCinemas, filterValue, type);
|
||||||
},
|
},
|
||||||
|
@ -290,6 +306,12 @@ export const sortPredicates = {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Number.MAX_VALUE;
|
return Number.MAX_VALUE;
|
||||||
|
},
|
||||||
|
|
||||||
|
sizeOnDisk: function(item) {
|
||||||
|
const { statistics = {} } = item;
|
||||||
|
|
||||||
|
return statistics.sizeOnDisk || 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -206,6 +206,12 @@ export const defaultState = {
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'releaseGroups',
|
||||||
|
label: () => translate('ReleaseGroup'),
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'tags',
|
name: 'tags',
|
||||||
label: () => translate('Tags'),
|
label: () => translate('Tags'),
|
||||||
|
@ -241,6 +247,17 @@ export const defaultState = {
|
||||||
return originalLanguage.name;
|
return originalLanguage.name;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
releaseGroups: function(item) {
|
||||||
|
const { statistics = {} } = item;
|
||||||
|
const { releaseGroups = [] } = statistics;
|
||||||
|
|
||||||
|
return releaseGroups.length ?
|
||||||
|
releaseGroups
|
||||||
|
.map((group) => group.toLowerCase())
|
||||||
|
.sort((a, b) => a.localeCompare(b)) :
|
||||||
|
undefined;
|
||||||
|
},
|
||||||
|
|
||||||
imdbRating: function(item) {
|
imdbRating: function(item) {
|
||||||
const { ratings = {} } = item;
|
const { ratings = {} } = item;
|
||||||
|
|
||||||
|
@ -313,6 +330,28 @@ export const defaultState = {
|
||||||
return collectionList.sort(sortByName);
|
return collectionList.sort(sortByName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'releaseGroups',
|
||||||
|
label: () => translate('ReleaseGroups'),
|
||||||
|
type: filterBuilderTypes.ARRAY,
|
||||||
|
optionsSelector: function(items) {
|
||||||
|
const groupList = items.reduce((acc, movie) => {
|
||||||
|
const { statistics = {} } = movie;
|
||||||
|
const { releaseGroups = [] } = statistics;
|
||||||
|
|
||||||
|
releaseGroups.forEach((releaseGroup) => {
|
||||||
|
acc.push({
|
||||||
|
id: releaseGroup,
|
||||||
|
name: releaseGroup
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return groupList.sort(sortByName);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'status',
|
name: 'status',
|
||||||
label: () => translate('ReleaseStatus'),
|
label: () => translate('ReleaseStatus'),
|
||||||
|
|
|
@ -1114,6 +1114,7 @@
|
||||||
"ReleaseBranchCheckOfficialBranchMessage": "Branch {0} is not a valid {appName} release branch, you will not receive updates",
|
"ReleaseBranchCheckOfficialBranchMessage": "Branch {0} is not a valid {appName} release branch, you will not receive updates",
|
||||||
"ReleaseDates": "Release Dates",
|
"ReleaseDates": "Release Dates",
|
||||||
"ReleaseGroup": "Release Group",
|
"ReleaseGroup": "Release Group",
|
||||||
|
"ReleaseGroups": "Release Groups",
|
||||||
"ReleaseHash": "Release Hash",
|
"ReleaseHash": "Release Hash",
|
||||||
"ReleaseProfiles": "Release Profiles",
|
"ReleaseProfiles": "Release Profiles",
|
||||||
"ReleaseProfilesLoadError": "Unable to load Release Profiles",
|
"ReleaseProfilesLoadError": "Unable to load Release Profiles",
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MovieStats
|
||||||
|
{
|
||||||
|
public class MovieStatistics : ResultSet
|
||||||
|
{
|
||||||
|
public int MovieId { get; set; }
|
||||||
|
public int MovieFileCount { get; set; }
|
||||||
|
public long SizeOnDisk { get; set; }
|
||||||
|
public string ReleaseGroupsString { get; set; }
|
||||||
|
|
||||||
|
public List<string> ReleaseGroups
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var releaseGroups = new List<string>();
|
||||||
|
|
||||||
|
if (ReleaseGroupsString.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
releaseGroups = ReleaseGroupsString
|
||||||
|
.Split('|')
|
||||||
|
.Distinct()
|
||||||
|
.Where(rg => rg.IsNotNullOrWhiteSpace())
|
||||||
|
.OrderBy(rg => rg)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return releaseGroups;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Dapper;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.Movies;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MovieStats
|
||||||
|
{
|
||||||
|
public interface IMovieStatisticsRepository
|
||||||
|
{
|
||||||
|
List<MovieStatistics> MovieStatistics();
|
||||||
|
List<MovieStatistics> MovieStatistics(int movieId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MovieStatisticsRepository : IMovieStatisticsRepository
|
||||||
|
{
|
||||||
|
private const string _selectMoviesTemplate = "SELECT /**select**/ FROM \"Movies\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
||||||
|
private const string _selectMovieFilesTemplate = "SELECT /**select**/ FROM \"MovieFiles\" /**join**/ /**innerjoin**/ /**leftjoin**/ /**where**/ /**groupby**/ /**having**/ /**orderby**/";
|
||||||
|
|
||||||
|
private readonly IMainDatabase _database;
|
||||||
|
|
||||||
|
public MovieStatisticsRepository(IMainDatabase database)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MovieStatistics> MovieStatistics()
|
||||||
|
{
|
||||||
|
return MapResults(Query(MoviesBuilder(), _selectMoviesTemplate),
|
||||||
|
Query(MovieFilesBuilder(), _selectMovieFilesTemplate));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MovieStatistics> MovieStatistics(int movieId)
|
||||||
|
{
|
||||||
|
return MapResults(Query(MoviesBuilder().Where<Movie>(x => x.Id == movieId), _selectMoviesTemplate),
|
||||||
|
Query(MovieFilesBuilder().Where<MovieFile>(x => x.MovieId == movieId), _selectMovieFilesTemplate));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MovieStatistics> MapResults(List<MovieStatistics> moviesResult, List<MovieStatistics> filesResult)
|
||||||
|
{
|
||||||
|
moviesResult.ForEach(e =>
|
||||||
|
{
|
||||||
|
var file = filesResult.SingleOrDefault(f => f.MovieId == e.MovieId);
|
||||||
|
|
||||||
|
e.SizeOnDisk = file?.SizeOnDisk ?? 0;
|
||||||
|
e.ReleaseGroupsString = file?.ReleaseGroupsString;
|
||||||
|
});
|
||||||
|
|
||||||
|
return moviesResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MovieStatistics> Query(SqlBuilder builder, string template)
|
||||||
|
{
|
||||||
|
var sql = builder.AddTemplate(template).LogQuery();
|
||||||
|
|
||||||
|
using var conn = _database.OpenConnection();
|
||||||
|
|
||||||
|
return conn.Query<MovieStatistics>(sql.RawSql, sql.Parameters).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SqlBuilder MoviesBuilder()
|
||||||
|
{
|
||||||
|
return new SqlBuilder(_database.DatabaseType)
|
||||||
|
.Select(@"""Movies"".""Id"" AS MovieId,
|
||||||
|
SUM(CASE WHEN ""MovieFileId"" > 0 THEN 1 ELSE 0 END) AS MovieFileCount")
|
||||||
|
.GroupBy<Movie>(x => x.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SqlBuilder MovieFilesBuilder()
|
||||||
|
{
|
||||||
|
if (_database.DatabaseType == DatabaseType.SQLite)
|
||||||
|
{
|
||||||
|
return new SqlBuilder(_database.DatabaseType)
|
||||||
|
.Select(@"""MovieId"",
|
||||||
|
SUM(COALESCE(""Size"", 0)) AS SizeOnDisk,
|
||||||
|
GROUP_CONCAT(""ReleaseGroup"", '|') AS ReleaseGroupsString")
|
||||||
|
.GroupBy<MovieFile>(x => x.MovieId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SqlBuilder(_database.DatabaseType)
|
||||||
|
.Select(@"""MovieId"",
|
||||||
|
SUM(COALESCE(""Size"", 0)) AS SizeOnDisk,
|
||||||
|
string_agg(""ReleaseGroup"", '|') AS ReleaseGroupsString")
|
||||||
|
.GroupBy<MovieFile>(x => x.MovieId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MovieStats
|
||||||
|
{
|
||||||
|
public interface IMovieStatisticsService
|
||||||
|
{
|
||||||
|
List<MovieStatistics> MovieStatistics();
|
||||||
|
MovieStatistics MovieStatistics(int movieId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MovieStatisticsService : IMovieStatisticsService
|
||||||
|
{
|
||||||
|
private readonly IMovieStatisticsRepository _movieStatisticsRepository;
|
||||||
|
|
||||||
|
public MovieStatisticsService(IMovieStatisticsRepository movieStatisticsRepository)
|
||||||
|
{
|
||||||
|
_movieStatisticsRepository = movieStatisticsRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MovieStatistics> MovieStatistics()
|
||||||
|
{
|
||||||
|
var movieStatistics = _movieStatisticsRepository.MovieStatistics();
|
||||||
|
|
||||||
|
return movieStatistics.GroupBy(m => m.MovieId).Select(m => m.First()).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovieStatistics MovieStatistics(int movieId)
|
||||||
|
{
|
||||||
|
var stats = _movieStatisticsRepository.MovieStatistics(movieId);
|
||||||
|
|
||||||
|
if (stats == null || stats.Count == 0)
|
||||||
|
{
|
||||||
|
return new MovieStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats.First();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ using NzbDrone.Core.Movies;
|
||||||
using NzbDrone.Core.Movies.Commands;
|
using NzbDrone.Core.Movies.Commands;
|
||||||
using NzbDrone.Core.Movies.Events;
|
using NzbDrone.Core.Movies.Events;
|
||||||
using NzbDrone.Core.Movies.Translations;
|
using NzbDrone.Core.Movies.Translations;
|
||||||
|
using NzbDrone.Core.MovieStats;
|
||||||
using NzbDrone.Core.RootFolders;
|
using NzbDrone.Core.RootFolders;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
using NzbDrone.Core.Validation.Paths;
|
using NzbDrone.Core.Validation.Paths;
|
||||||
|
@ -43,6 +44,7 @@ namespace Radarr.Api.V3.Movies
|
||||||
private readonly IMovieService _moviesService;
|
private readonly IMovieService _moviesService;
|
||||||
private readonly IMovieTranslationService _movieTranslationService;
|
private readonly IMovieTranslationService _movieTranslationService;
|
||||||
private readonly IAddMovieService _addMovieService;
|
private readonly IAddMovieService _addMovieService;
|
||||||
|
private readonly IMovieStatisticsService _movieStatisticsService;
|
||||||
private readonly IMapCoversToLocal _coverMapper;
|
private readonly IMapCoversToLocal _coverMapper;
|
||||||
private readonly IManageCommandQueue _commandQueueManager;
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
private readonly IRootFolderService _rootFolderService;
|
private readonly IRootFolderService _rootFolderService;
|
||||||
|
@ -54,6 +56,7 @@ namespace Radarr.Api.V3.Movies
|
||||||
IMovieService moviesService,
|
IMovieService moviesService,
|
||||||
IMovieTranslationService movieTranslationService,
|
IMovieTranslationService movieTranslationService,
|
||||||
IAddMovieService addMovieService,
|
IAddMovieService addMovieService,
|
||||||
|
IMovieStatisticsService movieStatisticsService,
|
||||||
IMapCoversToLocal coverMapper,
|
IMapCoversToLocal coverMapper,
|
||||||
IManageCommandQueue commandQueueManager,
|
IManageCommandQueue commandQueueManager,
|
||||||
IRootFolderService rootFolderService,
|
IRootFolderService rootFolderService,
|
||||||
|
@ -74,6 +77,7 @@ namespace Radarr.Api.V3.Movies
|
||||||
_moviesService = moviesService;
|
_moviesService = moviesService;
|
||||||
_movieTranslationService = movieTranslationService;
|
_movieTranslationService = movieTranslationService;
|
||||||
_addMovieService = addMovieService;
|
_addMovieService = addMovieService;
|
||||||
|
_movieStatisticsService = movieStatisticsService;
|
||||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_coverMapper = coverMapper;
|
_coverMapper = coverMapper;
|
||||||
|
@ -125,6 +129,7 @@ namespace Radarr.Api.V3.Movies
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
var movieStats = _movieStatisticsService.MovieStatistics();
|
||||||
var configLanguage = (Language)_configService.MovieInfoLanguage;
|
var configLanguage = (Language)_configService.MovieInfoLanguage;
|
||||||
var availDelay = _configService.AvailabilityDelay;
|
var availDelay = _configService.AvailabilityDelay;
|
||||||
|
|
||||||
|
@ -134,6 +139,7 @@ namespace Radarr.Api.V3.Movies
|
||||||
.GetAllTranslationsForLanguage(configLanguage);
|
.GetAllTranslationsForLanguage(configLanguage);
|
||||||
|
|
||||||
var tdict = translations.ToDictionary(x => x.MovieMetadataId);
|
var tdict = translations.ToDictionary(x => x.MovieMetadataId);
|
||||||
|
var sdict = movieStats.ToDictionary(x => x.MovieId);
|
||||||
|
|
||||||
if (!excludeLocalCovers)
|
if (!excludeLocalCovers)
|
||||||
{
|
{
|
||||||
|
@ -155,6 +161,8 @@ namespace Radarr.Api.V3.Movies
|
||||||
MapCoversToLocal(moviesResources, coverFileInfos);
|
MapCoversToLocal(moviesResources, coverFileInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LinkMovieStatistics(moviesResources, sdict);
|
||||||
|
|
||||||
var rootFolders = _rootFolderService.All();
|
var rootFolders = _rootFolderService.All();
|
||||||
|
|
||||||
moviesResources.ForEach(m => m.RootFolderPath = _rootFolderService.GetBestRootFolderPath(m.Path, rootFolders));
|
moviesResources.ForEach(m => m.RootFolderPath = _rootFolderService.GetBestRootFolderPath(m.Path, rootFolders));
|
||||||
|
@ -166,6 +174,7 @@ namespace Radarr.Api.V3.Movies
|
||||||
protected override MovieResource GetResourceById(int id)
|
protected override MovieResource GetResourceById(int id)
|
||||||
{
|
{
|
||||||
var movie = _moviesService.GetMovie(id);
|
var movie = _moviesService.GetMovie(id);
|
||||||
|
|
||||||
return MapToResource(movie);
|
return MapToResource(movie);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,6 +192,7 @@ namespace Radarr.Api.V3.Movies
|
||||||
|
|
||||||
var resource = movie.ToResource(availDelay, translation, _qualityUpgradableSpecification);
|
var resource = movie.ToResource(availDelay, translation, _qualityUpgradableSpecification);
|
||||||
MapCoversToLocal(resource);
|
MapCoversToLocal(resource);
|
||||||
|
FetchAndLinkMovieStatistics(resource);
|
||||||
|
|
||||||
resource.RootFolderPath = _rootFolderService.GetBestRootFolderPath(resource.Path);
|
resource.RootFolderPath = _rootFolderService.GetBestRootFolderPath(resource.Path);
|
||||||
|
|
||||||
|
@ -278,6 +288,29 @@ namespace Radarr.Api.V3.Movies
|
||||||
_coverMapper.ConvertToLocalUrls(movies.Select(x => Tuple.Create(x.Id, x.Images.AsEnumerable())), coverFileInfos);
|
_coverMapper.ConvertToLocalUrls(movies.Select(x => Tuple.Create(x.Id, x.Images.AsEnumerable())), coverFileInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void FetchAndLinkMovieStatistics(MovieResource resource)
|
||||||
|
{
|
||||||
|
LinkMovieStatistics(resource, _movieStatisticsService.MovieStatistics(resource.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LinkMovieStatistics(List<MovieResource> resources, Dictionary<int, MovieStatistics> sDict)
|
||||||
|
{
|
||||||
|
foreach (var movie in resources)
|
||||||
|
{
|
||||||
|
if (sDict.TryGetValue(movie.Id, out var stats))
|
||||||
|
{
|
||||||
|
LinkMovieStatistics(movie, stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LinkMovieStatistics(MovieResource resource, MovieStatistics movieStatistics)
|
||||||
|
{
|
||||||
|
resource.Statistics = movieStatistics.ToResource();
|
||||||
|
resource.HasFile = movieStatistics.MovieFileCount > 0;
|
||||||
|
resource.SizeOnDisk = movieStatistics.SizeOnDisk;
|
||||||
|
}
|
||||||
|
|
||||||
[NonAction]
|
[NonAction]
|
||||||
public void Handle(MovieFileImportedEvent message)
|
public void Handle(MovieFileImportedEvent message)
|
||||||
{
|
{
|
||||||
|
|
|
@ -47,7 +47,6 @@ namespace Radarr.Api.V3.Movies
|
||||||
// public bool Downloaded { get; set; }
|
// public bool Downloaded { get; set; }
|
||||||
public string RemotePoster { get; set; }
|
public string RemotePoster { get; set; }
|
||||||
public int Year { get; set; }
|
public int Year { get; set; }
|
||||||
public bool HasFile { get; set; }
|
|
||||||
public string YouTubeTrailerId { get; set; }
|
public string YouTubeTrailerId { get; set; }
|
||||||
public string Studio { get; set; }
|
public string Studio { get; set; }
|
||||||
|
|
||||||
|
@ -55,6 +54,9 @@ namespace Radarr.Api.V3.Movies
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public int QualityProfileId { get; set; }
|
public int QualityProfileId { get; set; }
|
||||||
|
|
||||||
|
// Compatibility
|
||||||
|
public bool HasFile { get; set; }
|
||||||
|
|
||||||
// Editing Only
|
// Editing Only
|
||||||
public bool Monitored { get; set; }
|
public bool Monitored { get; set; }
|
||||||
public MovieStatusType MinimumAvailability { get; set; }
|
public MovieStatusType MinimumAvailability { get; set; }
|
||||||
|
@ -77,6 +79,7 @@ namespace Radarr.Api.V3.Movies
|
||||||
public MovieFileResource MovieFile { get; set; }
|
public MovieFileResource MovieFile { get; set; }
|
||||||
public MovieCollectionResource Collection { get; set; }
|
public MovieCollectionResource Collection { get; set; }
|
||||||
public float Popularity { get; set; }
|
public float Popularity { get; set; }
|
||||||
|
public MovieStatisticsResource Statistics { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MovieResourceMapper
|
public static class MovieResourceMapper
|
||||||
|
@ -88,8 +91,6 @@ namespace Radarr.Api.V3.Movies
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var size = model.MovieFile?.Size ?? 0;
|
|
||||||
|
|
||||||
var movieFile = model.MovieFile?.ToResource(model, upgradableSpecification, formatCalculationService);
|
var movieFile = model.MovieFile?.ToResource(model, upgradableSpecification, formatCalculationService);
|
||||||
|
|
||||||
var translatedTitle = movieTranslation?.Title ?? model.Title;
|
var translatedTitle = movieTranslation?.Title ?? model.Title;
|
||||||
|
@ -108,9 +109,7 @@ namespace Radarr.Api.V3.Movies
|
||||||
InCinemas = model.MovieMetadata.Value.InCinemas,
|
InCinemas = model.MovieMetadata.Value.InCinemas,
|
||||||
PhysicalRelease = model.MovieMetadata.Value.PhysicalRelease,
|
PhysicalRelease = model.MovieMetadata.Value.PhysicalRelease,
|
||||||
DigitalRelease = model.MovieMetadata.Value.DigitalRelease,
|
DigitalRelease = model.MovieMetadata.Value.DigitalRelease,
|
||||||
HasFile = model.HasFile,
|
|
||||||
|
|
||||||
SizeOnDisk = size,
|
|
||||||
Status = model.MovieMetadata.Value.Status,
|
Status = model.MovieMetadata.Value.Status,
|
||||||
Overview = translatedOverview,
|
Overview = translatedOverview,
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.MovieStats;
|
||||||
|
|
||||||
|
namespace Radarr.Api.V3.Movies
|
||||||
|
{
|
||||||
|
public class MovieStatisticsResource
|
||||||
|
{
|
||||||
|
public int MovieFileCount { get; set; }
|
||||||
|
public long SizeOnDisk { get; set; }
|
||||||
|
public List<string> ReleaseGroups { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MovieStatisticsResourceMapper
|
||||||
|
{
|
||||||
|
public static MovieStatisticsResource ToResource(this MovieStatistics model)
|
||||||
|
{
|
||||||
|
if (model == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MovieStatisticsResource
|
||||||
|
{
|
||||||
|
MovieFileCount = model.MovieFileCount,
|
||||||
|
SizeOnDisk = model.SizeOnDisk,
|
||||||
|
ReleaseGroups = model.ReleaseGroups
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue