mirror of
https://github.com/Radarr/Radarr.git
synced 2024-10-03 22:57:18 +02:00
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 = {
|
||||
isSaving: false,
|
||||
statistics: {
|
||||
episodeFileCount: 0,
|
||||
totalEpisodeCount: 0,
|
||||
percentOfEpisodes: 0
|
||||
}
|
||||
statistics: {}
|
||||
};
|
||||
|
||||
export default CollectionMovieLabel;
|
||||
|
@ -50,12 +50,16 @@ class DeleteMovieModalContent extends Component {
|
||||
title,
|
||||
path,
|
||||
hasFile,
|
||||
statistics,
|
||||
deleteOptions,
|
||||
sizeOnDisk,
|
||||
onModalClose,
|
||||
onDeleteOptionChange
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
sizeOnDisk = 0
|
||||
} = statistics;
|
||||
|
||||
const deleteFiles = this.state.deleteFiles;
|
||||
const addImportExclusion = deleteOptions.addImportExclusion;
|
||||
|
||||
@ -151,12 +155,16 @@ class DeleteMovieModalContent extends Component {
|
||||
DeleteMovieModalContent.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
hasFile: PropTypes.bool.isRequired,
|
||||
sizeOnDisk: PropTypes.number.isRequired,
|
||||
deleteOptions: PropTypes.object.isRequired,
|
||||
onDeleteOptionChange: PropTypes.func.isRequired,
|
||||
onDeletePress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
DeleteMovieModalContent.defaultProps = {
|
||||
statistics: {}
|
||||
};
|
||||
|
||||
export default DeleteMovieModalContent;
|
||||
|
@ -238,7 +238,7 @@ class MovieDetails extends Component {
|
||||
certification,
|
||||
ratings,
|
||||
path,
|
||||
sizeOnDisk,
|
||||
statistics,
|
||||
qualityProfileId,
|
||||
monitored,
|
||||
studio,
|
||||
@ -267,6 +267,10 @@ class MovieDetails extends Component {
|
||||
movieRuntimeFormat
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
sizeOnDisk = 0
|
||||
} = statistics;
|
||||
|
||||
const {
|
||||
isOrganizeModalOpen,
|
||||
isEditMovieModalOpen,
|
||||
@ -734,7 +738,7 @@ MovieDetails.propTypes = {
|
||||
certification: PropTypes.string,
|
||||
ratings: PropTypes.object.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
sizeOnDisk: PropTypes.number.isRequired,
|
||||
statistics: PropTypes.object.isRequired,
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
status: PropTypes.string.isRequired,
|
||||
@ -773,9 +777,9 @@ MovieDetails.propTypes = {
|
||||
|
||||
MovieDetails.defaultProps = {
|
||||
genres: [],
|
||||
statistics: {},
|
||||
tags: [],
|
||||
isSaving: false,
|
||||
sizeOnDisk: 0
|
||||
isSaving: false
|
||||
};
|
||||
|
||||
export default MovieDetails;
|
||||
|
@ -17,13 +17,13 @@ function createUnoptimizedSelector() {
|
||||
createClientSideCollectionSelector('movies', 'movieIndex'),
|
||||
(movies: MoviesAppState) => {
|
||||
return movies.items.map((m) => {
|
||||
const { monitored, status, hasFile, sizeOnDisk } = m;
|
||||
const { monitored, status, hasFile, statistics } = m;
|
||||
|
||||
return {
|
||||
monitored,
|
||||
status,
|
||||
hasFile,
|
||||
sizeOnDisk,
|
||||
statistics,
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -44,16 +44,20 @@ export default function MovieIndexFooter() {
|
||||
let monitored = 0;
|
||||
let totalFileSize = 0;
|
||||
|
||||
movies.forEach((s) => {
|
||||
if (s.hasFile) {
|
||||
movies.forEach((m) => {
|
||||
const { statistics = { sizeOnDisk: 0 } } = m;
|
||||
|
||||
const { sizeOnDisk = 0 } = statistics;
|
||||
|
||||
if (m.hasFile) {
|
||||
movieFiles += 1;
|
||||
}
|
||||
|
||||
if (s.monitored) {
|
||||
if (m.monitored) {
|
||||
monitored++;
|
||||
}
|
||||
|
||||
totalFileSize += s.sizeOnDisk;
|
||||
totalFileSize += sizeOnDisk;
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -13,6 +13,7 @@ import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
||||
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
|
||||
import { Statistics } from 'Movie/Movie';
|
||||
import MoviePoster from 'Movie/MoviePoster';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
@ -66,17 +67,19 @@ function MovieIndexOverview(props: MovieIndexOverviewProps) {
|
||||
status,
|
||||
path,
|
||||
overview,
|
||||
statistics = {} as Statistics,
|
||||
images,
|
||||
hasFile,
|
||||
isAvailable,
|
||||
tmdbId,
|
||||
imdbId,
|
||||
studio,
|
||||
sizeOnDisk,
|
||||
added,
|
||||
youTubeTrailerId,
|
||||
} = movie;
|
||||
|
||||
const { sizeOnDisk = 0 } = statistics;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
||||
const [isDeleteMovieModalOpen, setIsDeleteMovieModalOpen] = useState(false);
|
||||
|
@ -16,6 +16,7 @@ import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||
import MovieIndexProgressBar from 'Movie/Index/ProgressBar/MovieIndexProgressBar';
|
||||
import MovieIndexPosterSelect from 'Movie/Index/Select/MovieIndexPosterSelect';
|
||||
import { Statistics } from 'Movie/Movie';
|
||||
import MoviePoster from 'Movie/MoviePoster';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
@ -75,12 +76,14 @@ function MovieIndexPoster(props: MovieIndexPosterProps) {
|
||||
path,
|
||||
movieFile,
|
||||
ratings,
|
||||
sizeOnDisk,
|
||||
statistics = {} as Statistics,
|
||||
certification,
|
||||
originalTitle,
|
||||
originalLanguage,
|
||||
} = movie;
|
||||
|
||||
const { sizeOnDisk = 0 } = statistics;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [hasPosterError, setHasPosterError] = useState(false);
|
||||
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = useState(false);
|
||||
|
@ -38,6 +38,7 @@
|
||||
flex: 1 0 125px;
|
||||
}
|
||||
|
||||
.releaseGroups,
|
||||
.inCinemas,
|
||||
.physicalRelease,
|
||||
.digitalRelease,
|
||||
|
@ -20,6 +20,7 @@ interface CssExports {
|
||||
'physicalRelease': string;
|
||||
'popularity': string;
|
||||
'qualityProfileId': string;
|
||||
'releaseGroups': string;
|
||||
'rottenTomatoesRating': string;
|
||||
'runtime': string;
|
||||
'sizeOnDisk': string;
|
||||
|
@ -19,6 +19,7 @@ import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
||||
import MovieDetailsLinks from 'Movie/Details/MovieDetailsLinks';
|
||||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||
import createMovieIndexItemSelector from 'Movie/Index/createMovieIndexItemSelector';
|
||||
import { Statistics } from 'Movie/Movie';
|
||||
import MoviePopularityIndex from 'Movie/MoviePopularityIndex';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
@ -60,6 +61,7 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
||||
originalLanguage,
|
||||
originalTitle,
|
||||
added,
|
||||
statistics = {} as Statistics,
|
||||
year,
|
||||
inCinemas,
|
||||
digitalRelease,
|
||||
@ -67,7 +69,6 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
||||
runtime,
|
||||
minimumAvailability,
|
||||
path,
|
||||
sizeOnDisk,
|
||||
genres = [],
|
||||
ratings,
|
||||
popularity,
|
||||
@ -82,6 +83,8 @@ function MovieIndexRow(props: MovieIndexRowProps) {
|
||||
isSaving = false,
|
||||
} = movie;
|
||||
|
||||
const { sizeOnDisk = 0, releaseGroups = [] } = statistics;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const [isEditMovieModalOpen, setIsEditMovieModalOpen] = 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') {
|
||||
return (
|
||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
||||
|
@ -31,6 +31,7 @@
|
||||
flex: 1 0 125px;
|
||||
}
|
||||
|
||||
.releaseGroups,
|
||||
.inCinemas,
|
||||
.physicalRelease,
|
||||
.digitalRelease,
|
||||
|
@ -17,6 +17,7 @@ interface CssExports {
|
||||
'physicalRelease': string;
|
||||
'popularity': string;
|
||||
'qualityProfileId': string;
|
||||
'releaseGroups': string;
|
||||
'rottenTomatoesRating': string;
|
||||
'runtime': string;
|
||||
'sizeOnDisk': string;
|
||||
|
@ -12,6 +12,12 @@ export interface Collection {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface Statistics {
|
||||
movieFileCount: number;
|
||||
releaseGroups: string[];
|
||||
sizeOnDisk: number;
|
||||
}
|
||||
|
||||
export interface Ratings {
|
||||
imdb: object;
|
||||
tmdb: object;
|
||||
@ -42,11 +48,11 @@ interface Movie extends ModelBase {
|
||||
runtime: number;
|
||||
minimumAvailability: string;
|
||||
path: string;
|
||||
sizeOnDisk: number;
|
||||
genres: string[];
|
||||
ratings: Ratings;
|
||||
popularity: number;
|
||||
certification: string;
|
||||
statistics: Statistics;
|
||||
tags: number[];
|
||||
images: Image[];
|
||||
movieFile: MovieFile;
|
||||
|
@ -128,6 +128,22 @@ export const filterPredicates = {
|
||||
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) {
|
||||
return dateFilterPredicate(item.inCinemas, filterValue, type);
|
||||
},
|
||||
@ -290,6 +306,12 @@ export const sortPredicates = {
|
||||
}
|
||||
|
||||
return Number.MAX_VALUE;
|
||||
},
|
||||
|
||||
sizeOnDisk: function(item) {
|
||||
const { statistics = {} } = item;
|
||||
|
||||
return statistics.sizeOnDisk || 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -206,6 +206,12 @@ export const defaultState = {
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'releaseGroups',
|
||||
label: () => translate('ReleaseGroup'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: () => translate('Tags'),
|
||||
@ -241,6 +247,17 @@ export const defaultState = {
|
||||
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) {
|
||||
const { ratings = {} } = item;
|
||||
|
||||
@ -313,6 +330,28 @@ export const defaultState = {
|
||||
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',
|
||||
label: () => translate('ReleaseStatus'),
|
||||
|
@ -1114,6 +1114,7 @@
|
||||
"ReleaseBranchCheckOfficialBranchMessage": "Branch {0} is not a valid {appName} release branch, you will not receive updates",
|
||||
"ReleaseDates": "Release Dates",
|
||||
"ReleaseGroup": "Release Group",
|
||||
"ReleaseGroups": "Release Groups",
|
||||
"ReleaseHash": "Release Hash",
|
||||
"ReleaseProfiles": "Release Profiles",
|
||||
"ReleaseProfilesLoadError": "Unable to load Release Profiles",
|
||||
|
35
src/NzbDrone.Core/MovieStats/MovieStatistics.cs
Normal file
35
src/NzbDrone.Core/MovieStats/MovieStatistics.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
88
src/NzbDrone.Core/MovieStats/MovieStatisticsRepository.cs
Normal file
88
src/NzbDrone.Core/MovieStats/MovieStatisticsRepository.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
40
src/NzbDrone.Core/MovieStats/MovieStatisticsService.cs
Normal file
40
src/NzbDrone.Core/MovieStats/MovieStatisticsService.cs
Normal file
@ -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.Commands;
|
||||
using NzbDrone.Core.Movies.Events;
|
||||
using NzbDrone.Core.Movies.Translations;
|
||||
using NzbDrone.Core.MovieStats;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Core.Validation.Paths;
|
||||
@ -43,6 +44,7 @@ public class MovieController : RestControllerWithSignalR<MovieResource, Movie>,
|
||||
private readonly IMovieService _moviesService;
|
||||
private readonly IMovieTranslationService _movieTranslationService;
|
||||
private readonly IAddMovieService _addMovieService;
|
||||
private readonly IMovieStatisticsService _movieStatisticsService;
|
||||
private readonly IMapCoversToLocal _coverMapper;
|
||||
private readonly IManageCommandQueue _commandQueueManager;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
@ -54,6 +56,7 @@ public MovieController(IBroadcastSignalRMessage signalRBroadcaster,
|
||||
IMovieService moviesService,
|
||||
IMovieTranslationService movieTranslationService,
|
||||
IAddMovieService addMovieService,
|
||||
IMovieStatisticsService movieStatisticsService,
|
||||
IMapCoversToLocal coverMapper,
|
||||
IManageCommandQueue commandQueueManager,
|
||||
IRootFolderService rootFolderService,
|
||||
@ -74,6 +77,7 @@ public MovieController(IBroadcastSignalRMessage signalRBroadcaster,
|
||||
_moviesService = moviesService;
|
||||
_movieTranslationService = movieTranslationService;
|
||||
_addMovieService = addMovieService;
|
||||
_movieStatisticsService = movieStatisticsService;
|
||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||
_configService = configService;
|
||||
_coverMapper = coverMapper;
|
||||
@ -125,6 +129,7 @@ public List<MovieResource> AllMovie(int? tmdbId, bool excludeLocalCovers = false
|
||||
}
|
||||
else
|
||||
{
|
||||
var movieStats = _movieStatisticsService.MovieStatistics();
|
||||
var configLanguage = (Language)_configService.MovieInfoLanguage;
|
||||
var availDelay = _configService.AvailabilityDelay;
|
||||
|
||||
@ -134,6 +139,7 @@ public List<MovieResource> AllMovie(int? tmdbId, bool excludeLocalCovers = false
|
||||
.GetAllTranslationsForLanguage(configLanguage);
|
||||
|
||||
var tdict = translations.ToDictionary(x => x.MovieMetadataId);
|
||||
var sdict = movieStats.ToDictionary(x => x.MovieId);
|
||||
|
||||
if (!excludeLocalCovers)
|
||||
{
|
||||
@ -155,6 +161,8 @@ public List<MovieResource> AllMovie(int? tmdbId, bool excludeLocalCovers = false
|
||||
MapCoversToLocal(moviesResources, coverFileInfos);
|
||||
}
|
||||
|
||||
LinkMovieStatistics(moviesResources, sdict);
|
||||
|
||||
var rootFolders = _rootFolderService.All();
|
||||
|
||||
moviesResources.ForEach(m => m.RootFolderPath = _rootFolderService.GetBestRootFolderPath(m.Path, rootFolders));
|
||||
@ -166,6 +174,7 @@ public List<MovieResource> AllMovie(int? tmdbId, bool excludeLocalCovers = false
|
||||
protected override MovieResource GetResourceById(int id)
|
||||
{
|
||||
var movie = _moviesService.GetMovie(id);
|
||||
|
||||
return MapToResource(movie);
|
||||
}
|
||||
|
||||
@ -183,6 +192,7 @@ protected MovieResource MapToResource(Movie movie)
|
||||
|
||||
var resource = movie.ToResource(availDelay, translation, _qualityUpgradableSpecification);
|
||||
MapCoversToLocal(resource);
|
||||
FetchAndLinkMovieStatistics(resource);
|
||||
|
||||
resource.RootFolderPath = _rootFolderService.GetBestRootFolderPath(resource.Path);
|
||||
|
||||
@ -278,6 +288,29 @@ private void MapCoversToLocal(IEnumerable<MovieResource> movies, Dictionary<stri
|
||||
_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]
|
||||
public void Handle(MovieFileImportedEvent message)
|
||||
{
|
||||
|
@ -47,7 +47,6 @@ public MovieResource()
|
||||
// public bool Downloaded { get; set; }
|
||||
public string RemotePoster { get; set; }
|
||||
public int Year { get; set; }
|
||||
public bool HasFile { get; set; }
|
||||
public string YouTubeTrailerId { get; set; }
|
||||
public string Studio { get; set; }
|
||||
|
||||
@ -55,6 +54,9 @@ public MovieResource()
|
||||
public string Path { get; set; }
|
||||
public int QualityProfileId { get; set; }
|
||||
|
||||
// Compatibility
|
||||
public bool HasFile { get; set; }
|
||||
|
||||
// Editing Only
|
||||
public bool Monitored { get; set; }
|
||||
public MovieStatusType MinimumAvailability { get; set; }
|
||||
@ -77,6 +79,7 @@ public MovieResource()
|
||||
public MovieFileResource MovieFile { get; set; }
|
||||
public MovieCollectionResource Collection { get; set; }
|
||||
public float Popularity { get; set; }
|
||||
public MovieStatisticsResource Statistics { get; set; }
|
||||
}
|
||||
|
||||
public static class MovieResourceMapper
|
||||
@ -88,8 +91,6 @@ public static MovieResource ToResource(this Movie model, int availDelay, MovieTr
|
||||
return null;
|
||||
}
|
||||
|
||||
var size = model.MovieFile?.Size ?? 0;
|
||||
|
||||
var movieFile = model.MovieFile?.ToResource(model, upgradableSpecification, formatCalculationService);
|
||||
|
||||
var translatedTitle = movieTranslation?.Title ?? model.Title;
|
||||
@ -108,9 +109,7 @@ public static MovieResource ToResource(this Movie model, int availDelay, MovieTr
|
||||
InCinemas = model.MovieMetadata.Value.InCinemas,
|
||||
PhysicalRelease = model.MovieMetadata.Value.PhysicalRelease,
|
||||
DigitalRelease = model.MovieMetadata.Value.DigitalRelease,
|
||||
HasFile = model.HasFile,
|
||||
|
||||
SizeOnDisk = size,
|
||||
Status = model.MovieMetadata.Value.Status,
|
||||
Overview = translatedOverview,
|
||||
|
||||
|
30
src/Radarr.Api.V3/Movies/MovieStatisticsResource.cs
Normal file
30
src/Radarr.Api.V3/Movies/MovieStatisticsResource.cs
Normal file
@ -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
Block a user