mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-19 04:59:35 -07:00
Almost finished linking frontend to backend. A few issues with DB mapping to work out.
This commit is contained in:
parent
5b0f11b19a
commit
fa52eabb79
35 changed files with 996 additions and 256 deletions
194
src/NzbDrone.Api/Music/ArtistModule.cs
Normal file
194
src/NzbDrone.Api/Music/ArtistModule.cs
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Datastore.Events;
|
||||||
|
using NzbDrone.Core.MediaCover;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using NzbDrone.Core.Music.Events;
|
||||||
|
using NzbDrone.Core.SeriesStats;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
using NzbDrone.Core.Validation.Paths;
|
||||||
|
using NzbDrone.SignalR;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Music
|
||||||
|
{
|
||||||
|
public class ArtistModule : NzbDroneRestModuleWithSignalR<ArtistResource, Core.Music.Artist>,
|
||||||
|
IHandle<TrackImportedEvent>,
|
||||||
|
IHandle<TrackFileDeletedEvent>,
|
||||||
|
IHandle<ArtistUpdatedEvent>,
|
||||||
|
IHandle<ArtistEditedEvent>,
|
||||||
|
IHandle<ArtistDeletedEvent>,
|
||||||
|
IHandle<ArtistRenamedEvent>
|
||||||
|
//IHandle<MediaCoversUpdatedEvent>
|
||||||
|
{
|
||||||
|
private readonly IArtistService _artistService;
|
||||||
|
private readonly IAddArtistService _addSeriesService;
|
||||||
|
private readonly ISeriesStatisticsService _seriesStatisticsService;
|
||||||
|
private readonly IMapCoversToLocal _coverMapper;
|
||||||
|
|
||||||
|
public ArtistModule(IBroadcastSignalRMessage signalRBroadcaster,
|
||||||
|
IArtistService artistService,
|
||||||
|
IAddArtistService addSeriesService,
|
||||||
|
ISeriesStatisticsService seriesStatisticsService,
|
||||||
|
IMapCoversToLocal coverMapper,
|
||||||
|
RootFolderValidator rootFolderValidator,
|
||||||
|
ArtistPathValidator seriesPathValidator,
|
||||||
|
ArtistExistsValidator artistExistsValidator,
|
||||||
|
DroneFactoryValidator droneFactoryValidator,
|
||||||
|
SeriesAncestorValidator seriesAncestorValidator,
|
||||||
|
ProfileExistsValidator profileExistsValidator
|
||||||
|
)
|
||||||
|
: base(signalRBroadcaster)
|
||||||
|
{
|
||||||
|
_artistService = artistService;
|
||||||
|
_addSeriesService = addSeriesService;
|
||||||
|
_seriesStatisticsService = seriesStatisticsService;
|
||||||
|
|
||||||
|
_coverMapper = coverMapper;
|
||||||
|
|
||||||
|
GetResourceAll = AllArtist;
|
||||||
|
GetResourceById = GetArtist;
|
||||||
|
CreateResource = AddArtist;
|
||||||
|
UpdateResource = UpdatArtist;
|
||||||
|
DeleteResource = DeleteArtist;
|
||||||
|
|
||||||
|
Validation.RuleBuilderExtensions.ValidId(SharedValidator.RuleFor(s => s.ProfileId));
|
||||||
|
|
||||||
|
SharedValidator.RuleFor(s => s.Path)
|
||||||
|
.Cascade(CascadeMode.StopOnFirstFailure)
|
||||||
|
.IsValidPath()
|
||||||
|
.SetValidator(rootFolderValidator)
|
||||||
|
.SetValidator(seriesPathValidator)
|
||||||
|
.SetValidator(droneFactoryValidator)
|
||||||
|
.SetValidator(seriesAncestorValidator)
|
||||||
|
.When(s => !s.Path.IsNullOrWhiteSpace());
|
||||||
|
|
||||||
|
SharedValidator.RuleFor(s => s.ProfileId).SetValidator(profileExistsValidator);
|
||||||
|
|
||||||
|
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
|
||||||
|
PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace());
|
||||||
|
PostValidator.RuleFor(s => s.ItunesId).GreaterThan(0).SetValidator(artistExistsValidator);
|
||||||
|
|
||||||
|
PutValidator.RuleFor(s => s.Path).IsValidPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArtistResource GetArtist(int id)
|
||||||
|
{
|
||||||
|
var artist = _artistService.GetArtist(id);
|
||||||
|
return MapToResource(artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArtistResource MapToResource(Artist artist)
|
||||||
|
{
|
||||||
|
if (artist == null) return null;
|
||||||
|
|
||||||
|
var resource = artist.ToResource();
|
||||||
|
MapCoversToLocal(resource);
|
||||||
|
//FetchAndLinkSeriesStatistics(resource);
|
||||||
|
//PopulateAlternateTitles(resource);
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<ArtistResource> AllArtist()
|
||||||
|
{
|
||||||
|
//var seriesStats = _seriesStatisticsService.SeriesStatistics();
|
||||||
|
var artistResources = _artistService.GetAllArtists().ToResource();
|
||||||
|
|
||||||
|
MapCoversToLocal(artistResources.ToArray());
|
||||||
|
//LinkSeriesStatistics(seriesResources, seriesStats);
|
||||||
|
//PopulateAlternateTitles(seriesResources);
|
||||||
|
|
||||||
|
return artistResources;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int AddArtist(ArtistResource seriesResource)
|
||||||
|
{
|
||||||
|
var model = seriesResource.ToModel();
|
||||||
|
|
||||||
|
return _addSeriesService.AddArtist(model).Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatArtist(ArtistResource artistResource)
|
||||||
|
{
|
||||||
|
var model = artistResource.ToModel(_artistService.GetArtist(artistResource.Id));
|
||||||
|
|
||||||
|
_artistService.UpdateArtist(model);
|
||||||
|
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, artistResource.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteArtist(int id)
|
||||||
|
{
|
||||||
|
var deleteFiles = false;
|
||||||
|
var deleteFilesQuery = Request.Query.deleteFiles;
|
||||||
|
|
||||||
|
if (deleteFilesQuery.HasValue)
|
||||||
|
{
|
||||||
|
deleteFiles = Convert.ToBoolean(deleteFilesQuery.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_artistService.DeleteArtist(id, deleteFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MapCoversToLocal(params ArtistResource[] artists)
|
||||||
|
{
|
||||||
|
foreach (var artistResource in artists)
|
||||||
|
{
|
||||||
|
_coverMapper.ConvertToLocalUrls(artistResource.Id, artistResource.Images);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(TrackImportedEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, message.ImportedTrack.ItunesTrackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(TrackFileDeletedEvent message)
|
||||||
|
{
|
||||||
|
if (message.Reason == DeleteMediaFileReason.Upgrade) return;
|
||||||
|
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, message.TrackFile.ItunesTrackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ArtistUpdatedEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, message.Artist.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ArtistEditedEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, message.Artist.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ArtistDeletedEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Deleted, message.Artist.ToResource());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ArtistRenamedEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, message.Artist.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
//public void Handle(ArtistDeletedEvent message)
|
||||||
|
//{
|
||||||
|
// BroadcastResourceChange(ModelAction.Deleted, message.Artist.ToResource());
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public void Handle(ArtistRenamedEvent message)
|
||||||
|
//{
|
||||||
|
// BroadcastResourceChange(ModelAction.Updated, message.Artist.Id);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public void Handle(MediaCoversUpdatedEvent message)
|
||||||
|
//{
|
||||||
|
// BroadcastResourceChange(ModelAction.Updated, message.Artist.Id);
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -114,7 +114,7 @@ namespace NzbDrone.Api.Music
|
||||||
Genres = model.Genres,
|
Genres = model.Genres,
|
||||||
Tags = model.Tags,
|
Tags = model.Tags,
|
||||||
Added = model.Added,
|
Added = model.Added,
|
||||||
//AddOptions = resource.AddOptions,
|
AddOptions = model.AddOptions,
|
||||||
//Ratings = resource.Ratings
|
//Ratings = resource.Ratings
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ namespace NzbDrone.Api.Music
|
||||||
Genres = resource.Genres,
|
Genres = resource.Genres,
|
||||||
Tags = resource.Tags,
|
Tags = resource.Tags,
|
||||||
Added = resource.Added,
|
Added = resource.Added,
|
||||||
//AddOptions = resource.AddOptions,
|
AddOptions = resource.AddOptions,
|
||||||
//Ratings = resource.Ratings
|
//Ratings = resource.Ratings
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,6 +113,7 @@
|
||||||
<Compile Include="Indexers\ReleasePushModule.cs" />
|
<Compile Include="Indexers\ReleasePushModule.cs" />
|
||||||
<Compile Include="Music\AlbumResource.cs" />
|
<Compile Include="Music\AlbumResource.cs" />
|
||||||
<Compile Include="Music\ArtistLookupModule.cs" />
|
<Compile Include="Music\ArtistLookupModule.cs" />
|
||||||
|
<Compile Include="Music\ArtistModule.cs" />
|
||||||
<Compile Include="Music\ArtistResource.cs" />
|
<Compile Include="Music\ArtistResource.cs" />
|
||||||
<Compile Include="Parse\ParseModule.cs" />
|
<Compile Include="Parse\ParseModule.cs" />
|
||||||
<Compile Include="Parse\ParseResource.cs" />
|
<Compile Include="Parse\ParseResource.cs" />
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace NzbDrone.Common.Cloud
|
||||||
Services = new HttpRequestBuilder("http://services.lidarr.tv/v1/")
|
Services = new HttpRequestBuilder("http://services.lidarr.tv/v1/")
|
||||||
.CreateFactory();
|
.CreateFactory();
|
||||||
|
|
||||||
Search = new HttpRequestBuilder("https://itunes.apple.com/search/")
|
Search = new HttpRequestBuilder("https://itunes.apple.com/{route}/")
|
||||||
.CreateFactory();
|
.CreateFactory();
|
||||||
|
|
||||||
SkyHookTvdb = new HttpRequestBuilder("http://skyhook.lidarr.tv/v1/tvdb/{route}/{language}/")
|
SkyHookTvdb = new HttpRequestBuilder("http://skyhook.lidarr.tv/v1/tvdb/{route}/{language}/")
|
||||||
|
|
|
@ -33,58 +33,58 @@ namespace NzbDrone.Core.DataAugmentation.Xem
|
||||||
{
|
{
|
||||||
_logger.Debug("Updating scene numbering mapping for: {0}", series);
|
_logger.Debug("Updating scene numbering mapping for: {0}", series);
|
||||||
|
|
||||||
try
|
//try
|
||||||
{
|
//{
|
||||||
var mappings = _xemProxy.GetSceneTvdbMappings(series.TvdbId);
|
// var mappings = _xemProxy.GetSceneTvdbMappings(series.TvdbId);
|
||||||
|
|
||||||
if (!mappings.Any() && !series.UseSceneNumbering)
|
// if (!mappings.Any() && !series.UseSceneNumbering)
|
||||||
{
|
// {
|
||||||
_logger.Debug("Mappings for: {0} are empty, skipping", series);
|
// _logger.Debug("Mappings for: {0} are empty, skipping", series);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
var episodes = _episodeService.GetEpisodeBySeries(series.Id);
|
// var episodes = _episodeService.GetEpisodeBySeries(series.Id);
|
||||||
|
|
||||||
foreach (var episode in episodes)
|
// foreach (var episode in episodes)
|
||||||
{
|
// {
|
||||||
episode.SceneAbsoluteEpisodeNumber = null;
|
// episode.SceneAbsoluteEpisodeNumber = null;
|
||||||
episode.SceneSeasonNumber = null;
|
// episode.SceneSeasonNumber = null;
|
||||||
episode.SceneEpisodeNumber = null;
|
// episode.SceneEpisodeNumber = null;
|
||||||
episode.UnverifiedSceneNumbering = false;
|
// episode.UnverifiedSceneNumbering = false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
foreach (var mapping in mappings)
|
// foreach (var mapping in mappings)
|
||||||
{
|
// {
|
||||||
_logger.Debug("Setting scene numbering mappings for {0} S{1:00}E{2:00}", series, mapping.Tvdb.Season, mapping.Tvdb.Episode);
|
// _logger.Debug("Setting scene numbering mappings for {0} S{1:00}E{2:00}", series, mapping.Tvdb.Season, mapping.Tvdb.Episode);
|
||||||
|
|
||||||
var episode = episodes.SingleOrDefault(e => e.SeasonNumber == mapping.Tvdb.Season && e.EpisodeNumber == mapping.Tvdb.Episode);
|
// var episode = episodes.SingleOrDefault(e => e.SeasonNumber == mapping.Tvdb.Season && e.EpisodeNumber == mapping.Tvdb.Episode);
|
||||||
|
|
||||||
if (episode == null)
|
// if (episode == null)
|
||||||
{
|
// {
|
||||||
_logger.Debug("Information hasn't been added to TheTVDB yet, skipping.");
|
// _logger.Debug("Information hasn't been added to TheTVDB yet, skipping.");
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
|
|
||||||
episode.SceneAbsoluteEpisodeNumber = mapping.Scene.Absolute;
|
// episode.SceneAbsoluteEpisodeNumber = mapping.Scene.Absolute;
|
||||||
episode.SceneSeasonNumber = mapping.Scene.Season;
|
// episode.SceneSeasonNumber = mapping.Scene.Season;
|
||||||
episode.SceneEpisodeNumber = mapping.Scene.Episode;
|
// episode.SceneEpisodeNumber = mapping.Scene.Episode;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (episodes.Any(v => v.SceneEpisodeNumber.HasValue && v.SceneSeasonNumber != 0))
|
// if (episodes.Any(v => v.SceneEpisodeNumber.HasValue && v.SceneSeasonNumber != 0))
|
||||||
{
|
// {
|
||||||
ExtrapolateMappings(series, episodes, mappings);
|
// ExtrapolateMappings(series, episodes, mappings);
|
||||||
}
|
// }
|
||||||
|
|
||||||
_episodeService.UpdateEpisodes(episodes);
|
// _episodeService.UpdateEpisodes(episodes);
|
||||||
series.UseSceneNumbering = mappings.Any();
|
// series.UseSceneNumbering = mappings.Any();
|
||||||
_seriesService.UpdateSeries(series);
|
// _seriesService.UpdateSeries(series);
|
||||||
|
|
||||||
_logger.Debug("XEM mapping updated for {0}", series);
|
// _logger.Debug("XEM mapping updated for {0}", series);
|
||||||
}
|
//}
|
||||||
catch (Exception ex)
|
//catch (Exception ex)
|
||||||
{
|
//{
|
||||||
_logger.Error(ex, "Error updating scene numbering mappings for {0}", series);
|
// _logger.Error(ex, "Error updating scene numbering mappings for {0}", series);
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExtrapolateMappings(Series series, List<Episode> episodes, List<Model.XemSceneTvdbMapping> mappings)
|
private void ExtrapolateMappings(Series series, List<Episode> episodes, List<Model.XemSceneTvdbMapping> mappings)
|
||||||
|
@ -212,32 +212,32 @@ namespace NzbDrone.Core.DataAugmentation.Xem
|
||||||
|
|
||||||
public void Handle(SeriesUpdatedEvent message)
|
public void Handle(SeriesUpdatedEvent message)
|
||||||
{
|
{
|
||||||
if (_cache.IsExpired(TimeSpan.FromHours(3)))
|
//if (_cache.IsExpired(TimeSpan.FromHours(3)))
|
||||||
{
|
//{
|
||||||
UpdateXemSeriesIds();
|
// UpdateXemSeriesIds();
|
||||||
}
|
//}
|
||||||
|
|
||||||
if (_cache.Count == 0)
|
//if (_cache.Count == 0)
|
||||||
{
|
//{
|
||||||
_logger.Debug("Scene numbering is not available");
|
// _logger.Debug("Scene numbering is not available");
|
||||||
return;
|
// return;
|
||||||
}
|
//}
|
||||||
|
|
||||||
if (!_cache.Find(message.Series.TvdbId.ToString()) && !message.Series.UseSceneNumbering)
|
//if (!_cache.Find(message.Series.TvdbId.ToString()) && !message.Series.UseSceneNumbering)
|
||||||
{
|
//{
|
||||||
_logger.Debug("Scene numbering is not available for {0} [{1}]", message.Series.Title, message.Series.TvdbId);
|
// _logger.Debug("Scene numbering is not available for {0} [{1}]", message.Series.Title, message.Series.TvdbId);
|
||||||
return;
|
// return;
|
||||||
}
|
//}
|
||||||
|
|
||||||
PerformUpdate(message.Series);
|
//PerformUpdate(message.Series);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(SeriesRefreshStartingEvent message)
|
public void Handle(SeriesRefreshStartingEvent message)
|
||||||
{
|
{
|
||||||
if (message.ManualTrigger && _cache.IsExpired(TimeSpan.FromMinutes(1)))
|
//if (message.ManualTrigger && _cache.IsExpired(TimeSpan.FromMinutes(1)))
|
||||||
{
|
//{
|
||||||
UpdateXemSeriesIds();
|
// UpdateXemSeriesIds();
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,21 +12,23 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Create.TableForModel("Artists")
|
Create.TableForModel("Artist")
|
||||||
.WithColumn("ItunesId").AsInt32().Unique()
|
.WithColumn("ItunesId").AsInt32().Unique()
|
||||||
.WithColumn("ArtistName").AsString().Unique()
|
.WithColumn("ArtistName").AsString().Unique()
|
||||||
.WithColumn("ArtistSlug").AsString().Unique()
|
.WithColumn("ArtistSlug").AsString().Unique()
|
||||||
.WithColumn("CleanTitle").AsString()
|
.WithColumn("CleanTitle").AsString() // Do we need this?
|
||||||
.WithColumn("Monitored").AsBoolean()
|
.WithColumn("Monitored").AsBoolean()
|
||||||
.WithColumn("LastInfoSync").AsDateTime().Nullable()
|
.WithColumn("LastInfoSync").AsDateTime().Nullable()
|
||||||
.WithColumn("LastDiskSync").AsDateTime().Nullable()
|
.WithColumn("LastDiskSync").AsDateTime().Nullable()
|
||||||
.WithColumn("Overview").AsString()
|
|
||||||
.WithColumn("Status").AsInt32()
|
.WithColumn("Status").AsInt32()
|
||||||
.WithColumn("Path").AsString()
|
.WithColumn("Path").AsString()
|
||||||
.WithColumn("Images").AsString()
|
.WithColumn("Images").AsString()
|
||||||
.WithColumn("QualityProfileId").AsInt32()
|
.WithColumn("QualityProfileId").AsInt32()
|
||||||
.WithColumn("AirTime").AsString().Nullable() // JVM: This might be DropDate instead
|
.WithColumn("Added").AsDateTime()
|
||||||
//.WithColumn("BacklogSetting").AsInt32()
|
.WithColumn("AddOptions").AsString()
|
||||||
|
.WithColumn("AlbumFolder").AsInt32()
|
||||||
|
.WithColumn("Genre").AsString()
|
||||||
|
.WithColumn("Albums").AsString()
|
||||||
;
|
;
|
||||||
|
|
||||||
Create.TableForModel("Albums")
|
Create.TableForModel("Albums")
|
||||||
|
@ -37,7 +39,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
.WithColumn("Image").AsInt32()
|
.WithColumn("Image").AsInt32()
|
||||||
.WithColumn("TrackCount").AsInt32()
|
.WithColumn("TrackCount").AsInt32()
|
||||||
.WithColumn("DiscCount").AsInt32()
|
.WithColumn("DiscCount").AsInt32()
|
||||||
.WithColumn("Monitored").AsBoolean();
|
.WithColumn("Monitored").AsBoolean()
|
||||||
|
.WithColumn("Overview").AsString();
|
||||||
|
|
||||||
Create.TableForModel("Tracks")
|
Create.TableForModel("Tracks")
|
||||||
.WithColumn("ItunesTrackId").AsInt32().Unique()
|
.WithColumn("ItunesTrackId").AsInt32().Unique()
|
||||||
|
|
31
src/NzbDrone.Core/Exceptions/ArtistNotFoundException.cs
Normal file
31
src/NzbDrone.Core/Exceptions/ArtistNotFoundException.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Exceptions
|
||||||
|
{
|
||||||
|
public class ArtistNotFoundException : NzbDroneException
|
||||||
|
{
|
||||||
|
public int ItunesId { get; set; }
|
||||||
|
|
||||||
|
public ArtistNotFoundException(int itunesId)
|
||||||
|
: base(string.Format("Series with iTunesId {0} was not found, it may have been removed from iTunes.", itunesId))
|
||||||
|
{
|
||||||
|
ItunesId = itunesId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArtistNotFoundException(int itunesId, string message, params object[] args)
|
||||||
|
: base(message, args)
|
||||||
|
{
|
||||||
|
ItunesId = itunesId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArtistNotFoundException(int itunesId, string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
ItunesId = itunesId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
src/NzbDrone.Core/MediaFiles/Events/ArtistRenamedEvent.cs
Normal file
19
src/NzbDrone.Core/MediaFiles/Events/ArtistRenamedEvent.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Events
|
||||||
|
{
|
||||||
|
public class ArtistRenamedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Artist Artist { get; private set; }
|
||||||
|
|
||||||
|
public ArtistRenamedEvent(Artist artist)
|
||||||
|
{
|
||||||
|
Artist = artist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/NzbDrone.Core/MediaFiles/Events/TrackFileDeletedEvent.cs
Normal file
20
src/NzbDrone.Core/MediaFiles/Events/TrackFileDeletedEvent.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Events
|
||||||
|
{
|
||||||
|
public class TrackFileDeletedEvent : IEvent
|
||||||
|
{
|
||||||
|
public TrackFile TrackFile { get; private set; }
|
||||||
|
public DeleteMediaFileReason Reason { get; private set; }
|
||||||
|
|
||||||
|
public TrackFileDeletedEvent(TrackFile trackFile, DeleteMediaFileReason reason)
|
||||||
|
{
|
||||||
|
TrackFile = trackFile;
|
||||||
|
Reason = reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
src/NzbDrone.Core/MediaFiles/Events/TrackImportedEvent.cs
Normal file
36
src/NzbDrone.Core/MediaFiles/Events/TrackImportedEvent.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MediaFiles.Events
|
||||||
|
{
|
||||||
|
public class TrackImportedEvent : IEvent
|
||||||
|
{
|
||||||
|
public LocalTrack TrackInfo { get; private set; }
|
||||||
|
public TrackFile ImportedTrack { get; private set; }
|
||||||
|
public bool NewDownload { get; private set; }
|
||||||
|
public string DownloadClient { get; private set; }
|
||||||
|
public string DownloadId { get; private set; }
|
||||||
|
public bool IsReadOnly { get; set; }
|
||||||
|
|
||||||
|
public TrackImportedEvent(LocalTrack trackInfo, TrackFile importedTrack, bool newDownload)
|
||||||
|
{
|
||||||
|
TrackInfo = trackInfo;
|
||||||
|
ImportedTrack = importedTrack;
|
||||||
|
NewDownload = newDownload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TrackImportedEvent(LocalTrack trackInfo, TrackFile importedTrack, bool newDownload, string downloadClient, string downloadId, bool isReadOnly)
|
||||||
|
{
|
||||||
|
TrackInfo = trackInfo;
|
||||||
|
ImportedTrack = importedTrack;
|
||||||
|
NewDownload = newDownload;
|
||||||
|
DownloadClient = downloadClient;
|
||||||
|
DownloadId = downloadId;
|
||||||
|
IsReadOnly = isReadOnly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs
Normal file
11
src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
|
{
|
||||||
|
public interface IProvideArtistInfo
|
||||||
|
{
|
||||||
|
Tuple<Artist, List<Track>> GetArtistInfo(int itunesId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MetadataSource.SkyHook
|
namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
{
|
{
|
||||||
public class SkyHookProxy : IProvideSeriesInfo, ISearchForNewSeries
|
public class SkyHookProxy : IProvideSeriesInfo, IProvideArtistInfo, ISearchForNewSeries
|
||||||
{
|
{
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
@ -124,6 +124,39 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Tuple<Artist, List<Track>> GetArtistInfo(int itunesId)
|
||||||
|
{
|
||||||
|
Console.WriteLine("[GetArtistInfo] id:" + itunesId);
|
||||||
|
//https://itunes.apple.com/lookup?id=909253
|
||||||
|
var httpRequest = _requestBuilder.Create()
|
||||||
|
.SetSegment("route", "lookup")
|
||||||
|
.AddQueryParam("id", itunesId.ToString())
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
httpRequest.AllowAutoRedirect = true;
|
||||||
|
httpRequest.SuppressHttpError = true;
|
||||||
|
|
||||||
|
var httpResponse = _httpClient.Get<ArtistResource>(httpRequest);
|
||||||
|
|
||||||
|
if (httpResponse.HasHttpError)
|
||||||
|
{
|
||||||
|
if (httpResponse.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
throw new ArtistNotFoundException(itunesId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new HttpException(httpRequest, httpResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("GetArtistInfo, GetArtistInfo");
|
||||||
|
//var tracks = httpResponse.Resource.Episodes.Select(MapEpisode);
|
||||||
|
//var artist = MapArtist(httpResponse.Resource);
|
||||||
|
// I don't know how we are getting tracks from iTunes yet.
|
||||||
|
return new Tuple<Artist, List<Track>>(MapArtists(httpResponse.Resource)[0], new List<Track>());
|
||||||
|
//return new Tuple<Artist, List<Track>>(artist, tracks.ToList());
|
||||||
|
}
|
||||||
public List<Artist> SearchForNewArtist(string title)
|
public List<Artist> SearchForNewArtist(string title)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -142,52 +175,27 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
return new List<Artist>();
|
return new List<Artist>();
|
||||||
}
|
}
|
||||||
|
|
||||||
//try
|
try
|
||||||
//{
|
{
|
||||||
// return new List<Artist> { GetArtistInfo(itunesId).Item1 };
|
return new List<Artist> { GetArtistInfo(itunesId).Item1 };
|
||||||
//}
|
}
|
||||||
//catch (ArtistNotFoundException)
|
catch (ArtistNotFoundException)
|
||||||
//{
|
{
|
||||||
// return new List<Artist>();
|
return new List<Artist>();
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpRequest = _requestBuilder.Create()
|
var httpRequest = _requestBuilder.Create()
|
||||||
|
.SetSegment("route", "search")
|
||||||
.AddQueryParam("entity", "album")
|
.AddQueryParam("entity", "album")
|
||||||
.AddQueryParam("term", title.ToLower().Trim())
|
.AddQueryParam("term", title.ToLower().Trim())
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Console.WriteLine("httpRequest: ", httpRequest);
|
|
||||||
|
|
||||||
var httpResponse = _httpClient.Get<ArtistResource>(httpRequest);
|
var httpResponse = _httpClient.Get<ArtistResource>(httpRequest);
|
||||||
|
|
||||||
Album tempAlbum;
|
return MapArtists(httpResponse.Resource);
|
||||||
List<Artist> artists = new List<Artist>();
|
|
||||||
foreach (var album in httpResponse.Resource.Results)
|
|
||||||
{
|
|
||||||
int index = artists.FindIndex(a => a.ItunesId == album.ArtistId);
|
|
||||||
tempAlbum = MapAlbum(album);
|
|
||||||
|
|
||||||
if (index >= 0)
|
|
||||||
{
|
|
||||||
artists[index].Albums.Add(tempAlbum);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Artist tempArtist = new Artist();
|
|
||||||
// TODO: Perform the MapArtist call here
|
|
||||||
tempArtist.ItunesId = album.ArtistId;
|
|
||||||
tempArtist.ArtistName = album.ArtistName;
|
|
||||||
tempArtist.Genres.Add(album.PrimaryGenreName);
|
|
||||||
tempArtist.Albums.Add(tempAlbum);
|
|
||||||
artists.Add(tempArtist);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return artists;
|
|
||||||
}
|
}
|
||||||
catch (HttpException)
|
catch (HttpException)
|
||||||
{
|
{
|
||||||
|
@ -200,6 +208,34 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Artist> MapArtists(ArtistResource resource)
|
||||||
|
{
|
||||||
|
Album tempAlbum;
|
||||||
|
List<Artist> artists = new List<Artist>();
|
||||||
|
foreach (var album in resource.Results)
|
||||||
|
{
|
||||||
|
int index = artists.FindIndex(a => a.ItunesId == album.ArtistId);
|
||||||
|
tempAlbum = MapAlbum(album);
|
||||||
|
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
artists[index].Albums.Add(tempAlbum);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Artist tempArtist = new Artist();
|
||||||
|
tempArtist.ItunesId = album.ArtistId;
|
||||||
|
tempArtist.ArtistName = album.ArtistName;
|
||||||
|
tempArtist.Genres.Add(album.PrimaryGenreName);
|
||||||
|
tempArtist.Albums.Add(tempAlbum);
|
||||||
|
artists.Add(tempArtist);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return artists;
|
||||||
|
}
|
||||||
|
|
||||||
private Album MapAlbum(AlbumResource albumQuery)
|
private Album MapAlbum(AlbumResource albumQuery)
|
||||||
{
|
{
|
||||||
Album album = new Album();
|
Album album = new Album();
|
||||||
|
|
13
src/NzbDrone.Core/Music/AddArtistOptions.cs
Normal file
13
src/NzbDrone.Core/Music/AddArtistOptions.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music
|
||||||
|
{
|
||||||
|
public class AddArtistOptions : MonitoringOptions
|
||||||
|
{
|
||||||
|
public bool SearchForMissingTracks { get; set; }
|
||||||
|
}
|
||||||
|
}
|
101
src/NzbDrone.Core/Music/AddArtistService.cs
Normal file
101
src/NzbDrone.Core/Music/AddArtistService.cs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using FluentValidation;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.EnsureThat;
|
||||||
|
using NzbDrone.Core.Exceptions;
|
||||||
|
using NzbDrone.Core.MetadataSource;
|
||||||
|
using NzbDrone.Core.Organizer;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using NzbDrone.Core.MetadataSource.SkyHook;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music
|
||||||
|
{
|
||||||
|
public interface IAddArtistService
|
||||||
|
{
|
||||||
|
Artist AddArtist(Artist newArtist);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddSeriesService : IAddArtistService
|
||||||
|
{
|
||||||
|
private readonly IArtistService _artistService;
|
||||||
|
private readonly IProvideArtistInfo _artistInfo;
|
||||||
|
private readonly IBuildFileNames _fileNameBuilder;
|
||||||
|
private readonly IAddArtistValidator _addArtistValidator;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public AddSeriesService(IArtistService artistService,
|
||||||
|
IProvideArtistInfo artistInfo,
|
||||||
|
IBuildFileNames fileNameBuilder,
|
||||||
|
IAddArtistValidator addArtistValidator,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_artistService = artistService;
|
||||||
|
_artistInfo = artistInfo;
|
||||||
|
_fileNameBuilder = fileNameBuilder;
|
||||||
|
_addArtistValidator = addArtistValidator;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Artist AddArtist(Artist newArtist)
|
||||||
|
{
|
||||||
|
Ensure.That(newArtist, () => newArtist).IsNotNull();
|
||||||
|
|
||||||
|
newArtist = AddSkyhookData(newArtist);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(newArtist.Path))
|
||||||
|
{
|
||||||
|
var folderName = newArtist.ArtistName;// _fileNameBuilder.GetArtistFolder(newArtist);
|
||||||
|
newArtist.Path = Path.Combine(newArtist.RootFolderPath, folderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
newArtist.CleanTitle = newArtist.ArtistName.CleanSeriesTitle();
|
||||||
|
newArtist.SortTitle = ArtistNameNormalizer.Normalize(newArtist.ArtistName, newArtist.ItunesId);
|
||||||
|
newArtist.Added = DateTime.UtcNow;
|
||||||
|
|
||||||
|
var validationResult = _addArtistValidator.Validate(newArtist);
|
||||||
|
|
||||||
|
if (!validationResult.IsValid)
|
||||||
|
{
|
||||||
|
throw new ValidationException(validationResult.Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Info("Adding Series {0} Path: [{1}]", newArtist, newArtist.Path);
|
||||||
|
_artistService.AddArtist(newArtist);
|
||||||
|
|
||||||
|
return newArtist;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Artist AddSkyhookData(Artist newArtist)
|
||||||
|
{
|
||||||
|
Tuple<Artist, List<Track>> tuple;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tuple = _artistInfo.GetArtistInfo(newArtist.ItunesId);
|
||||||
|
}
|
||||||
|
catch (SeriesNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.Error("tvdbid {1} was not found, it may have been removed from TheTVDB.", newArtist.ItunesId);
|
||||||
|
|
||||||
|
throw new ValidationException(new List<ValidationFailure>
|
||||||
|
{
|
||||||
|
new ValidationFailure("TvdbId", "A series with this ID was not found", newArtist.ItunesId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var artist = tuple.Item1;
|
||||||
|
|
||||||
|
// If seasons were passed in on the new series use them, otherwise use the seasons from Skyhook
|
||||||
|
// TODO: Refactor for albums
|
||||||
|
newArtist.Albums = newArtist.Albums != null && newArtist.Albums.Any() ? newArtist.Albums : artist.Albums;
|
||||||
|
|
||||||
|
artist.ApplyChanges(newArtist);
|
||||||
|
|
||||||
|
return artist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/NzbDrone.Core/Music/AddArtistValidator.cs
Normal file
34
src/NzbDrone.Core/Music/AddArtistValidator.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using FluentValidation;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Core.Validation.Paths;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music
|
||||||
|
{
|
||||||
|
public interface IAddArtistValidator
|
||||||
|
{
|
||||||
|
ValidationResult Validate(Artist instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddArtistValidator : AbstractValidator<Artist>, IAddArtistValidator
|
||||||
|
{
|
||||||
|
public AddArtistValidator(RootFolderValidator rootFolderValidator,
|
||||||
|
SeriesPathValidator seriesPathValidator,
|
||||||
|
DroneFactoryValidator droneFactoryValidator,
|
||||||
|
SeriesAncestorValidator seriesAncestorValidator,
|
||||||
|
ArtistSlugValidator seriesTitleSlugValidator)
|
||||||
|
{
|
||||||
|
RuleFor(c => c.Path).Cascade(CascadeMode.StopOnFirstFailure)
|
||||||
|
.IsValidPath()
|
||||||
|
.SetValidator(rootFolderValidator)
|
||||||
|
.SetValidator(seriesPathValidator)
|
||||||
|
.SetValidator(droneFactoryValidator)
|
||||||
|
.SetValidator(seriesAncestorValidator);
|
||||||
|
|
||||||
|
RuleFor(c => c.ArtistSlug).SetValidator(seriesTitleSlugValidator);// TODO: Check if we are going to use a slug or artistName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Profiles;
|
using NzbDrone.Core.Profiles;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -56,7 +57,7 @@ namespace NzbDrone.Core.Music
|
||||||
public HashSet<int> Tags { get; set; }
|
public HashSet<int> Tags { get; set; }
|
||||||
public bool ArtistFolder { get; set; }
|
public bool ArtistFolder { get; set; }
|
||||||
|
|
||||||
//public AddSeriesOptions AddOptions { get; set; } // TODO: Learn what this does
|
public AddSeriesOptions AddOptions { get; set; } // TODO: Learn what this does
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
@ -78,7 +79,7 @@ namespace NzbDrone.Core.Music
|
||||||
//SeriesType = otherArtist.SeriesType;
|
//SeriesType = otherArtist.SeriesType;
|
||||||
RootFolderPath = otherArtist.RootFolderPath;
|
RootFolderPath = otherArtist.RootFolderPath;
|
||||||
Tags = otherArtist.Tags;
|
Tags = otherArtist.Tags;
|
||||||
//AddOptions = otherArtist.AddOptions;
|
AddOptions = otherArtist.AddOptions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
28
src/NzbDrone.Core/Music/ArtistNameNormalizer.cs
Normal file
28
src/NzbDrone.Core/Music/ArtistNameNormalizer.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music
|
||||||
|
{
|
||||||
|
|
||||||
|
public static class ArtistNameNormalizer
|
||||||
|
{
|
||||||
|
private readonly static Dictionary<int, string> PreComputedTitles = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
{ 281588, "a to z" },
|
||||||
|
{ 266757, "ad trials triumph early church" },
|
||||||
|
{ 289260, "ad bible continues"}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string Normalize(string title, int iTunesId)
|
||||||
|
{
|
||||||
|
if (PreComputedTitles.ContainsKey(iTunesId))
|
||||||
|
{
|
||||||
|
return PreComputedTitles[iTunesId];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Parser.Parser.NormalizeTitle(title).ToLower();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,129 +1,40 @@
|
||||||
using NzbDrone.Core.Datastore;
|
using System.Linq;
|
||||||
using System;
|
using NzbDrone.Core.Datastore;
|
||||||
using System.Collections.Generic;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Music
|
namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
public interface IArtistRepository : IBasicRepository<Artist>
|
public interface IArtistRepository : IBasicRepository<Artist>
|
||||||
{
|
{
|
||||||
bool ArtistPathExists(string path);
|
bool ArtistPathExists(string path);
|
||||||
Artist FindByTitle(string cleanTitle);
|
Artist FindByName(string cleanTitle);
|
||||||
Artist FindByItunesId(int iTunesId);
|
Artist FindByItunesId(int iTunesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ArtistRepository : IArtistRepository
|
public class ArtistRepository : BasicRepository<Artist>, IArtistRepository
|
||||||
{
|
{
|
||||||
public IEnumerable<Artist> All()
|
public ArtistRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||||
|
: base(database, eventAggregator)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public bool ArtistPathExists(string path)
|
public bool ArtistPathExists(string path)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return Query.Where(c => c.Path == path).Any();
|
||||||
}
|
|
||||||
|
|
||||||
public int Count()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(Artist model)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Delete(int id)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteMany(IEnumerable<int> ids)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteMany(List<Artist> model)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Artist FindByItunesId(int iTunesId)
|
public Artist FindByItunesId(int iTunesId)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return Query.Where(s => s.ItunesId == iTunesId).SingleOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Artist FindByTitle(string cleanTitle)
|
public Artist FindByName(string cleanName)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
cleanName = cleanName.ToLowerInvariant();
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Artist> Get(IEnumerable<int> ids)
|
return Query.Where(s => s.CleanTitle == cleanName)
|
||||||
{
|
.SingleOrDefault();
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Artist Get(int id)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PagingSpec<Artist> GetPaged(PagingSpec<Artist> pagingSpec)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HasItems()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Artist Insert(Artist model)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void InsertMany(IList<Artist> model)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Purge(bool vacuum = false)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetFields(Artist model, params Expression<Func<Artist, object>>[] properties)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Artist Single()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Artist SingleOrDefault()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Artist Update(Artist model)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateMany(IList<Artist> model)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Artist Upsert(Artist model)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Music.Events;
|
||||||
using NzbDrone.Core.Organizer;
|
using NzbDrone.Core.Organizer;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Music
|
namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
|
@ -14,7 +18,7 @@ namespace NzbDrone.Core.Music
|
||||||
List<Artist> GetArtists(IEnumerable<int> artistIds);
|
List<Artist> GetArtists(IEnumerable<int> artistIds);
|
||||||
Artist AddArtist(Artist newArtist);
|
Artist AddArtist(Artist newArtist);
|
||||||
Artist FindByItunesId(int itunesId);
|
Artist FindByItunesId(int itunesId);
|
||||||
Artist FindByTitle(string title);
|
Artist FindByName(string title);
|
||||||
Artist FindByTitleInexact(string title);
|
Artist FindByTitleInexact(string title);
|
||||||
void DeleteArtist(int artistId, bool deleteFiles);
|
void DeleteArtist(int artistId, bool deleteFiles);
|
||||||
List<Artist> GetAllArtists();
|
List<Artist> GetAllArtists();
|
||||||
|
@ -32,29 +36,47 @@ namespace NzbDrone.Core.Music
|
||||||
private readonly IBuildFileNames _fileNameBuilder;
|
private readonly IBuildFileNames _fileNameBuilder;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public ArtistService(IArtistRepository artistRepository,
|
||||||
|
IEventAggregator eventAggregator,
|
||||||
|
ITrackService trackService,
|
||||||
|
IBuildFileNames fileNameBuilder,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_artistRepository = artistRepository;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
_trackService = trackService;
|
||||||
|
_fileNameBuilder = fileNameBuilder;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
public Artist AddArtist(Artist newArtist)
|
public Artist AddArtist(Artist newArtist)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
_artistRepository.Insert(newArtist);
|
||||||
|
_eventAggregator.PublishEvent(new ArtistAddedEvent(GetArtist(newArtist.Id)));
|
||||||
|
|
||||||
|
return newArtist;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ArtistPathExists(string folder)
|
public bool ArtistPathExists(string folder)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return _artistRepository.ArtistPathExists(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteArtist(int artistId, bool deleteFiles)
|
public void DeleteArtist(int artistId, bool deleteFiles)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var artist = _artistRepository.Get(artistId);
|
||||||
|
_artistRepository.Delete(artistId);
|
||||||
|
_eventAggregator.PublishEvent(new ArtistDeletedEvent(artist, deleteFiles));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Artist FindByItunesId(int itunesId)
|
public Artist FindByItunesId(int itunesId)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return _artistRepository.FindByItunesId(itunesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Artist FindByTitle(string title)
|
public Artist FindByName(string title)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return _artistRepository.FindByName(title.CleanArtistTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Artist FindByTitleInexact(string title)
|
public Artist FindByTitleInexact(string title)
|
||||||
|
@ -64,32 +86,70 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
public List<Artist> GetAllArtists()
|
public List<Artist> GetAllArtists()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
_logger.Debug("Count of repository: " + _artistRepository.Count());
|
||||||
|
// TEMP: Return empty list while we debug the DB error
|
||||||
|
return new List<Artist>();
|
||||||
|
//return _artistRepository.All().ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Artist GetArtist(int artistId)
|
public Artist GetArtist(int artistId)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return _artistRepository.Get(artistId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Artist> GetArtists(IEnumerable<int> artistIds)
|
public List<Artist> GetArtists(IEnumerable<int> artistIds)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
return _artistRepository.Get(artistIds).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveAddOptions(Artist artist)
|
public void RemoveAddOptions(Artist artist)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
_artistRepository.SetFields(artist, s => s.AddOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Artist UpdateArtist(Artist artist)
|
public Artist UpdateArtist(Artist artist)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var storedArtist = GetArtist(artist.Id); // Is it Id or iTunesId?
|
||||||
|
|
||||||
|
foreach (var album in artist.Albums)
|
||||||
|
{
|
||||||
|
var storedAlbum = storedArtist.Albums.SingleOrDefault(s => s.AlbumId == album.AlbumId);
|
||||||
|
|
||||||
|
if (storedAlbum != null && album.Monitored != storedAlbum.Monitored)
|
||||||
|
{
|
||||||
|
_trackService.SetTrackMonitoredByAlbum(artist.Id, album.AlbumId, album.Monitored);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedArtist = _artistRepository.Update(artist);
|
||||||
|
_eventAggregator.PublishEvent(new ArtistEditedEvent(updatedArtist, storedArtist));
|
||||||
|
|
||||||
|
return updatedArtist;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Artist> UpdateArtists(List<Artist> artist)
|
public List<Artist> UpdateArtists(List<Artist> artist)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
_logger.Debug("Updating {0} artist", artist.Count);
|
||||||
|
foreach (var s in artist)
|
||||||
|
{
|
||||||
|
_logger.Trace("Updating: {0}", s.ArtistName);
|
||||||
|
if (!s.RootFolderPath.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
var folderName = new DirectoryInfo(s.Path).Name;
|
||||||
|
s.Path = Path.Combine(s.RootFolderPath, folderName);
|
||||||
|
_logger.Trace("Changing path for {0} to {1}", s.ArtistName, s.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Trace("Not changing path for: {0}", s.ArtistName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_artistRepository.UpdateMany(artist);
|
||||||
|
_logger.Debug("{0} artists updated", artist.Count);
|
||||||
|
|
||||||
|
return artist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
29
src/NzbDrone.Core/Music/ArtistSlugValidator.cs
Normal file
29
src/NzbDrone.Core/Music/ArtistSlugValidator.cs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
using FluentValidation.Validators;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music
|
||||||
|
{
|
||||||
|
public class ArtistSlugValidator : PropertyValidator
|
||||||
|
{
|
||||||
|
private readonly IArtistService _artistService;
|
||||||
|
|
||||||
|
public ArtistSlugValidator(IArtistService artistService)
|
||||||
|
: base("Title slug is in use by another artist with a similar name")
|
||||||
|
{
|
||||||
|
_artistService = artistService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsValid(PropertyValidatorContext context)
|
||||||
|
{
|
||||||
|
if (context.PropertyValue == null) return true;
|
||||||
|
|
||||||
|
dynamic instance = context.ParentContext.InstanceToValidate;
|
||||||
|
var instanceId = (int)instance.Id;
|
||||||
|
|
||||||
|
return !_artistService.GetAllArtists().Exists(s => s.ArtistSlug.Equals(context.PropertyValue.ToString()) && s.Id != instanceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/NzbDrone.Core/Music/Events/ArtistAddedEvent.cs
Normal file
18
src/NzbDrone.Core/Music/Events/ArtistAddedEvent.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music.Events
|
||||||
|
{
|
||||||
|
public class ArtistAddedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Artist Artist { get; private set; }
|
||||||
|
|
||||||
|
public ArtistAddedEvent(Artist artist)
|
||||||
|
{
|
||||||
|
Artist = artist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/NzbDrone.Core/Music/Events/ArtistDeletedEvent.cs
Normal file
20
src/NzbDrone.Core/Music/Events/ArtistDeletedEvent.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music.Events
|
||||||
|
{
|
||||||
|
public class ArtistDeletedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Artist Artist { get; private set; }
|
||||||
|
public bool DeleteFiles { get; private set; }
|
||||||
|
|
||||||
|
public ArtistDeletedEvent(Artist artist, bool deleteFiles)
|
||||||
|
{
|
||||||
|
Artist = artist;
|
||||||
|
DeleteFiles = deleteFiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/NzbDrone.Core/Music/Events/ArtistEditedEvent.cs
Normal file
20
src/NzbDrone.Core/Music/Events/ArtistEditedEvent.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music.Events
|
||||||
|
{
|
||||||
|
public class ArtistEditedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Artist Artist { get; private set; }
|
||||||
|
public Artist OldArtist { get; private set; }
|
||||||
|
|
||||||
|
public ArtistEditedEvent(Artist artist, Artist oldArtist)
|
||||||
|
{
|
||||||
|
Artist = artist;
|
||||||
|
OldArtist = oldArtist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/NzbDrone.Core/Music/Events/ArtistUpdatedEvent.cs
Normal file
14
src/NzbDrone.Core/Music/Events/ArtistUpdatedEvent.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music.Events
|
||||||
|
{
|
||||||
|
public class ArtistUpdatedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Artist Artist { get; private set; }
|
||||||
|
|
||||||
|
public ArtistUpdatedEvent(Artist artist)
|
||||||
|
{
|
||||||
|
Artist = artist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -518,6 +518,7 @@
|
||||||
<Compile Include="Download\ProcessDownloadDecisions.cs" />
|
<Compile Include="Download\ProcessDownloadDecisions.cs" />
|
||||||
<Compile Include="Download\ProcessedDecisions.cs" />
|
<Compile Include="Download\ProcessedDecisions.cs" />
|
||||||
<Compile Include="Download\RedownloadFailedDownloadService.cs" />
|
<Compile Include="Download\RedownloadFailedDownloadService.cs" />
|
||||||
|
<Compile Include="Exceptions\ArtistNotFoundException.cs" />
|
||||||
<Compile Include="Exceptions\BadRequestException.cs" />
|
<Compile Include="Exceptions\BadRequestException.cs" />
|
||||||
<Compile Include="Exceptions\DownstreamException.cs" />
|
<Compile Include="Exceptions\DownstreamException.cs" />
|
||||||
<Compile Include="Exceptions\NzbDroneClientException.cs" />
|
<Compile Include="Exceptions\NzbDroneClientException.cs" />
|
||||||
|
@ -758,6 +759,7 @@
|
||||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\SameEpisodesImportSpecification.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\Specifications\SameEpisodesImportSpecification.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\UnverifiedSceneNumberingSpecification.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\Specifications\UnverifiedSceneNumberingSpecification.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecification.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecification.cs" />
|
||||||
|
<Compile Include="MediaFiles\Events\ArtistRenamedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Events\EpisodeDownloadedEvent.cs" />
|
<Compile Include="MediaFiles\Events\EpisodeDownloadedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Events\EpisodeFileAddedEvent.cs" />
|
<Compile Include="MediaFiles\Events\EpisodeFileAddedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Events\EpisodeFileDeletedEvent.cs" />
|
<Compile Include="MediaFiles\Events\EpisodeFileDeletedEvent.cs" />
|
||||||
|
@ -766,6 +768,8 @@
|
||||||
<Compile Include="MediaFiles\Events\SeriesRenamedEvent.cs" />
|
<Compile Include="MediaFiles\Events\SeriesRenamedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Events\SeriesScanSkippedEvent.cs" />
|
<Compile Include="MediaFiles\Events\SeriesScanSkippedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Events\SeriesScannedEvent.cs" />
|
<Compile Include="MediaFiles\Events\SeriesScannedEvent.cs" />
|
||||||
|
<Compile Include="MediaFiles\Events\TrackFileDeletedEvent.cs" />
|
||||||
|
<Compile Include="MediaFiles\Events\TrackImportedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\FileDateType.cs" />
|
<Compile Include="MediaFiles\FileDateType.cs" />
|
||||||
<Compile Include="MediaFiles\MediaFileAttributeService.cs" />
|
<Compile Include="MediaFiles\MediaFileAttributeService.cs" />
|
||||||
<Compile Include="MediaFiles\MediaFileExtensions.cs" />
|
<Compile Include="MediaFiles\MediaFileExtensions.cs" />
|
||||||
|
@ -808,6 +812,7 @@
|
||||||
<Compile Include="Messaging\Events\IEventAggregator.cs" />
|
<Compile Include="Messaging\Events\IEventAggregator.cs" />
|
||||||
<Compile Include="Messaging\Events\IHandle.cs" />
|
<Compile Include="Messaging\Events\IHandle.cs" />
|
||||||
<Compile Include="Messaging\IProcessMessage.cs" />
|
<Compile Include="Messaging\IProcessMessage.cs" />
|
||||||
|
<Compile Include="MetadataSource\IProvideArtistInfo.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\Resource\ActorResource.cs" />
|
<Compile Include="MetadataSource\SkyHook\Resource\ActorResource.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\Resource\ArtistResource.cs" />
|
<Compile Include="MetadataSource\SkyHook\Resource\ArtistResource.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\Resource\EpisodeResource.cs" />
|
<Compile Include="MetadataSource\SkyHook\Resource\EpisodeResource.cs" />
|
||||||
|
@ -840,10 +845,19 @@
|
||||||
<Compile Include="Extras\Metadata\MetadataType.cs" />
|
<Compile Include="Extras\Metadata\MetadataType.cs" />
|
||||||
<Compile Include="MetadataSource\IProvideSeriesInfo.cs" />
|
<Compile Include="MetadataSource\IProvideSeriesInfo.cs" />
|
||||||
<Compile Include="MetadataSource\ISearchForNewSeries.cs" />
|
<Compile Include="MetadataSource\ISearchForNewSeries.cs" />
|
||||||
|
<Compile Include="Music\AddArtistOptions.cs" />
|
||||||
|
<Compile Include="Music\AddArtistService.cs" />
|
||||||
|
<Compile Include="Music\AddArtistValidator.cs" />
|
||||||
<Compile Include="Music\Album.cs" />
|
<Compile Include="Music\Album.cs" />
|
||||||
<Compile Include="Music\Artist.cs" />
|
<Compile Include="Music\Artist.cs" />
|
||||||
|
<Compile Include="Music\ArtistNameNormalizer.cs" />
|
||||||
|
<Compile Include="Music\ArtistSlugValidator.cs" />
|
||||||
<Compile Include="Music\ArtistRepository.cs" />
|
<Compile Include="Music\ArtistRepository.cs" />
|
||||||
<Compile Include="Music\ArtistService.cs" />
|
<Compile Include="Music\ArtistService.cs" />
|
||||||
|
<Compile Include="Music\Events\ArtistAddedEvent.cs" />
|
||||||
|
<Compile Include="Music\Events\ArtistDeletedEvent.cs" />
|
||||||
|
<Compile Include="Music\Events\ArtistEditedEvent.cs" />
|
||||||
|
<Compile Include="Music\Events\ArtistUpdatedEvent.cs" />
|
||||||
<Compile Include="Music\Track.cs" />
|
<Compile Include="Music\Track.cs" />
|
||||||
<Compile Include="Music\TrackService.cs" />
|
<Compile Include="Music\TrackService.cs" />
|
||||||
<Compile Include="Notifications\Join\JoinAuthException.cs" />
|
<Compile Include="Notifications\Join\JoinAuthException.cs" />
|
||||||
|
@ -1141,6 +1155,8 @@
|
||||||
<Compile Include="Validation\NzbDroneValidationFailure.cs" />
|
<Compile Include="Validation\NzbDroneValidationFailure.cs" />
|
||||||
<Compile Include="Validation\NzbDroneValidationResult.cs" />
|
<Compile Include="Validation\NzbDroneValidationResult.cs" />
|
||||||
<Compile Include="Validation\NzbDroneValidationState.cs" />
|
<Compile Include="Validation\NzbDroneValidationState.cs" />
|
||||||
|
<Compile Include="Validation\Paths\ArtistExistsValidator.cs" />
|
||||||
|
<Compile Include="Validation\Paths\ArtistPathValidator.cs" />
|
||||||
<Compile Include="Validation\Paths\MappedNetworkDriveValidator.cs" />
|
<Compile Include="Validation\Paths\MappedNetworkDriveValidator.cs" />
|
||||||
<Compile Include="Validation\Paths\DroneFactoryValidator.cs" />
|
<Compile Include="Validation\Paths\DroneFactoryValidator.cs" />
|
||||||
<Compile Include="Validation\Paths\FolderWritableValidator.cs" />
|
<Compile Include="Validation\Paths\FolderWritableValidator.cs" />
|
||||||
|
|
|
@ -11,6 +11,7 @@ using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Organizer
|
namespace NzbDrone.Core.Organizer
|
||||||
{
|
{
|
||||||
|
@ -22,6 +23,9 @@ namespace NzbDrone.Core.Organizer
|
||||||
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
||||||
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
|
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
|
||||||
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
|
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
|
||||||
|
|
||||||
|
// TODO: Implement Music functions
|
||||||
|
//string GetArtistFolder(Artist artist, NamingConfig namingConfig = null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileNameBuilder : IBuildFileNames
|
public class FileNameBuilder : IBuildFileNames
|
||||||
|
@ -278,6 +282,12 @@ namespace NzbDrone.Core.Organizer
|
||||||
tokenHandlers["{Series CleanTitle}"] = m => CleanTitle(series.Title);
|
tokenHandlers["{Series CleanTitle}"] = m => CleanTitle(series.Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddArtistTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Artist artist)
|
||||||
|
{
|
||||||
|
tokenHandlers["{Artist Name}"] = m => artist.ArtistName;
|
||||||
|
tokenHandlers["{Artist CleanTitle}"] = m => CleanTitle(artist.ArtistName);
|
||||||
|
}
|
||||||
|
|
||||||
private string AddSeasonEpisodeNumberingTokens(string pattern, Dictionary<string, Func<TokenMatch, string>> tokenHandlers, List<Episode> episodes, NamingConfig namingConfig)
|
private string AddSeasonEpisodeNumberingTokens(string pattern, Dictionary<string, Func<TokenMatch, string>> tokenHandlers, List<Episode> episodes, NamingConfig namingConfig)
|
||||||
{
|
{
|
||||||
var episodeFormats = GetEpisodeFormat(pattern).DistinctBy(v => v.SeasonEpisodePattern).ToList();
|
var episodeFormats = GetEpisodeFormat(pattern).DistinctBy(v => v.SeasonEpisodePattern).ToList();
|
||||||
|
@ -768,6 +778,36 @@ namespace NzbDrone.Core.Organizer
|
||||||
|
|
||||||
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
|
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//public string GetArtistFolder(Artist artist, NamingConfig namingConfig = null)
|
||||||
|
//{
|
||||||
|
// if (namingConfig == null)
|
||||||
|
// {
|
||||||
|
// namingConfig = _namingConfigService.GetConfig();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
|
|
||||||
|
// AddArtistTokens(tokenHandlers, artist);
|
||||||
|
|
||||||
|
// return CleanFolderName(ReplaceTokens("{Artist Name}", tokenHandlers, namingConfig)); //namingConfig.ArtistFolderFormat,
|
||||||
|
//}
|
||||||
|
|
||||||
|
//public string GetAlbumFolder(Artist artist, string albumName, NamingConfig namingConfig = null)
|
||||||
|
//{
|
||||||
|
// throw new NotImplementedException();
|
||||||
|
// //if (namingConfig == null)
|
||||||
|
// //{
|
||||||
|
// // namingConfig = _namingConfigService.GetConfig();
|
||||||
|
// //}
|
||||||
|
|
||||||
|
// //var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
|
|
||||||
|
// //AddSeriesTokens(tokenHandlers, artist);
|
||||||
|
// //AddSeasonTokens(tokenHandlers, seasonNumber);
|
||||||
|
|
||||||
|
// //return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class TokenMatch
|
internal sealed class TokenMatch
|
||||||
|
|
|
@ -13,7 +13,9 @@ namespace NzbDrone.Core.Organizer
|
||||||
DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title} {Quality Full}",
|
DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title} {Quality Full}",
|
||||||
AnimeEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
|
AnimeEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
|
||||||
SeriesFolderFormat = "{Series Title}",
|
SeriesFolderFormat = "{Series Title}",
|
||||||
SeasonFolderFormat = "Season {season}"
|
SeasonFolderFormat = "Season {season}",
|
||||||
|
ArtistFolderFormat = "{Artist Name}",
|
||||||
|
AlbumFolderFormat = "{Album Name} ({Year})"
|
||||||
};
|
};
|
||||||
|
|
||||||
public bool RenameEpisodes { get; set; }
|
public bool RenameEpisodes { get; set; }
|
||||||
|
@ -24,5 +26,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
public string AnimeEpisodeFormat { get; set; }
|
public string AnimeEpisodeFormat { get; set; }
|
||||||
public string SeriesFolderFormat { get; set; }
|
public string SeriesFolderFormat { get; set; }
|
||||||
public string SeasonFolderFormat { get; set; }
|
public string SeasonFolderFormat { get; set; }
|
||||||
|
public string ArtistFolderFormat { get; set; }
|
||||||
|
public string AlbumFolderFormat { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -564,6 +564,17 @@ namespace NzbDrone.Core.Parser
|
||||||
return NormalizeRegex.Replace(title, string.Empty).ToLower().RemoveAccent();
|
return NormalizeRegex.Replace(title, string.Empty).ToLower().RemoveAccent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string CleanArtistTitle(this string title)
|
||||||
|
{
|
||||||
|
long number = 0;
|
||||||
|
|
||||||
|
//If Title only contains numbers return it as is.
|
||||||
|
if (long.TryParse(title, out number))
|
||||||
|
return title;
|
||||||
|
|
||||||
|
return NormalizeRegex.Replace(title, string.Empty).ToLower().RemoveAccent();
|
||||||
|
}
|
||||||
|
|
||||||
public static string NormalizeEpisodeTitle(string title)
|
public static string NormalizeEpisodeTitle(string title)
|
||||||
{
|
{
|
||||||
title = SpecialEpisodeWordRegex.Replace(title, string.Empty);
|
title = SpecialEpisodeWordRegex.Replace(title, string.Empty);
|
||||||
|
|
|
@ -3,5 +3,6 @@
|
||||||
public class AddSeriesOptions : MonitoringOptions
|
public class AddSeriesOptions : MonitoringOptions
|
||||||
{
|
{
|
||||||
public bool SearchForMissingEpisodes { get; set; }
|
public bool SearchForMissingEpisodes { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,8 @@ namespace NzbDrone.Core.Tv
|
||||||
{
|
{
|
||||||
public bool IgnoreEpisodesWithFiles { get; set; }
|
public bool IgnoreEpisodesWithFiles { get; set; }
|
||||||
public bool IgnoreEpisodesWithoutFiles { get; set; }
|
public bool IgnoreEpisodesWithoutFiles { get; set; }
|
||||||
|
|
||||||
|
public bool IgnoreTracksWithFiles { get; set; }
|
||||||
|
public bool IgnoreTracksWithoutFiles { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
29
src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs
Normal file
29
src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
using FluentValidation.Validators;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Validation.Paths
|
||||||
|
{
|
||||||
|
public class ArtistExistsValidator : PropertyValidator
|
||||||
|
{
|
||||||
|
private readonly IArtistService _artistService;
|
||||||
|
|
||||||
|
public ArtistExistsValidator(IArtistService artistService)
|
||||||
|
: base("This artist has already been added")
|
||||||
|
{
|
||||||
|
_artistService = artistService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsValid(PropertyValidatorContext context)
|
||||||
|
{
|
||||||
|
if (context.PropertyValue == null) return true;
|
||||||
|
|
||||||
|
var itunesId = Convert.ToInt32(context.PropertyValue.ToString());
|
||||||
|
|
||||||
|
return (!_artistService.GetAllArtists().Exists(s => s.ItunesId == itunesId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/NzbDrone.Core/Validation/Paths/ArtistPathValidator.cs
Normal file
31
src/NzbDrone.Core/Validation/Paths/ArtistPathValidator.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using FluentValidation.Validators;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Validation.Paths
|
||||||
|
{
|
||||||
|
public class ArtistPathValidator : PropertyValidator
|
||||||
|
{
|
||||||
|
private readonly IArtistService _artistService;
|
||||||
|
|
||||||
|
public ArtistPathValidator(IArtistService artistService)
|
||||||
|
: base("Path is already configured for another artist")
|
||||||
|
{
|
||||||
|
_artistService = artistService;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsValid(PropertyValidatorContext context)
|
||||||
|
{
|
||||||
|
if (context.PropertyValue == null) return true;
|
||||||
|
|
||||||
|
dynamic instance = context.ParentContext.InstanceToValidate;
|
||||||
|
var instanceId = (int)instance.Id;
|
||||||
|
|
||||||
|
return (!_artistService.GetAllArtists().Exists(s => s.Path.PathEquals(context.PropertyValue.ToString()) && s.Id != instanceId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,25 +18,29 @@ var view = Marionette.ItemView.extend({
|
||||||
template : 'AddSeries/SearchResultViewTemplate',
|
template : 'AddSeries/SearchResultViewTemplate',
|
||||||
|
|
||||||
ui : {
|
ui : {
|
||||||
profile : '.x-profile',
|
profile : '.x-profile',
|
||||||
rootFolder : '.x-root-folder',
|
rootFolder : '.x-root-folder',
|
||||||
seasonFolder : '.x-season-folder',
|
seasonFolder : '.x-season-folder',
|
||||||
seriesType : '.x-series-type',
|
seriesType : '.x-series-type',
|
||||||
monitor : '.x-monitor',
|
monitor : '.x-monitor',
|
||||||
monitorTooltip : '.x-monitor-tooltip',
|
monitorTooltip : '.x-monitor-tooltip',
|
||||||
addButton : '.x-add',
|
addButton : '.x-add',
|
||||||
addSearchButton : '.x-add-search',
|
addAlbumButton : '.x-add-album',
|
||||||
overview : '.x-overview'
|
addSearchButton : '.x-add-search',
|
||||||
|
addAlbumSearchButton : '.x-add-album-search',
|
||||||
|
overview : '.x-overview'
|
||||||
},
|
},
|
||||||
|
|
||||||
events : {
|
events : {
|
||||||
'click .x-add' : '_addWithoutSearch',
|
'click .x-add' : '_addWithoutSearch',
|
||||||
'click .x-add-search' : '_addAndSearch',
|
'click .x-add-album' : '_addWithoutSearch',
|
||||||
'change .x-profile' : '_profileChanged',
|
'click .x-add-search' : '_addAndSearch',
|
||||||
'change .x-root-folder' : '_rootFolderChanged',
|
'click .x-add-album-search' : '_addAndSearch',
|
||||||
'change .x-season-folder' : '_seasonFolderChanged',
|
'change .x-profile' : '_profileChanged',
|
||||||
'change .x-series-type' : '_seriesTypeChanged',
|
'change .x-root-folder' : '_rootFolderChanged',
|
||||||
'change .x-monitor' : '_monitorChanged'
|
'change .x-season-folder' : '_seasonFolderChanged',
|
||||||
|
'change .x-series-type' : '_seriesTypeChanged',
|
||||||
|
'change .x-monitor' : '_monitorChanged'
|
||||||
},
|
},
|
||||||
|
|
||||||
initialize : function() {
|
initialize : function() {
|
||||||
|
@ -161,7 +165,8 @@ var view = Marionette.ItemView.extend({
|
||||||
this._rootFolderChanged();
|
this._rootFolderChanged();
|
||||||
},
|
},
|
||||||
|
|
||||||
_addWithoutSearch : function() {
|
_addWithoutSearch : function(evt) {
|
||||||
|
console.log(evt);
|
||||||
this._addSeries(false);
|
this._addSeries(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -171,8 +176,8 @@ var view = Marionette.ItemView.extend({
|
||||||
|
|
||||||
_addSeries : function(searchForMissing) {
|
_addSeries : function(searchForMissing) {
|
||||||
// TODO: Refactor to handle multiple add buttons/albums
|
// TODO: Refactor to handle multiple add buttons/albums
|
||||||
var addButton = this.ui.addButton[0];
|
var addButton = this.ui.addButton;
|
||||||
var addSearchButton = this.ui.addSearchButton[0];
|
var addSearchButton = this.ui.addSearchButton;
|
||||||
console.log('_addSeries, searchForMissing=', searchForMissing);
|
console.log('_addSeries, searchForMissing=', searchForMissing);
|
||||||
|
|
||||||
addButton.addClass('disabled');
|
addButton.addClass('disabled');
|
||||||
|
@ -221,7 +226,7 @@ var view = Marionette.ItemView.extend({
|
||||||
message : 'Added: ' + self.model.get('title'),
|
message : 'Added: ' + self.model.get('title'),
|
||||||
actions : {
|
actions : {
|
||||||
goToSeries : {
|
goToSeries : {
|
||||||
label : 'Go to Series',
|
label : 'Go to Artist',
|
||||||
action : function() {
|
action : function() {
|
||||||
Backbone.history.navigate('/artist/' + self.model.get('titleSlug'), { trigger : true });
|
Backbone.history.navigate('/artist/' + self.model.get('titleSlug'), { trigger : true });
|
||||||
}
|
}
|
||||||
|
@ -246,7 +251,7 @@ var view = Marionette.ItemView.extend({
|
||||||
var lastSeason = _.max(this.model.get('seasons'), 'seasonNumber');
|
var lastSeason = _.max(this.model.get('seasons'), 'seasonNumber');
|
||||||
var firstSeason = _.min(_.reject(this.model.get('seasons'), { seasonNumber : 0 }), 'seasonNumber');
|
var firstSeason = _.min(_.reject(this.model.get('seasons'), { seasonNumber : 0 }), 'seasonNumber');
|
||||||
|
|
||||||
this.model.setSeasonPass(firstSeason.seasonNumber);
|
//this.model.setSeasonPass(firstSeason.seasonNumber); // TODO
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
ignoreEpisodesWithFiles : false,
|
ignoreEpisodesWithFiles : false,
|
||||||
|
@ -262,14 +267,14 @@ var view = Marionette.ItemView.extend({
|
||||||
options.ignoreEpisodesWithoutFiles = true;
|
options.ignoreEpisodesWithoutFiles = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (monitor === 'latest') {
|
/*else if (monitor === 'latest') {
|
||||||
this.model.setSeasonPass(lastSeason.seasonNumber);
|
this.model.setSeasonPass(lastSeason.seasonNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (monitor === 'first') {
|
else if (monitor === 'first') {
|
||||||
this.model.setSeasonPass(lastSeason.seasonNumber + 1);
|
this.model.setSeasonPass(lastSeason.seasonNumber + 1);
|
||||||
this.model.setSeasonMonitored(firstSeason.seasonNumber);
|
this.model.setSeasonMonitored(firstSeason.seasonNumber);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
else if (monitor === 'missing') {
|
else if (monitor === 'missing') {
|
||||||
options.ignoreEpisodesWithFiles = true;
|
options.ignoreEpisodesWithFiles = true;
|
||||||
|
@ -279,9 +284,9 @@ var view = Marionette.ItemView.extend({
|
||||||
options.ignoreEpisodesWithoutFiles = true;
|
options.ignoreEpisodesWithoutFiles = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (monitor === 'none') {
|
/*else if (monitor === 'none') {
|
||||||
this.model.setSeasonPass(lastSeason.seasonNumber + 1);
|
this.model.setSeasonPass(lastSeason.seasonNumber + 1);
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,11 +73,11 @@
|
||||||
<!--Uncomment if we need to add even more controls to add series-->
|
<!--Uncomment if we need to add even more controls to add series-->
|
||||||
<!--<label style="visibility: hidden">Add</label>-->
|
<!--<label style="visibility: hidden">Add</label>-->
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-success add x-add" title="Add">
|
<button class="btn btn-success add x-add" title="Add" data-artist="{{artistName}}">
|
||||||
<i class="icon-sonarr-add"></i>
|
<i class="icon-sonarr-add"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="btn btn-success add x-add-search" title="Add and Search for missing tracks">
|
<button class="btn btn-success add x-add-search" title="Add and Search for missing tracks" data-artist="{{artistName}}">
|
||||||
<i class="icon-sonarr-search"></i>
|
<i class="icon-sonarr-search"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -116,11 +116,11 @@
|
||||||
<!--Uncomment if we need to add even more controls to add series-->
|
<!--Uncomment if we need to add even more controls to add series-->
|
||||||
<!--<label style="visibility: hidden">Add</label>-->
|
<!--<label style="visibility: hidden">Add</label>-->
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-success add x-add" title="Add">
|
<button class="btn btn-success add x-add-album" title="Add" data-album="{{albumName}}">
|
||||||
<i class="icon-sonarr-add"></i>
|
<i class="icon-sonarr-add"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="btn btn-success add x-add-search" title="Add and Search for missing tracks">
|
<button class="btn btn-success add x-add-album-search" title="Add and Search for missing tracks" data-album="{{albumName}}">
|
||||||
<i class="icon-sonarr-search"></i>
|
<i class="icon-sonarr-search"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,9 +11,9 @@ module.exports = Backbone.Model.extend({
|
||||||
status : 0
|
status : 0
|
||||||
},
|
},
|
||||||
|
|
||||||
setAlbumsMonitored : function(seasonNumber) {
|
setAlbumsMonitored : function(albumName) {
|
||||||
_.each(this.get('albums'), function(album) {
|
_.each(this.get('albums'), function(album) {
|
||||||
if (season.seasonNumber === seasonNumber) {
|
if (season.albumName === albumName) {
|
||||||
album.monitored = !album.monitored;
|
album.monitored = !album.monitored;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue