mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-16 10:03:51 -07:00
Add AlbumCutoffService and Refactor UI
This commit is contained in:
parent
8683c92de6
commit
eaba78ad4a
6 changed files with 192 additions and 61 deletions
|
@ -76,19 +76,19 @@ export const defaultState = {
|
|||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
// {
|
||||
// name: 'episode',
|
||||
// label: 'Episode',
|
||||
// isVisible: true
|
||||
// },
|
||||
{
|
||||
name: 'episode',
|
||||
label: 'Episode',
|
||||
name: 'albumTitle',
|
||||
label: 'Album Title',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'episodeTitle',
|
||||
label: 'Episode Title',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'airDateUtc',
|
||||
label: 'Air Date',
|
||||
name: 'releaseDate',
|
||||
label: 'Release Date',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
|
@ -97,11 +97,11 @@ export const defaultState = {
|
|||
label: 'Language',
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
label: 'Status',
|
||||
isVisible: true
|
||||
},
|
||||
// {
|
||||
// name: 'status',
|
||||
// label: 'Status',
|
||||
// isVisible: true
|
||||
// },
|
||||
{
|
||||
name: 'actions',
|
||||
columnLabel: 'Actions',
|
||||
|
|
|
@ -3,7 +3,6 @@ import React from 'react';
|
|||
import episodeEntities from 'Episode/episodeEntities';
|
||||
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
|
||||
import EpisodeStatusConnector from 'Episode/EpisodeStatusConnector';
|
||||
import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber';
|
||||
import EpisodeSearchCellConnector from 'Episode/EpisodeSearchCellConnector';
|
||||
import TrackFileLanguageConnector from 'TrackFile/TrackFileLanguageConnector';
|
||||
import ArtistNameLink from 'Artist/ArtistNameLink';
|
||||
|
@ -18,13 +17,7 @@ function CutoffUnmetRow(props) {
|
|||
id,
|
||||
trackFileId,
|
||||
artist,
|
||||
seasonNumber,
|
||||
episodeNumber,
|
||||
absoluteEpisodeNumber,
|
||||
sceneSeasonNumber,
|
||||
sceneEpisodeNumber,
|
||||
sceneAbsoluteEpisodeNumber,
|
||||
airDateUtc,
|
||||
releaseDate,
|
||||
title,
|
||||
isSelected,
|
||||
columns,
|
||||
|
@ -54,33 +47,14 @@ function CutoffUnmetRow(props) {
|
|||
return (
|
||||
<TableRowCell key={name}>
|
||||
<ArtistNameLink
|
||||
titleSlug={artist.titleSlug}
|
||||
title={artist.title}
|
||||
nameSlug={artist.nameSlug}
|
||||
artistName={artist.artistName}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'episode') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.episode}
|
||||
>
|
||||
<SeasonEpisodeNumber
|
||||
seasonNumber={seasonNumber}
|
||||
episodeNumber={episodeNumber}
|
||||
absoluteEpisodeNumber={absoluteEpisodeNumber}
|
||||
artistType={artist.artistType}
|
||||
sceneSeasonNumber={sceneSeasonNumber}
|
||||
sceneEpisodeNumber={sceneEpisodeNumber}
|
||||
sceneAbsoluteEpisodeNumber={sceneAbsoluteEpisodeNumber}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'episodeTitle') {
|
||||
if (name === 'albumTitle') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
<EpisodeTitleLink
|
||||
|
@ -94,11 +68,11 @@ function CutoffUnmetRow(props) {
|
|||
);
|
||||
}
|
||||
|
||||
if (name === 'airDateUtc') {
|
||||
if (name === 'releaseDate') {
|
||||
return (
|
||||
<RelativeDateCellConnector
|
||||
key={name}
|
||||
date={airDateUtc}
|
||||
date={releaseDate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -155,13 +129,7 @@ CutoffUnmetRow.propTypes = {
|
|||
id: PropTypes.number.isRequired,
|
||||
trackFileId: PropTypes.number,
|
||||
artist: PropTypes.object.isRequired,
|
||||
seasonNumber: PropTypes.number.isRequired,
|
||||
episodeNumber: PropTypes.number.isRequired,
|
||||
absoluteEpisodeNumber: PropTypes.number,
|
||||
sceneSeasonNumber: PropTypes.number,
|
||||
sceneEpisodeNumber: PropTypes.number,
|
||||
sceneAbsoluteEpisodeNumber: PropTypes.number,
|
||||
airDateUtc: PropTypes.string.isRequired,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
isSelected: PropTypes.bool,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Tv; //TODO Remove after EpisodeCutoffService is Refactored
|
||||
using NzbDrone.Core.ArtistStats;
|
||||
using NzbDrone.SignalR;
|
||||
using Lidarr.Api.V3.Albums;
|
||||
|
@ -12,9 +11,9 @@ namespace Lidarr.Api.V3.Wanted
|
|||
{
|
||||
public class CutoffModule : AlbumModuleWithSignalR
|
||||
{
|
||||
private readonly IEpisodeCutoffService _episodeCutoffService;
|
||||
private readonly IAlbumCutoffService _albumCutoffService;
|
||||
|
||||
public CutoffModule(IEpisodeCutoffService episodeCutoffService,
|
||||
public CutoffModule(IAlbumCutoffService albumCutoffService,
|
||||
IAlbumService albumService,
|
||||
IArtistStatisticsService artistStatisticsService,
|
||||
IArtistService artistService,
|
||||
|
@ -22,7 +21,7 @@ namespace Lidarr.Api.V3.Wanted
|
|||
IBroadcastSignalRMessage signalRBroadcaster)
|
||||
: base(albumService, artistStatisticsService, artistService, upgradableSpecification, signalRBroadcaster, "wanted/cutoff")
|
||||
{
|
||||
_episodeCutoffService = episodeCutoffService;
|
||||
_albumCutoffService = albumCutoffService;
|
||||
GetResourcePaged = GetCutoffUnmetAlbums;
|
||||
}
|
||||
|
||||
|
@ -37,7 +36,6 @@ namespace Lidarr.Api.V3.Wanted
|
|||
};
|
||||
|
||||
var includeArtist = Request.GetBooleanQueryParameter("includeArtist");
|
||||
var includeTrackFile = Request.GetBooleanQueryParameter("includeTrackFile");
|
||||
|
||||
if (pagingResource.FilterKey == "monitored" && pagingResource.FilterValue == "false")
|
||||
{
|
||||
|
@ -48,9 +46,9 @@ namespace Lidarr.Api.V3.Wanted
|
|||
pagingSpec.FilterExpression = v => v.Monitored == true && v.Artist.Monitored == true;
|
||||
}
|
||||
|
||||
//var resource = ApplyToPage(_episodeCutoffService.EpisodesWhereCutoffUnmet, pagingSpec, v => MapToResource(v, includeSeries, includeEpisodeFile));
|
||||
return null;
|
||||
//return resource;
|
||||
var resource = ApplyToPage(_albumCutoffService.AlbumsWhereCutoffUnmet, pagingSpec, v => MapToResource(v, includeArtist));
|
||||
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
65
src/NzbDrone.Core/Music/AlbumCutoffService.cs
Normal file
65
src/NzbDrone.Core/Music/AlbumCutoffService.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Profiles.Qualities;
|
||||
using NzbDrone.Core.Profiles.Languages;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public interface IAlbumCutoffService
|
||||
{
|
||||
PagingSpec<Album> AlbumsWhereCutoffUnmet(PagingSpec<Album> pagingSpec);
|
||||
}
|
||||
|
||||
public class AlbumCutoffService : IAlbumCutoffService
|
||||
{
|
||||
private readonly IAlbumRepository _albumRepository;
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly ILanguageProfileService _languageProfileService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public AlbumCutoffService(IAlbumRepository albumRepository, IProfileService profileService, ILanguageProfileService languageProfileService, Logger logger)
|
||||
{
|
||||
_albumRepository = albumRepository;
|
||||
_profileService = profileService;
|
||||
_languageProfileService = languageProfileService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public PagingSpec<Album> AlbumsWhereCutoffUnmet(PagingSpec<Album> pagingSpec)
|
||||
{
|
||||
var qualitiesBelowCutoff = new List<QualitiesBelowCutoff>();
|
||||
var languagesBelowCutoff = new List<LanguagesBelowCutoff>();
|
||||
var profiles = _profileService.All();
|
||||
var languageProfiles = _languageProfileService.All();
|
||||
|
||||
//Get all items less than the cutoff
|
||||
foreach (var profile in profiles)
|
||||
{
|
||||
var cutoffIndex = profile.Items.FindIndex(v => v.Quality == profile.Cutoff);
|
||||
var belowCutoff = profile.Items.Take(cutoffIndex).ToList();
|
||||
|
||||
if (belowCutoff.Any())
|
||||
{
|
||||
qualitiesBelowCutoff.Add(new QualitiesBelowCutoff(profile.Id, belowCutoff.Select(i => i.Quality.Id)));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var profile in languageProfiles)
|
||||
{
|
||||
var languageCutoffIndex = profile.Languages.FindIndex(v => v.Language == profile.Cutoff);
|
||||
var belowLanguageCutoff = profile.Languages.Take(languageCutoffIndex).ToList();
|
||||
|
||||
if (belowLanguageCutoff.Any())
|
||||
{
|
||||
languagesBelowCutoff.Add(new LanguagesBelowCutoff(profile.Id, belowLanguageCutoff.Select(l => l.Language.Id)));
|
||||
}
|
||||
}
|
||||
|
||||
return _albumRepository.AlbumsWhereCutoffUnmet(pagingSpec, qualitiesBelowCutoff, languagesBelowCutoff);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@ using NzbDrone.Core.Datastore;
|
|||
using NzbDrone.Core.Datastore.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Languages;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using Marr.Data.QGen;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
|
@ -18,6 +20,7 @@ namespace NzbDrone.Core.Music
|
|||
Album FindByArtistAndName(string artistName, string cleanTitle);
|
||||
Album FindById(string spotifyId);
|
||||
PagingSpec<Album> AlbumsWithoutFiles(PagingSpec<Album> pagingSpec);
|
||||
PagingSpec<Album> AlbumsWhereCutoffUnmet(PagingSpec<Album> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff, List<LanguagesBelowCutoff> languagesBelowCutoff);
|
||||
List<Album> AlbumsBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored);
|
||||
void SetMonitoredFlat(Album album, bool monitored);
|
||||
void SetMonitored(IEnumerable<int> ids, bool monitored);
|
||||
|
@ -61,6 +64,15 @@ namespace NzbDrone.Core.Music
|
|||
return pagingSpec;
|
||||
}
|
||||
|
||||
public PagingSpec<Album> AlbumsWhereCutoffUnmet(PagingSpec<Album> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff, List<LanguagesBelowCutoff> languagesBelowCutoff)
|
||||
{
|
||||
|
||||
pagingSpec.TotalRecords = GetCutOffAlbumsQueryCount(pagingSpec, qualitiesBelowCutoff, languagesBelowCutoff);
|
||||
pagingSpec.Records = GetCutOffAlbumsQuery(pagingSpec, qualitiesBelowCutoff, languagesBelowCutoff).ToList();
|
||||
|
||||
return pagingSpec;
|
||||
}
|
||||
|
||||
public List<Album> AlbumsBetweenDates(DateTime startDate, DateTime endDate, bool includeUnmonitored)
|
||||
{
|
||||
var query = Query.Join<Album, Artist>(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistId == s.Id)
|
||||
|
@ -148,6 +160,93 @@ namespace NzbDrone.Core.Music
|
|||
currentTime.ToString("yyyy-MM-dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
private QueryBuilder<Album> GetCutOffAlbumsQuery(PagingSpec<Album> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff, List<LanguagesBelowCutoff> languagesBelowCutoff)
|
||||
{
|
||||
string sortKey;
|
||||
string monitored = "(Albums.[Monitored] = 0) OR (Artists.[Monitored] = 0)";
|
||||
|
||||
if (pagingSpec.FilterExpression.ToString().Contains("True"))
|
||||
{
|
||||
monitored = "(Albums.[Monitored] = 1) AND (Artists.[Monitored] = 1)";
|
||||
}
|
||||
|
||||
if (pagingSpec.SortKey == "releaseDate")
|
||||
{
|
||||
sortKey = "Albums." + pagingSpec.SortKey;
|
||||
}
|
||||
else if (pagingSpec.SortKey == "artist.sortName")
|
||||
{
|
||||
sortKey = "Artists." + pagingSpec.SortKey.Split('.').Last();
|
||||
}
|
||||
else
|
||||
{
|
||||
sortKey = "Albums.releaseDate";
|
||||
}
|
||||
|
||||
string query = string.Format("SELECT Albums.* FROM(SELECT TrackFiles.AlbumId, TrackFiles.Language, COUNT(*) AS FileCount, " +
|
||||
" MIN(Quality) AS MinQuality FROM TrackFiles GROUP BY TrackFiles.ArtistId, TrackFiles.AlbumId) as TrackFiles" +
|
||||
" LEFT OUTER JOIN Albums ON TrackFiles.AlbumId == Albums.Id" +
|
||||
" LEFT OUTER JOIN Artists ON Albums.ArtistId == Artists.Id" +
|
||||
" WHERE ({0}) AND ({1} OR {2})" +
|
||||
" GROUP BY TrackFiles.AlbumId" +
|
||||
" ORDER BY {3} {4} LIMIT {5} OFFSET {6}",
|
||||
monitored, BuildQualityCutoffWhereClause(qualitiesBelowCutoff), BuildLanguageCutoffWhereClause(languagesBelowCutoff), sortKey, pagingSpec.ToSortDirection(), pagingSpec.PageSize, pagingSpec.PagingOffset());
|
||||
|
||||
return Query.QueryText(query);
|
||||
|
||||
}
|
||||
|
||||
private int GetCutOffAlbumsQueryCount(PagingSpec<Album> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff, List<LanguagesBelowCutoff> languagesBelowCutoff)
|
||||
{
|
||||
var monitored = 0;
|
||||
|
||||
if (pagingSpec.FilterExpression.ToString().Contains("True"))
|
||||
{
|
||||
monitored = 1;
|
||||
}
|
||||
|
||||
string query = string.Format("SELECT Albums.* FROM (SELECT TrackFiles.AlbumId, TrackFiles.Language, COUNT(*) AS FileCount," +
|
||||
" MIN(Quality) AS MinQuality FROM TrackFiles GROUP BY TrackFiles.ArtistId, TrackFiles.AlbumId) as TrackFiles" +
|
||||
" LEFT OUTER JOIN Albums ON TrackFiles.AlbumId == Albums.Id" +
|
||||
" LEFT OUTER JOIN Artists ON Albums.ArtistId == Artists.Id" +
|
||||
" WHERE ({0}) AND ({1} OR {2})" +
|
||||
" GROUP BY TrackFiles.AlbumId",
|
||||
monitored, BuildQualityCutoffWhereClause(qualitiesBelowCutoff), BuildLanguageCutoffWhereClause(languagesBelowCutoff));
|
||||
|
||||
return Query.QueryText(query).Count();
|
||||
}
|
||||
|
||||
|
||||
private string BuildLanguageCutoffWhereClause(List<LanguagesBelowCutoff> languagesBelowCutoff)
|
||||
{
|
||||
var clauses = new List<String>();
|
||||
|
||||
foreach (var language in languagesBelowCutoff)
|
||||
{
|
||||
foreach (var belowCutoff in language.LanguageIds)
|
||||
{
|
||||
clauses.Add(String.Format("(Artists.[LanguageProfileId] = {0} AND TrackFiles.[Language] = {1})", language.ProfileId, belowCutoff));
|
||||
}
|
||||
}
|
||||
|
||||
return String.Format("({0})", String.Join(" OR ", clauses));
|
||||
}
|
||||
|
||||
private string BuildQualityCutoffWhereClause(List<QualitiesBelowCutoff> qualitiesBelowCutoff)
|
||||
{
|
||||
var clauses = new List<string>();
|
||||
|
||||
foreach (var profile in qualitiesBelowCutoff)
|
||||
{
|
||||
foreach (var belowCutoff in profile.QualityIds)
|
||||
{
|
||||
clauses.Add(string.Format("(Artists.[ProfileId] = {0} AND TrackFiles.MinQuality LIKE '%_quality_: {1},%')", profile.ProfileId, belowCutoff));
|
||||
}
|
||||
}
|
||||
|
||||
return string.Format("({0})", string.Join(" OR ", clauses));
|
||||
}
|
||||
|
||||
public void SetMonitoredFlat(Album album, bool monitored)
|
||||
{
|
||||
album.Monitored = monitored;
|
||||
|
|
|
@ -866,6 +866,7 @@
|
|||
<Compile Include="Extras\Metadata\MetadataService.cs" />
|
||||
<Compile Include="Extras\Metadata\MetadataType.cs" />
|
||||
<Compile Include="Music\ArtistStatusType.cs" />
|
||||
<Compile Include="Music\AlbumCutoffService.cs" />
|
||||
<Compile Include="Music\Links.cs" />
|
||||
<Compile Include="Music\Commands\MoveArtistCommand.cs" />
|
||||
<Compile Include="Music\Events\ArtistMovedEvent.cs" />
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue