mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-16 10:03:51 -07:00
Merging feature/db into feature/trackParse
This commit is contained in:
commit
e7f72a9d08
168 changed files with 5696 additions and 673 deletions
|
@ -17,8 +17,10 @@ gulp.task('less', function() {
|
||||||
paths.src.content + 'theme.less',
|
paths.src.content + 'theme.less',
|
||||||
paths.src.content + 'overrides.less',
|
paths.src.content + 'overrides.less',
|
||||||
paths.src.root + 'Series/series.less',
|
paths.src.root + 'Series/series.less',
|
||||||
|
paths.src.root + 'Artist/artist.less',
|
||||||
paths.src.root + 'Activity/activity.less',
|
paths.src.root + 'Activity/activity.less',
|
||||||
paths.src.root + 'AddSeries/addSeries.less',
|
paths.src.root + 'AddSeries/addSeries.less',
|
||||||
|
paths.src.root + 'AddArtist/addArtist.less',
|
||||||
paths.src.root + 'Calendar/calendar.less',
|
paths.src.root + 'Calendar/calendar.less',
|
||||||
paths.src.root + 'Cells/cells.less',
|
paths.src.root + 'Cells/cells.less',
|
||||||
paths.src.root + 'ManualImport/manualimport.less',
|
paths.src.root + 'ManualImport/manualimport.less',
|
||||||
|
|
|
@ -35,10 +35,13 @@ namespace NzbDrone.Api.Config
|
||||||
|
|
||||||
SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 5);
|
SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 5);
|
||||||
SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat();
|
SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat();
|
||||||
|
SharedValidator.RuleFor(c => c.StandardTrackFormat).ValidTrackFormat();
|
||||||
SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat();
|
SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat();
|
||||||
SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat();
|
SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat();
|
||||||
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
|
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
|
||||||
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
|
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
|
||||||
|
SharedValidator.RuleFor(c => c.ArtistFolderFormat).ValidArtistFolderFormat();
|
||||||
|
SharedValidator.RuleFor(c => c.AlbumFolderFormat).ValidAlbumFolderFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateNamingConfig(NamingConfigResource resource)
|
private void UpdateNamingConfig(NamingConfigResource resource)
|
||||||
|
@ -74,6 +77,7 @@ namespace NzbDrone.Api.Config
|
||||||
var sampleResource = new NamingSampleResource();
|
var sampleResource = new NamingSampleResource();
|
||||||
|
|
||||||
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
|
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
|
||||||
|
var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec);
|
||||||
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
|
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
|
||||||
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
|
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
|
||||||
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
|
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
|
||||||
|
@ -83,6 +87,10 @@ namespace NzbDrone.Api.Config
|
||||||
? "Invalid format"
|
? "Invalid format"
|
||||||
: singleEpisodeSampleResult.FileName;
|
: singleEpisodeSampleResult.FileName;
|
||||||
|
|
||||||
|
sampleResource.SingleTrackExample = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult) != null
|
||||||
|
? "Invalid format"
|
||||||
|
: singleTrackSampleResult.FileName;
|
||||||
|
|
||||||
sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null
|
sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null
|
||||||
? "Invalid format"
|
? "Invalid format"
|
||||||
: multiEpisodeSampleResult.FileName;
|
: multiEpisodeSampleResult.FileName;
|
||||||
|
@ -107,18 +115,28 @@ namespace NzbDrone.Api.Config
|
||||||
? "Invalid format"
|
? "Invalid format"
|
||||||
: _filenameSampleService.GetSeasonFolderSample(nameSpec);
|
: _filenameSampleService.GetSeasonFolderSample(nameSpec);
|
||||||
|
|
||||||
|
sampleResource.ArtistFolderExample = nameSpec.ArtistFolderFormat.IsNullOrWhiteSpace()
|
||||||
|
? "Invalid format"
|
||||||
|
: _filenameSampleService.GetArtistFolderSample(nameSpec);
|
||||||
|
|
||||||
|
sampleResource.AlbumFolderExample = nameSpec.AlbumFolderFormat.IsNullOrWhiteSpace()
|
||||||
|
? "Invalid format"
|
||||||
|
: _filenameSampleService.GetAlbumFolderSample(nameSpec);
|
||||||
|
|
||||||
return sampleResource.AsResponse();
|
return sampleResource.AsResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateFormatResult(NamingConfig nameSpec)
|
private void ValidateFormatResult(NamingConfig nameSpec)
|
||||||
{
|
{
|
||||||
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
|
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
|
||||||
|
var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec);
|
||||||
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
|
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
|
||||||
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
|
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
|
||||||
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
|
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
|
||||||
var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec);
|
var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec);
|
||||||
|
|
||||||
var singleEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult);
|
var singleEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult);
|
||||||
|
var singleTrackValidationResult = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult);
|
||||||
var multiEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult);
|
var multiEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult);
|
||||||
var dailyEpisodeValidationResult = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult);
|
var dailyEpisodeValidationResult = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult);
|
||||||
var animeEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult);
|
var animeEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult);
|
||||||
|
@ -127,6 +145,7 @@ namespace NzbDrone.Api.Config
|
||||||
var validationFailures = new List<ValidationFailure>();
|
var validationFailures = new List<ValidationFailure>();
|
||||||
|
|
||||||
validationFailures.AddIfNotNull(singleEpisodeValidationResult);
|
validationFailures.AddIfNotNull(singleEpisodeValidationResult);
|
||||||
|
validationFailures.AddIfNotNull(singleTrackValidationResult);
|
||||||
validationFailures.AddIfNotNull(multiEpisodeValidationResult);
|
validationFailures.AddIfNotNull(multiEpisodeValidationResult);
|
||||||
validationFailures.AddIfNotNull(dailyEpisodeValidationResult);
|
validationFailures.AddIfNotNull(dailyEpisodeValidationResult);
|
||||||
validationFailures.AddIfNotNull(animeEpisodeValidationResult);
|
validationFailures.AddIfNotNull(animeEpisodeValidationResult);
|
||||||
|
|
|
@ -6,13 +6,17 @@ namespace NzbDrone.Api.Config
|
||||||
public class NamingConfigResource : RestResource
|
public class NamingConfigResource : RestResource
|
||||||
{
|
{
|
||||||
public bool RenameEpisodes { get; set; }
|
public bool RenameEpisodes { get; set; }
|
||||||
|
public bool RenameTracks { get; set; }
|
||||||
public bool ReplaceIllegalCharacters { get; set; }
|
public bool ReplaceIllegalCharacters { get; set; }
|
||||||
public int MultiEpisodeStyle { get; set; }
|
public int MultiEpisodeStyle { get; set; }
|
||||||
public string StandardEpisodeFormat { get; set; }
|
public string StandardEpisodeFormat { get; set; }
|
||||||
|
public string StandardTrackFormat { get; set; }
|
||||||
public string DailyEpisodeFormat { get; set; }
|
public string DailyEpisodeFormat { get; set; }
|
||||||
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; }
|
||||||
public bool IncludeSeriesTitle { get; set; }
|
public bool IncludeSeriesTitle { get; set; }
|
||||||
public bool IncludeEpisodeTitle { get; set; }
|
public bool IncludeEpisodeTitle { get; set; }
|
||||||
public bool IncludeQuality { get; set; }
|
public bool IncludeQuality { get; set; }
|
||||||
|
@ -30,13 +34,17 @@ namespace NzbDrone.Api.Config
|
||||||
Id = model.Id,
|
Id = model.Id,
|
||||||
|
|
||||||
RenameEpisodes = model.RenameEpisodes,
|
RenameEpisodes = model.RenameEpisodes,
|
||||||
|
RenameTracks = model.RenameTracks,
|
||||||
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
|
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
|
||||||
MultiEpisodeStyle = model.MultiEpisodeStyle,
|
MultiEpisodeStyle = model.MultiEpisodeStyle,
|
||||||
StandardEpisodeFormat = model.StandardEpisodeFormat,
|
StandardEpisodeFormat = model.StandardEpisodeFormat,
|
||||||
|
StandardTrackFormat = model.StandardTrackFormat,
|
||||||
DailyEpisodeFormat = model.DailyEpisodeFormat,
|
DailyEpisodeFormat = model.DailyEpisodeFormat,
|
||||||
AnimeEpisodeFormat = model.AnimeEpisodeFormat,
|
AnimeEpisodeFormat = model.AnimeEpisodeFormat,
|
||||||
SeriesFolderFormat = model.SeriesFolderFormat,
|
SeriesFolderFormat = model.SeriesFolderFormat,
|
||||||
SeasonFolderFormat = model.SeasonFolderFormat
|
SeasonFolderFormat = model.SeasonFolderFormat,
|
||||||
|
ArtistFolderFormat = model.ArtistFolderFormat,
|
||||||
|
AlbumFolderFormat = model.AlbumFolderFormat
|
||||||
//IncludeSeriesTitle
|
//IncludeSeriesTitle
|
||||||
//IncludeEpisodeTitle
|
//IncludeEpisodeTitle
|
||||||
//IncludeQuality
|
//IncludeQuality
|
||||||
|
@ -63,13 +71,17 @@ namespace NzbDrone.Api.Config
|
||||||
Id = resource.Id,
|
Id = resource.Id,
|
||||||
|
|
||||||
RenameEpisodes = resource.RenameEpisodes,
|
RenameEpisodes = resource.RenameEpisodes,
|
||||||
|
RenameTracks = resource.RenameTracks,
|
||||||
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
|
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
|
||||||
MultiEpisodeStyle = resource.MultiEpisodeStyle,
|
MultiEpisodeStyle = resource.MultiEpisodeStyle,
|
||||||
StandardEpisodeFormat = resource.StandardEpisodeFormat,
|
StandardEpisodeFormat = resource.StandardEpisodeFormat,
|
||||||
|
StandardTrackFormat = resource.StandardTrackFormat,
|
||||||
DailyEpisodeFormat = resource.DailyEpisodeFormat,
|
DailyEpisodeFormat = resource.DailyEpisodeFormat,
|
||||||
AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
|
AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
|
||||||
SeriesFolderFormat = resource.SeriesFolderFormat,
|
SeriesFolderFormat = resource.SeriesFolderFormat,
|
||||||
SeasonFolderFormat = resource.SeasonFolderFormat
|
SeasonFolderFormat = resource.SeasonFolderFormat,
|
||||||
|
ArtistFolderFormat = resource.ArtistFolderFormat,
|
||||||
|
AlbumFolderFormat = resource.AlbumFolderFormat
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,14 @@
|
||||||
public class NamingSampleResource
|
public class NamingSampleResource
|
||||||
{
|
{
|
||||||
public string SingleEpisodeExample { get; set; }
|
public string SingleEpisodeExample { get; set; }
|
||||||
|
public string SingleTrackExample { get; set; }
|
||||||
public string MultiEpisodeExample { get; set; }
|
public string MultiEpisodeExample { get; set; }
|
||||||
public string DailyEpisodeExample { get; set; }
|
public string DailyEpisodeExample { get; set; }
|
||||||
public string AnimeEpisodeExample { get; set; }
|
public string AnimeEpisodeExample { get; set; }
|
||||||
public string AnimeMultiEpisodeExample { get; set; }
|
public string AnimeMultiEpisodeExample { get; set; }
|
||||||
public string SeriesFolderExample { get; set; }
|
public string SeriesFolderExample { get; set; }
|
||||||
public string SeasonFolderExample { get; set; }
|
public string SeasonFolderExample { get; set; }
|
||||||
|
public string ArtistFolderExample { get; set; }
|
||||||
|
public string AlbumFolderExample { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,7 +11,7 @@ namespace NzbDrone.Api.Music
|
||||||
public string AlbumId { get; set; }
|
public string AlbumId { get; set; }
|
||||||
public string AlbumName { get; set; }
|
public string AlbumName { get; set; }
|
||||||
public bool Monitored { get; set; }
|
public bool Monitored { get; set; }
|
||||||
public int Year { get; set; }
|
public DateTime ReleaseDate { get; set; }
|
||||||
public List<string> Genres { get; set; }
|
public List<string> Genres { get; set; }
|
||||||
public string ArtworkUrl { get; set; }
|
public string ArtworkUrl { get; set; }
|
||||||
|
|
||||||
|
@ -25,12 +25,12 @@ namespace NzbDrone.Api.Music
|
||||||
|
|
||||||
return new AlbumResource
|
return new AlbumResource
|
||||||
{
|
{
|
||||||
AlbumId = model.AlbumId,
|
AlbumId = model.ForeignAlbumId,
|
||||||
Monitored = model.Monitored,
|
Monitored = model.Monitored,
|
||||||
Year = model.Year,
|
ReleaseDate = model.ReleaseDate,
|
||||||
AlbumName = model.Title,
|
AlbumName = model.Title,
|
||||||
Genres = model.Genres,
|
Genres = model.Genres,
|
||||||
ArtworkUrl = model.ArtworkUrl
|
//ArtworkUrl = model.ArtworkUrl
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,12 +40,12 @@ namespace NzbDrone.Api.Music
|
||||||
|
|
||||||
return new Album
|
return new Album
|
||||||
{
|
{
|
||||||
AlbumId = resource.AlbumId,
|
ForeignAlbumId = resource.AlbumId,
|
||||||
Monitored = resource.Monitored,
|
Monitored = resource.Monitored,
|
||||||
Year = resource.Year,
|
ReleaseDate = resource.ReleaseDate,
|
||||||
Title = resource.AlbumName,
|
Title = resource.AlbumName,
|
||||||
Genres = resource.Genres,
|
Genres = resource.Genres,
|
||||||
ArtworkUrl = resource.ArtworkUrl
|
//ArtworkUrl = resource.ArtworkUrl
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ namespace NzbDrone.Api.Music
|
||||||
|
|
||||||
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
|
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.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace());
|
||||||
PostValidator.RuleFor(s => s.SpotifyId).NotEqual("").SetValidator(artistExistsValidator);
|
PostValidator.RuleFor(s => s.ForeignArtistId).NotEqual("").SetValidator(artistExistsValidator);
|
||||||
|
|
||||||
PutValidator.RuleFor(s => s.Path).IsValidPath();
|
PutValidator.RuleFor(s => s.Path).IsValidPath();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ namespace NzbDrone.Api.Music
|
||||||
|
|
||||||
|
|
||||||
//View Only
|
//View Only
|
||||||
public string ArtistName { get; set; }
|
public string Name { get; set; }
|
||||||
public string SpotifyId { get; set; }
|
public string ForeignArtistId { get; set; }
|
||||||
public string Overview { get; set; }
|
public string Overview { get; set; }
|
||||||
|
|
||||||
public int AlbumCount
|
public int AlbumCount
|
||||||
|
@ -59,7 +59,7 @@ namespace NzbDrone.Api.Music
|
||||||
public DateTime Added { get; set; }
|
public DateTime Added { get; set; }
|
||||||
public AddSeriesOptions AddOptions { get; set; }
|
public AddSeriesOptions AddOptions { get; set; }
|
||||||
public Ratings Ratings { get; set; }
|
public Ratings Ratings { get; set; }
|
||||||
public string ArtistSlug { get; internal set; }
|
public string NameSlug { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ArtistResourceMapper
|
public static class ArtistResourceMapper
|
||||||
|
@ -72,7 +72,7 @@ namespace NzbDrone.Api.Music
|
||||||
{
|
{
|
||||||
Id = model.Id,
|
Id = model.Id,
|
||||||
|
|
||||||
ArtistName = model.ArtistName,
|
Name = model.Name,
|
||||||
//AlternateTitles
|
//AlternateTitles
|
||||||
//SortTitle = resource.SortTitle,
|
//SortTitle = resource.SortTitle,
|
||||||
|
|
||||||
|
@ -94,7 +94,6 @@ namespace NzbDrone.Api.Music
|
||||||
Path = model.Path,
|
Path = model.Path,
|
||||||
ProfileId = model.ProfileId,
|
ProfileId = model.ProfileId,
|
||||||
|
|
||||||
ArtistFolder = model.ArtistFolder,
|
|
||||||
Monitored = model.Monitored,
|
Monitored = model.Monitored,
|
||||||
|
|
||||||
//UseSceneNumbering = resource.UseSceneNumbering,
|
//UseSceneNumbering = resource.UseSceneNumbering,
|
||||||
|
@ -105,8 +104,8 @@ namespace NzbDrone.Api.Music
|
||||||
//FirstAired = resource.FirstAired,
|
//FirstAired = resource.FirstAired,
|
||||||
//LastInfoSync = resource.LastInfoSync,
|
//LastInfoSync = resource.LastInfoSync,
|
||||||
//SeriesType = resource.SeriesType,
|
//SeriesType = resource.SeriesType,
|
||||||
SpotifyId = model.SpotifyId,
|
ForeignArtistId = model.ForeignArtistId,
|
||||||
ArtistSlug = model.ArtistSlug,
|
NameSlug = model.NameSlug,
|
||||||
|
|
||||||
RootFolderPath = model.RootFolderPath,
|
RootFolderPath = model.RootFolderPath,
|
||||||
Genres = model.Genres,
|
Genres = model.Genres,
|
||||||
|
@ -125,7 +124,7 @@ namespace NzbDrone.Api.Music
|
||||||
{
|
{
|
||||||
Id = resource.Id,
|
Id = resource.Id,
|
||||||
|
|
||||||
ArtistName = resource.ArtistName,
|
Name = resource.Name,
|
||||||
//AlternateTitles
|
//AlternateTitles
|
||||||
//SortTitle = resource.SortTitle,
|
//SortTitle = resource.SortTitle,
|
||||||
|
|
||||||
|
@ -147,11 +146,10 @@ namespace NzbDrone.Api.Music
|
||||||
Path = resource.Path,
|
Path = resource.Path,
|
||||||
ProfileId = resource.ProfileId,
|
ProfileId = resource.ProfileId,
|
||||||
|
|
||||||
ArtistFolder = resource.ArtistFolder,
|
|
||||||
Monitored = resource.Monitored,
|
Monitored = resource.Monitored,
|
||||||
//LastInfoSync = resource.LastInfoSync,
|
//LastInfoSync = resource.LastInfoSync,
|
||||||
SpotifyId = resource.SpotifyId,
|
ForeignArtistId = resource.ForeignArtistId,
|
||||||
ArtistSlug = resource.ArtistSlug,
|
NameSlug = resource.NameSlug,
|
||||||
|
|
||||||
RootFolderPath = resource.RootFolderPath,
|
RootFolderPath = resource.RootFolderPath,
|
||||||
Genres = resource.Genres,
|
Genres = resource.Genres,
|
||||||
|
|
|
@ -17,8 +17,10 @@ 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://api.spotify.com/{version}/{route}/") // TODO: maybe use {version}
|
//Search = new HttpRequestBuilder("https://api.spotify.com/{version}/{route}/") // TODO: maybe use {version}
|
||||||
.SetSegment("version", "v1")
|
// .SetSegment("version", "v1")
|
||||||
|
// .CreateFactory();
|
||||||
|
Search = new HttpRequestBuilder("http://localhost:5000/{route}/") // TODO: maybe use {version}
|
||||||
.CreateFactory();
|
.CreateFactory();
|
||||||
|
|
||||||
InternalSearch = new HttpRequestBuilder("https://itunes.apple.com/WebObjects/MZStore.woa/wa/{route}") //viewArtist or search
|
InternalSearch = new HttpRequestBuilder("https://itunes.apple.com/WebObjects/MZStore.woa/wa/{route}") //viewArtist or search
|
||||||
|
|
|
@ -12,64 +12,94 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
{
|
{
|
||||||
protected override void MainDbUpgrade()
|
protected override void MainDbUpgrade()
|
||||||
{
|
{
|
||||||
Create.TableForModel("Artist")
|
Create.TableForModel("Artists")
|
||||||
.WithColumn("SpotifyId").AsString().Nullable().Unique()
|
.WithColumn("ForeignArtistId").AsString().Unique()
|
||||||
.WithColumn("ArtistName").AsString().Unique()
|
.WithColumn("MBId").AsString().Nullable()
|
||||||
.WithColumn("ArtistSlug").AsString().Nullable() //.Unique()
|
.WithColumn("AMId").AsString().Nullable()
|
||||||
.WithColumn("CleanTitle").AsString().Nullable() // Do we need this?
|
.WithColumn("TADBId").AsInt32().Nullable()
|
||||||
.WithColumn("Monitored").AsBoolean()
|
.WithColumn("DiscogsId").AsInt32().Nullable()
|
||||||
|
.WithColumn("Name").AsString()
|
||||||
|
.WithColumn("NameSlug").AsString().Nullable().Unique()
|
||||||
|
.WithColumn("CleanName").AsString().Indexed()
|
||||||
|
.WithColumn("Status").AsInt32()
|
||||||
.WithColumn("Overview").AsString().Nullable()
|
.WithColumn("Overview").AsString().Nullable()
|
||||||
.WithColumn("AlbumFolder").AsBoolean().Nullable()
|
.WithColumn("Images").AsString()
|
||||||
.WithColumn("ArtistFolder").AsBoolean().Nullable()
|
.WithColumn("Path").AsString().Indexed()
|
||||||
|
.WithColumn("Monitored").AsBoolean()
|
||||||
|
.WithColumn("AlbumFolder").AsBoolean()
|
||||||
.WithColumn("LastInfoSync").AsDateTime().Nullable()
|
.WithColumn("LastInfoSync").AsDateTime().Nullable()
|
||||||
.WithColumn("LastDiskSync").AsDateTime().Nullable()
|
.WithColumn("LastDiskSync").AsDateTime().Nullable()
|
||||||
.WithColumn("Status").AsInt32().Nullable()
|
.WithColumn("DateFormed").AsDateTime().Nullable()
|
||||||
.WithColumn("Path").AsString()
|
.WithColumn("Members").AsString().Nullable()
|
||||||
.WithColumn("Images").AsString().Nullable()
|
.WithColumn("Ratings").AsString().Nullable()
|
||||||
.WithColumn("QualityProfileId").AsInt32().Nullable()
|
|
||||||
.WithColumn("RootFolderPath").AsString().Nullable()
|
|
||||||
.WithColumn("Added").AsDateTime().Nullable()
|
|
||||||
.WithColumn("ProfileId").AsInt32().Nullable() // This is either ProfileId or Profile
|
|
||||||
.WithColumn("Genres").AsString().Nullable()
|
.WithColumn("Genres").AsString().Nullable()
|
||||||
.WithColumn("Albums").AsString().Nullable()
|
.WithColumn("SortName").AsString().Nullable()
|
||||||
|
.WithColumn("ProfileId").AsInt32().Nullable()
|
||||||
.WithColumn("Tags").AsString().Nullable()
|
.WithColumn("Tags").AsString().Nullable()
|
||||||
.WithColumn("AddOptions").AsString().Nullable()
|
.WithColumn("Added").AsDateTime().Nullable()
|
||||||
;
|
.WithColumn("AddOptions").AsString().Nullable();
|
||||||
|
|
||||||
Create.TableForModel("Albums")
|
Create.TableForModel("Albums")
|
||||||
.WithColumn("AlbumId").AsString().Unique()
|
.WithColumn("ForeignAlbumId").AsString().Unique()
|
||||||
.WithColumn("ArtistId").AsInt32() // Should this be artistId (string)
|
.WithColumn("ArtistId").AsInt32()
|
||||||
|
.WithColumn("MBId").AsString().Nullable().Indexed()
|
||||||
|
.WithColumn("AMId").AsString().Nullable()
|
||||||
|
.WithColumn("TADBId").AsInt32().Nullable().Indexed()
|
||||||
|
.WithColumn("DiscogsId").AsInt32().Nullable()
|
||||||
.WithColumn("Title").AsString()
|
.WithColumn("Title").AsString()
|
||||||
.WithColumn("Year").AsInt32()
|
.WithColumn("TitleSlug").AsString().Nullable().Unique()
|
||||||
.WithColumn("Image").AsInt32()
|
.WithColumn("CleanTitle").AsString().Indexed()
|
||||||
.WithColumn("TrackCount").AsInt32()
|
.WithColumn("Overview").AsString().Nullable()
|
||||||
.WithColumn("DiscCount").AsInt32()
|
.WithColumn("Images").AsString()
|
||||||
|
.WithColumn("Path").AsString().Indexed()
|
||||||
.WithColumn("Monitored").AsBoolean()
|
.WithColumn("Monitored").AsBoolean()
|
||||||
.WithColumn("Overview").AsString();
|
.WithColumn("LastInfoSync").AsDateTime().Nullable()
|
||||||
|
.WithColumn("LastDiskSync").AsDateTime().Nullable()
|
||||||
|
.WithColumn("ReleaseDate").AsDateTime().Nullable()
|
||||||
|
.WithColumn("Ratings").AsString().Nullable()
|
||||||
|
.WithColumn("Genres").AsString().Nullable()
|
||||||
|
.WithColumn("Label").AsString().Nullable()
|
||||||
|
.WithColumn("SortTitle").AsString().Nullable()
|
||||||
|
.WithColumn("ProfileId").AsInt32().Nullable()
|
||||||
|
.WithColumn("Tags").AsString().Nullable()
|
||||||
|
.WithColumn("Added").AsDateTime().Nullable()
|
||||||
|
.WithColumn("AlbumType").AsString()
|
||||||
|
.WithColumn("AddOptions").AsString().Nullable();
|
||||||
|
|
||||||
Create.TableForModel("Tracks")
|
Create.TableForModel("Tracks")
|
||||||
.WithColumn("SpotifyTrackId").AsString().Nullable() // This shouldn't be nullable, but TrackRepository won't behave. Someone please fix this.
|
.WithColumn("ForeignTrackId").AsString().Unique()
|
||||||
.WithColumn("AlbumId").AsString()
|
.WithColumn("ArtistId").AsInt32().Indexed()
|
||||||
.WithColumn("ArtistId").AsString() // This may be a list of Ids in future for compilations
|
.WithColumn("AlbumId").AsInt32()
|
||||||
.WithColumn("ArtistSpotifyId").AsString()
|
.WithColumn("MBId").AsString().Nullable().Indexed()
|
||||||
.WithColumn("Compilation").AsBoolean()
|
|
||||||
.WithColumn("TrackNumber").AsInt32()
|
.WithColumn("TrackNumber").AsInt32()
|
||||||
.WithColumn("Title").AsString().Nullable()
|
.WithColumn("Title").AsString().Nullable()
|
||||||
.WithColumn("Ignored").AsBoolean().Nullable()
|
.WithColumn("Explicit").AsBoolean()
|
||||||
.WithColumn("Explict").AsBoolean()
|
.WithColumn("Compilation").AsBoolean()
|
||||||
|
.WithColumn("DiscNumber").AsInt32().Nullable()
|
||||||
|
.WithColumn("TrackFileId").AsInt32().Nullable().Indexed()
|
||||||
.WithColumn("Monitored").AsBoolean()
|
.WithColumn("Monitored").AsBoolean()
|
||||||
.WithColumn("TrackFileId").AsInt32().Nullable()
|
.WithColumn("Ratings").AsString().Nullable();
|
||||||
.WithColumn("ReleaseDate").AsDateTime().Nullable();
|
|
||||||
|
|
||||||
|
Create.Index().OnTable("Tracks").OnColumn("ArtistId").Ascending()
|
||||||
|
.OnColumn("AlbumId").Ascending()
|
||||||
|
.OnColumn("TrackNumber").Ascending();
|
||||||
|
|
||||||
Create.TableForModel("TrackFiles")
|
Create.TableForModel("TrackFiles")
|
||||||
.WithColumn("ArtistId").AsInt32()
|
.WithColumn("ArtistId").AsInt32().Indexed()
|
||||||
.WithColumn("Path").AsString().Unique()
|
.WithColumn("AlbumId").AsInt32().Indexed()
|
||||||
.WithColumn("Quality").AsString()
|
.WithColumn("Quality").AsString()
|
||||||
.WithColumn("Size").AsInt64()
|
.WithColumn("Size").AsInt64()
|
||||||
.WithColumn("DateAdded").AsDateTime()
|
.WithColumn("DateAdded").AsDateTime()
|
||||||
.WithColumn("AlbumId").AsInt32(); // How does this impact stand alone tracks?
|
.WithColumn("SceneName").AsString().Nullable()
|
||||||
|
.WithColumn("ReleaseGroup").AsString().Nullable()
|
||||||
|
.WithColumn("MediaInfo").AsString().Nullable()
|
||||||
|
.WithColumn("RelativePath").AsString().Nullable();
|
||||||
|
|
||||||
|
Alter.Table("NamingConfig")
|
||||||
|
.AddColumn("ArtistFolderFormat").AsString().Nullable()
|
||||||
|
.AddColumn("RenameTracks").AsBoolean().Nullable()
|
||||||
|
.AddColumn("StandardTrackFormat").AsString().Nullable()
|
||||||
|
.AddColumn("AlbumFolderFormat").AsString().Nullable();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
using FluentMigrator;
|
|
||||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore.Migration
|
|
||||||
{
|
|
||||||
[Migration(112)]
|
|
||||||
public class add_music_fields_to_namingconfig : NzbDroneMigrationBase
|
|
||||||
{
|
|
||||||
protected override void MainDbUpgrade()
|
|
||||||
{
|
|
||||||
Alter.Table("NamingConfig").AddColumn("ArtistFolderFormat").AsAnsiString().Nullable();
|
|
||||||
Alter.Table("NamingConfig").AddColumn("AlbumFolderFormat").AsAnsiString().Nullable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -92,11 +92,13 @@ namespace NzbDrone.Core.Datastore
|
||||||
.Relationship()
|
.Relationship()
|
||||||
.HasOne(episode => episode.EpisodeFile, episode => episode.EpisodeFileId);
|
.HasOne(episode => episode.EpisodeFile, episode => episode.EpisodeFileId);
|
||||||
|
|
||||||
Mapper.Entity<Artist>().RegisterModel("Artist")
|
Mapper.Entity<Artist>().RegisterModel("Artists")
|
||||||
.Ignore(s => s.RootFolderPath)
|
.Ignore(s => s.RootFolderPath)
|
||||||
.Relationship()
|
.Relationship()
|
||||||
.HasOne(a => a.Profile, a => a.ProfileId);
|
.HasOne(a => a.Profile, a => a.ProfileId);
|
||||||
|
|
||||||
|
Mapper.Entity<Album>().RegisterModel("Albums");
|
||||||
|
|
||||||
Mapper.Entity<TrackFile>().RegisterModel("TrackFiles")
|
Mapper.Entity<TrackFile>().RegisterModel("TrackFiles")
|
||||||
.Ignore(f => f.Path)
|
.Ignore(f => f.Path)
|
||||||
.Relationships.AutoMapICollectionOrComplexProperties()
|
.Relationships.AutoMapICollectionOrComplexProperties()
|
||||||
|
|
17
src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs
Normal file
17
src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.Parser.Model;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Download
|
||||||
|
{
|
||||||
|
public class AlbumGrabbedEvent : IEvent
|
||||||
|
{
|
||||||
|
public RemoteAlbum Album { get; private set; }
|
||||||
|
public string DownloadClient { get; set; }
|
||||||
|
public string DownloadId { get; set; }
|
||||||
|
|
||||||
|
public AlbumGrabbedEvent(RemoteAlbum album)
|
||||||
|
{
|
||||||
|
Album = album;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
Port = 8112;
|
Port = 8112;
|
||||||
Password = "deluge";
|
Password = "deluge";
|
||||||
TvCategory = "tv-Lidarr";
|
TvCategory = "lidarr";
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
||||||
|
@ -43,10 +43,10 @@ namespace NzbDrone.Core.Download.Clients.Deluge
|
||||||
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
|
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||||
public string TvCategory { get; set; }
|
public string TvCategory { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
|
||||||
public int RecentTvPriority { get; set; }
|
public int RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
|
||||||
public int OlderTvPriority { get; set; }
|
public int OlderTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)]
|
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)]
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||||
{
|
{
|
||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
Port = 4321;
|
Port = 4321;
|
||||||
TvCategory = "TV Shows";
|
TvCategory = "Music";
|
||||||
RecentTvPriority = (int)NzbVortexPriority.Normal;
|
RecentTvPriority = (int)NzbVortexPriority.Normal;
|
||||||
OlderTvPriority = (int)NzbVortexPriority.Normal;
|
OlderTvPriority = (int)NzbVortexPriority.Normal;
|
||||||
}
|
}
|
||||||
|
@ -46,10 +46,10 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
|
||||||
[FieldDefinition(3, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
|
[FieldDefinition(3, Label = "Group", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||||
public string TvCategory { get; set; }
|
public string TvCategory { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(4, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
[FieldDefinition(4, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
|
||||||
public int RecentTvPriority { get; set; }
|
public int RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(5, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(5, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbVortexPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
|
||||||
public int OlderTvPriority { get; set; }
|
public int OlderTvPriority { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
|
|
|
@ -26,7 +26,9 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
{
|
{
|
||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
Port = 6789;
|
Port = 6789;
|
||||||
TvCategory = "tv";
|
TvCategory = "Music";
|
||||||
|
Username = "nzbget";
|
||||||
|
Password = "tegbzn6789";
|
||||||
RecentTvPriority = (int)NzbgetPriority.Normal;
|
RecentTvPriority = (int)NzbgetPriority.Normal;
|
||||||
OlderTvPriority = (int)NzbgetPriority.Normal;
|
OlderTvPriority = (int)NzbgetPriority.Normal;
|
||||||
}
|
}
|
||||||
|
@ -46,10 +48,10 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
|
||||||
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
|
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||||
public string TvCategory { get; set; }
|
public string TvCategory { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
|
||||||
public int RecentTvPriority { get; set; }
|
public int RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(NzbgetPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
|
||||||
public int OlderTvPriority { get; set; }
|
public int OlderTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)]
|
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox)]
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||||
{
|
{
|
||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
Port = 9091;
|
Port = 9091;
|
||||||
TvCategory = "tv-Lidarr";
|
TvCategory = "lidarr";
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
||||||
|
@ -40,10 +40,10 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
|
||||||
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
|
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||||
public string TvCategory { get; set; }
|
public string TvCategory { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
|
||||||
public int RecentTvPriority { get; set; }
|
public int RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(QBittorrentPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
|
||||||
public int OlderTvPriority { get; set; }
|
public int OlderTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use a secure connection. See Options -> Web UI -> 'Use HTTPS instead of HTTP' in qBittorrent.")]
|
[FieldDefinition(7, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use a secure connection. See Options -> Web UI -> 'Use HTTPS instead of HTTP' in qBittorrent.")]
|
||||||
|
|
|
@ -38,7 +38,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
{
|
{
|
||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
Port = 8080;
|
Port = 8080;
|
||||||
TvCategory = "tv";
|
TvCategory = "music";
|
||||||
RecentTvPriority = (int)SabnzbdPriority.Default;
|
RecentTvPriority = (int)SabnzbdPriority.Default;
|
||||||
OlderTvPriority = (int)SabnzbdPriority.Default;
|
OlderTvPriority = (int)SabnzbdPriority.Default;
|
||||||
}
|
}
|
||||||
|
@ -61,10 +61,10 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
|
||||||
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
|
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||||
public string TvCategory { get; set; }
|
public string TvCategory { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
[FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
|
||||||
public int RecentTvPriority { get; set; }
|
public int RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(7, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(SabnzbdPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
|
||||||
public int OlderTvPriority { get; set; }
|
public int OlderTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(8, Label = "Use SSL", Type = FieldType.Checkbox)]
|
[FieldDefinition(8, Label = "Use SSL", Type = FieldType.Checkbox)]
|
||||||
|
|
|
@ -56,10 +56,10 @@ namespace NzbDrone.Core.Download.Clients.Transmission
|
||||||
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")]
|
[FieldDefinition(6, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default Transmission location")]
|
||||||
public string TvDirectory { get; set; }
|
public string TvDirectory { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
[FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
|
||||||
public int RecentTvPriority { get; set; }
|
public int RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
|
||||||
public int OlderTvPriority { get; set; }
|
public int OlderTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(9, Label = "Use SSL", Type = FieldType.Checkbox)]
|
[FieldDefinition(9, Label = "Use SSL", Type = FieldType.Checkbox)]
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
Port = 8080;
|
Port = 8080;
|
||||||
UrlBase = "RPC2";
|
UrlBase = "RPC2";
|
||||||
TvCategory = "tv-Lidarr";
|
TvCategory = "lidarr";
|
||||||
OlderTvPriority = (int)RTorrentPriority.Normal;
|
OlderTvPriority = (int)RTorrentPriority.Normal;
|
||||||
RecentTvPriority = (int)RTorrentPriority.Normal;
|
RecentTvPriority = (int)RTorrentPriority.Normal;
|
||||||
}
|
}
|
||||||
|
@ -55,10 +55,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||||
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default rTorrent location")]
|
[FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default rTorrent location")]
|
||||||
public string TvDirectory { get; set; }
|
public string TvDirectory { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
[FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
|
||||||
public int RecentTvPriority { get; set; }
|
public int RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
|
||||||
public int OlderTvPriority { get; set; }
|
public int OlderTvPriority { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||||
{
|
{
|
||||||
Host = "localhost";
|
Host = "localhost";
|
||||||
Port = 9091;
|
Port = 9091;
|
||||||
TvCategory = "tv-Lidarr";
|
TvCategory = "lidarr";
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
[FieldDefinition(0, Label = "Host", Type = FieldType.Textbox)]
|
||||||
|
@ -41,10 +41,10 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
|
||||||
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
|
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Lidarr avoids conflicts with unrelated downloads, but it's optional")]
|
||||||
public string TvCategory { get; set; }
|
public string TvCategory { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]
|
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing albums released within the last 14 days")]
|
||||||
public int RecentTvPriority { get; set; }
|
public int RecentTvPriority { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")]
|
[FieldDefinition(6, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing albums released over 14 days ago")]
|
||||||
public int OlderTvPriority { get; set; }
|
public int OlderTvPriority { get; set; }
|
||||||
|
|
||||||
public NzbDroneValidationResult Validate()
|
public NzbDroneValidationResult Validate()
|
||||||
|
|
|
@ -154,7 +154,7 @@ namespace NzbDrone.Core.Download
|
||||||
|
|
||||||
torrentFile = response.ResponseData;
|
torrentFile = response.ResponseData;
|
||||||
|
|
||||||
_logger.Debug("Downloading torrent for episode '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, torrentFile.Length, torrentUrl);
|
_logger.Debug("Downloading torrent for release '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, torrentFile.Length, torrentUrl);
|
||||||
}
|
}
|
||||||
catch (HttpException ex)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
|
@ -164,14 +164,14 @@ namespace NzbDrone.Core.Download
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Downloading torrent file for episode '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl);
|
_logger.Error(ex, "Downloading torrent file for release '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex);
|
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex);
|
||||||
}
|
}
|
||||||
catch (WebException ex)
|
catch (WebException ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Downloading torrent file for episode '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl);
|
_logger.Error(ex, "Downloading torrent file for release '{0}' failed ({1})", remoteEpisode.Release.Title, torrentUrl);
|
||||||
|
|
||||||
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex);
|
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed", ex);
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ namespace NzbDrone.Core.Download
|
||||||
}
|
}
|
||||||
catch (FormatException ex)
|
catch (FormatException ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Failed to parse magnetlink for episode '{0}': '{1}'", remoteEpisode.Release.Title, magnetUrl);
|
_logger.Error(ex, "Failed to parse magnetlink for release '{0}': '{1}'", remoteEpisode.Release.Title, magnetUrl);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
nzbData = _httpClient.Get(new HttpRequest(url)).ResponseData;
|
nzbData = _httpClient.Get(new HttpRequest(url)).ResponseData;
|
||||||
|
|
||||||
_logger.Debug("Downloaded nzb for episode '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, nzbData.Length, url);
|
_logger.Debug("Downloaded nzb for release '{0}' finished ({1} bytes from {2})", remoteEpisode.Release.Title, nzbData.Length, url);
|
||||||
}
|
}
|
||||||
catch (HttpException ex)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
|
@ -52,14 +52,14 @@ namespace NzbDrone.Core.Download
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Downloading nzb for episode '{0}' failed ({1})", remoteEpisode.Release.Title, url);
|
_logger.Error(ex, "Downloading nzb for release '{0}' failed ({1})", remoteEpisode.Release.Title, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading nzb failed", ex);
|
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading nzb failed", ex);
|
||||||
}
|
}
|
||||||
catch (WebException ex)
|
catch (WebException ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Downloading nzb for episode '{0}' failed ({1})", remoteEpisode.Release.Title, url);
|
_logger.Error(ex, "Downloading nzb for release '{0}' failed ({1})", remoteEpisode.Release.Title, url);
|
||||||
|
|
||||||
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading nzb failed", ex);
|
throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading nzb failed", ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
{
|
{
|
||||||
public interface IProvideArtistInfo
|
public interface IProvideArtistInfo
|
||||||
{
|
{
|
||||||
Tuple<Artist, List<Track>> GetArtistInfo(string spotifyId);
|
Tuple<Artist, List<Album>> GetArtistInfo(string lidarrId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
|
||||||
{
|
|
||||||
public class AlbumInfoResource
|
|
||||||
{
|
|
||||||
public AlbumInfoResource()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
public string AlbumType { get; set; } // Might need to make this a separate class
|
|
||||||
public List<ArtistInfoResource> Artists { get; set; } // Will always be length of 1 unless a compilation
|
|
||||||
public string Url { get; set; } // Link to the endpoint api to give full info for this object
|
|
||||||
public string Id { get; set; } // This is a unique Album ID. Needed for all future API calls
|
|
||||||
public List<ImageResource> Images { get; set; }
|
|
||||||
public string Name { get; set; } // In case of a takedown, this may be empty
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
||||||
|
{
|
||||||
|
public class AlbumResource
|
||||||
|
{
|
||||||
|
public AlbumResource()
|
||||||
|
{
|
||||||
|
Tracks = new List<TrackResource>();
|
||||||
|
}
|
||||||
|
//public string AlbumType { get; set; } // Might need to make this a separate class
|
||||||
|
public List<ArtistResource> Artists { get; set; } // Will always be length of 1 unless a compilation
|
||||||
|
public string Url { get; set; } // Link to the endpoint api to give full info for this object
|
||||||
|
public string Id { get; set; } // This is a unique Album ID. Needed for all future API calls
|
||||||
|
public DateTime ReleaseDate { get; set; }
|
||||||
|
public List<ImageResource> Images { get; set; }
|
||||||
|
public string Title { get; set; } // In case of a takedown, this may be empty
|
||||||
|
public string Overview { get; set; }
|
||||||
|
public List<string> Genres { get; set; }
|
||||||
|
public string Label { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public List<TrackResource> Tracks { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -11,10 +11,9 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
||||||
|
|
||||||
public List<string> Genres { get; set; }
|
public List<string> Genres { get; set; }
|
||||||
public string AristUrl { get; set; }
|
public string AristUrl { get; set; }
|
||||||
|
public string Overview { get; set; }
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public List<ImageResource> Images { get; set; }
|
public List<ImageResource> Images { get; set; }
|
||||||
public string Name { get; set; }
|
public string ArtistName { get; set; }
|
||||||
|
|
||||||
// We may need external_urls.spotify to external linking...
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,44 +5,19 @@ using System.Text;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
||||||
{
|
{
|
||||||
|
|
||||||
public class AristResultResource
|
|
||||||
{
|
|
||||||
public AristResultResource()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<ArtistInfoResource> Items { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AlbumResultResource
|
|
||||||
{
|
|
||||||
public AlbumResultResource()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<AlbumInfoResource> Items { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TrackResultResource
|
|
||||||
{
|
|
||||||
public TrackResultResource()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<TrackInfoResource> Items { get; set; }
|
|
||||||
}
|
|
||||||
public class ArtistResource
|
public class ArtistResource
|
||||||
{
|
{
|
||||||
public ArtistResource()
|
public ArtistResource() {
|
||||||
{
|
Albums = new List<AlbumResource>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AristResultResource Artists { get; set; }
|
public List<string> Genres { get; set; }
|
||||||
public AristResultResource Albums { get; set; }
|
public string AristUrl { get; set; }
|
||||||
|
public string Overview { get; set; }
|
||||||
|
public string Id { get; set; }
|
||||||
|
public List<ImageResource> Images { get; set; }
|
||||||
|
public string ArtistName { get; set; }
|
||||||
|
public List<AlbumResource> Albums { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@ using System.Text;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
||||||
{
|
{
|
||||||
public class TrackInfoResource
|
public class TrackResource
|
||||||
{
|
{
|
||||||
public TrackInfoResource()
|
public TrackResource()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,10 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
||||||
public int DurationMs { get; set; }
|
public int DurationMs { get; set; }
|
||||||
public string Href { get; set; }
|
public string Href { get; set; }
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string TrackName { get; set; }
|
||||||
public int TrackNumber { get; set; }
|
public int TrackNumber { get; set; }
|
||||||
public bool Explicit { get; set; }
|
public bool Explicit { get; set; }
|
||||||
public List<ArtistInfoResource> Artists { get; set; }
|
public List<ArtistResource> Artists { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -74,17 +74,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Tuple<Artist, List<Track>> GetArtistInfo(string spotifyId)
|
public Tuple<Artist, List<Album>> GetArtistInfo(string foreignArtistId)
|
||||||
{
|
{
|
||||||
|
|
||||||
_logger.Debug("Getting Artist with SpotifyId of {0}", spotifyId);
|
_logger.Debug("Getting Artist with SpotifyId of {0}", foreignArtistId);
|
||||||
|
|
||||||
///v1/albums/{id}
|
|
||||||
//
|
|
||||||
|
|
||||||
// We need to perform a direct lookup of the artist
|
// We need to perform a direct lookup of the artist
|
||||||
var httpRequest = _requestBuilder.Create()
|
var httpRequest = _requestBuilder.Create()
|
||||||
.SetSegment("route", "artists/" + spotifyId)
|
.SetSegment("route", "artists/" + foreignArtistId)
|
||||||
//.SetSegment("route", "search")
|
//.SetSegment("route", "search")
|
||||||
//.AddQueryParam("type", "artist,album")
|
//.AddQueryParam("type", "artist,album")
|
||||||
//.AddQueryParam("q", spotifyId.ToString())
|
//.AddQueryParam("q", spotifyId.ToString())
|
||||||
|
@ -95,14 +92,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
httpRequest.AllowAutoRedirect = true;
|
httpRequest.AllowAutoRedirect = true;
|
||||||
httpRequest.SuppressHttpError = true;
|
httpRequest.SuppressHttpError = true;
|
||||||
|
|
||||||
var httpResponse = _httpClient.Get<ArtistInfoResource>(httpRequest);
|
var httpResponse = _httpClient.Get<ArtistResource>(httpRequest);
|
||||||
|
|
||||||
|
|
||||||
if (httpResponse.HasHttpError)
|
if (httpResponse.HasHttpError)
|
||||||
{
|
{
|
||||||
if (httpResponse.StatusCode == HttpStatusCode.NotFound)
|
if (httpResponse.StatusCode == HttpStatusCode.NotFound)
|
||||||
{
|
{
|
||||||
throw new ArtistNotFoundException(spotifyId);
|
throw new ArtistNotFoundException(foreignArtistId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -110,92 +107,96 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Artist artist = new Artist();
|
// It is safe to assume an id will only return one Artist back
|
||||||
artist.ArtistName = httpResponse.Resource.Name;
|
var albums = httpResponse.Resource.Albums.Select(MapAlbum);
|
||||||
artist.SpotifyId = httpResponse.Resource.Id;
|
var artist = MapArtist(httpResponse.Resource);
|
||||||
artist.Genres = httpResponse.Resource.Genres;
|
|
||||||
|
|
||||||
var albumRet = MapAlbums(artist);
|
//artist.Name = httpResponse.Resource.Artists.Items[0].ArtistName;
|
||||||
artist = albumRet.Item1;
|
|
||||||
|
|
||||||
|
//artist.ForeignArtistId = httpResponse.Resource.Artists.Items[0].Id;
|
||||||
|
//artist.Genres = httpResponse.Resource.Artists.Items[0].Genres;
|
||||||
|
|
||||||
|
//var albumRet = MapAlbums(artist);
|
||||||
|
//artist = albumRet.Item1;
|
||||||
|
|
||||||
return new Tuple<Artist, List<Track>>(albumRet.Item1, albumRet.Item2);
|
return new Tuple<Artist, List<Album>>(artist, albums.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tuple<Artist, List<Track>> MapAlbums(Artist artist)
|
|
||||||
{
|
|
||||||
|
|
||||||
// Find all albums for the artist and all tracks for said album
|
|
||||||
///v1/artists/{id}/albums
|
|
||||||
var httpRequest = _requestBuilder.Create()
|
|
||||||
.SetSegment("route", "artists/" + artist.SpotifyId + "/albums")
|
|
||||||
.Build();
|
|
||||||
httpRequest.AllowAutoRedirect = true;
|
|
||||||
httpRequest.SuppressHttpError = true;
|
|
||||||
|
|
||||||
var httpResponse = _httpClient.Get<AlbumResultResource>(httpRequest);
|
//private Tuple<Artist, List<Track>> MapAlbums(Artist artist)
|
||||||
|
//{
|
||||||
|
|
||||||
if (httpResponse.HasHttpError)
|
// // Find all albums for the artist and all tracks for said album
|
||||||
{
|
// ///v1/artists/{id}/albums
|
||||||
throw new HttpException(httpRequest, httpResponse);
|
// var httpRequest = _requestBuilder.Create()
|
||||||
}
|
// .SetSegment("route", "artists/" + artist.ForeignArtistId + "/albums")
|
||||||
|
// .Build();
|
||||||
|
// httpRequest.AllowAutoRedirect = true;
|
||||||
|
// httpRequest.SuppressHttpError = true;
|
||||||
|
|
||||||
List<Track> masterTracks = new List<Track>();
|
// var httpResponse = _httpClient.Get<AlbumResultResource>(httpRequest);
|
||||||
List<Album> albums = new List<Album>();
|
|
||||||
foreach(var albumResource in httpResponse.Resource.Items)
|
|
||||||
{
|
|
||||||
Album album = new Album();
|
|
||||||
album.AlbumId = albumResource.Id;
|
|
||||||
album.Title = albumResource.Name;
|
|
||||||
album.ArtworkUrl = albumResource.Images.Count > 0 ? albumResource.Images[0].Url : "";
|
|
||||||
album.Tracks = MapTracksToAlbum(album);
|
|
||||||
masterTracks.InsertRange(masterTracks.Count, album.Tracks);
|
|
||||||
albums.Add(album);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: We now need to get all tracks for each album
|
// if (httpResponse.HasHttpError)
|
||||||
|
// {
|
||||||
|
// throw new HttpException(httpRequest, httpResponse);
|
||||||
|
// }
|
||||||
|
|
||||||
artist.Albums = albums;
|
// List<Track> masterTracks = new List<Track>();
|
||||||
return new Tuple<Artist, List<Track>>(artist, masterTracks);
|
// List<Album> albums = new List<Album>();
|
||||||
}
|
// foreach(var albumResource in httpResponse.Resource.Items)
|
||||||
|
// {
|
||||||
|
// Album album = new Album();
|
||||||
|
// album.AlbumId = albumResource.Id;
|
||||||
|
// album.Title = albumResource.AlbumName;
|
||||||
|
// album.ArtworkUrl = albumResource.Images.Count > 0 ? albumResource.Images[0].Url : "";
|
||||||
|
// album.Tracks = MapTracksToAlbum(album);
|
||||||
|
// masterTracks.InsertRange(masterTracks.Count, album.Tracks);
|
||||||
|
// albums.Add(album);
|
||||||
|
// }
|
||||||
|
|
||||||
private List<Track> MapTracksToAlbum(Album album)
|
// // TODO: We now need to get all tracks for each album
|
||||||
{
|
|
||||||
var httpRequest = _requestBuilder.Create()
|
|
||||||
.SetSegment("route", "albums/" + album.AlbumId + "/tracks")
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
httpRequest.AllowAutoRedirect = true;
|
// artist.Albums = albums;
|
||||||
httpRequest.SuppressHttpError = true;
|
// return new Tuple<Artist, List<Track>>(artist, masterTracks);
|
||||||
|
//}
|
||||||
|
|
||||||
var httpResponse = _httpClient.Get<TrackResultResource>(httpRequest);
|
//private List<Track> MapTracksToAlbum(Album album)
|
||||||
|
//{
|
||||||
|
// var httpRequest = _requestBuilder.Create()
|
||||||
|
// .SetSegment("route", "albums/" + album.AlbumId + "/tracks")
|
||||||
|
// .Build();
|
||||||
|
|
||||||
if (httpResponse.HasHttpError)
|
// httpRequest.AllowAutoRedirect = true;
|
||||||
{
|
// httpRequest.SuppressHttpError = true;
|
||||||
throw new HttpException(httpRequest, httpResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Track> tracks = new List<Track>();
|
// var httpResponse = _httpClient.Get<TrackResultResource>(httpRequest);
|
||||||
foreach(var trackResource in httpResponse.Resource.Items)
|
|
||||||
{
|
|
||||||
Track track = new Track();
|
|
||||||
track.AlbumId = album.AlbumId;
|
|
||||||
//track.Album = album; // This will cause infinite loop when trying to serialize.
|
|
||||||
// TODO: Implement more track mapping
|
|
||||||
//track.Artist = trackResource.Artists
|
|
||||||
//track.ArtistId = album.
|
|
||||||
track.SpotifyTrackId = trackResource.Id;
|
|
||||||
track.ArtistSpotifyId = trackResource.Artists.Count > 0 ? trackResource.Artists[0].Id : null;
|
|
||||||
track.Explict = trackResource.Explicit;
|
|
||||||
track.Compilation = trackResource.Artists.Count > 1;
|
|
||||||
track.TrackNumber = trackResource.TrackNumber;
|
|
||||||
track.Title = trackResource.Name;
|
|
||||||
tracks.Add(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tracks;
|
// if (httpResponse.HasHttpError)
|
||||||
}
|
// {
|
||||||
|
// throw new HttpException(httpRequest, httpResponse);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// List<Track> tracks = new List<Track>();
|
||||||
|
// foreach (var trackResource in httpResponse.Resource.Items)
|
||||||
|
// {
|
||||||
|
// Track track = new Track();
|
||||||
|
// track.AlbumId = album.AlbumId;
|
||||||
|
// //track.Album = album; // This will cause infinite loop when trying to serialize.
|
||||||
|
// // TODO: Implement more track mapping
|
||||||
|
// //track.Artist = trackResource.Artists
|
||||||
|
// //track.ArtistId = album.
|
||||||
|
// track.SpotifyTrackId = trackResource.Id;
|
||||||
|
// track.ArtistSpotifyId = trackResource.Artists.Count > 0 ? trackResource.Artists[0].Id : null;
|
||||||
|
// track.Explict = trackResource.Explicit;
|
||||||
|
// track.Compilation = trackResource.Artists.Count > 1;
|
||||||
|
// track.TrackNumber = trackResource.TrackNumber;
|
||||||
|
// track.Title = trackResource.TrackName;
|
||||||
|
// tracks.Add(track);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return tracks;
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
public List<Artist> SearchForNewArtist(string title)
|
public List<Artist> SearchForNewArtist(string title)
|
||||||
|
@ -226,18 +227,32 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
|
|
||||||
var httpRequest = _requestBuilder.Create()
|
var httpRequest = _requestBuilder.Create()
|
||||||
.SetSegment("route", "search")
|
.SetSegment("route", "search")
|
||||||
.AddQueryParam("type", "artist,album")
|
.AddQueryParam("type", "artist") // TODO: LidarrAPI.Metadata is getting , encoded. Needs to be raw ,
|
||||||
.AddQueryParam("q", title.ToLower().Trim())
|
.AddQueryParam("query", title.ToLower().Trim())
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var httpResponse = _httpClient.Get<ArtistResource>(httpRequest);
|
var httpResponse = _httpClient.Get<List<ArtistResource>>(httpRequest);
|
||||||
|
|
||||||
|
return httpResponse.Resource.SelectList(MapArtist);
|
||||||
|
//List<Artist> artists = MapArtists(httpResponse.Resource);
|
||||||
|
//List<Artist> artists = new List<Artist>();
|
||||||
|
//foreach (var artistResource in httpResponse.Resource.Artists.Items)
|
||||||
|
//{
|
||||||
|
// Artist artist = new Artist();
|
||||||
|
// artist.Name = artistResource.ArtistName;
|
||||||
|
// artist.ForeignArtistId = artistResource.Id; // TODO: Rename spotifyId to LidarrId
|
||||||
|
// artist.Genres = artistResource.Genres;
|
||||||
|
// artist.NameSlug = Parser.Parser.CleanArtistTitle(artist.Name);
|
||||||
|
// artist.CleanName = Parser.Parser.CleanArtistTitle(artist.Name);
|
||||||
|
//artist.Images = artistResource.Images;
|
||||||
|
|
||||||
|
// artists.Add(artist);
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
List<Artist> artists = MapArtists(httpResponse.Resource);
|
//return artists;
|
||||||
|
|
||||||
return artists;
|
|
||||||
}
|
}
|
||||||
catch (HttpException)
|
catch (HttpException)
|
||||||
{
|
{
|
||||||
|
@ -250,39 +265,50 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Artist MapArtistInfo(ArtistInfoResource resource)
|
private static Album MapAlbum(AlbumResource resource)
|
||||||
{
|
{
|
||||||
// This expects ArtistInfoResource, thus just need to populate one artist
|
Album album = new Album();
|
||||||
Artist artist = new Artist();
|
album.Title = resource.Title;
|
||||||
//artist.Overview = resource.artistBio;
|
album.ForeignAlbumId = resource.Id;
|
||||||
//artist.ArtistName = resource.name;
|
album.ReleaseDate = resource.ReleaseDate;
|
||||||
//foreach(var genre in resource.genreNames)
|
album.CleanTitle = Parser.Parser.CleanArtistTitle(album.Title);
|
||||||
//{
|
album.AlbumType = resource.Type;
|
||||||
// artist.Genres.Add(genre);
|
|
||||||
//}
|
|
||||||
|
|
||||||
return artist;
|
var tracks = resource.Tracks.Select(MapTrack);
|
||||||
}
|
album.Tracks = tracks.ToList();
|
||||||
|
|
||||||
private List<Artist> MapArtists(ArtistResource resource)
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
List<Artist> artists = new List<Artist>();
|
return album;
|
||||||
foreach(var artistResource in resource.Artists.Items)
|
}
|
||||||
{
|
|
||||||
Artist artist = new Artist();
|
private static Track MapTrack(TrackResource resource)
|
||||||
artist.ArtistName = artistResource.Name;
|
{
|
||||||
artist.SpotifyId = artistResource.Id;
|
Track track = new Track();
|
||||||
artist.Genres = artistResource.Genres;
|
track.Title = resource.TrackName;
|
||||||
artist.ArtistSlug = Parser.Parser.CleanArtistTitle(artist.ArtistName);
|
track.ForeignTrackId = resource.Id;
|
||||||
artists.Add(artist);
|
track.TrackNumber = resource.TrackNumber;
|
||||||
}
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Artist MapArtist(ArtistResource resource)
|
||||||
|
{
|
||||||
|
|
||||||
|
Artist artist = new Artist();
|
||||||
|
|
||||||
|
artist.Name = resource.ArtistName;
|
||||||
|
artist.ForeignArtistId = resource.Id; // TODO: Rename spotifyId to LidarrId
|
||||||
|
artist.Genres = resource.Genres;
|
||||||
|
artist.Overview = resource.Overview;
|
||||||
|
artist.NameSlug = Parser.Parser.CleanArtistTitle(artist.Name);
|
||||||
|
artist.CleanName = Parser.Parser.CleanArtistTitle(artist.Name);
|
||||||
|
//artist.Images = resource.Artists.Items[0].Images;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Maybe? Get all the albums for said artist
|
// Maybe? Get all the albums for said artist
|
||||||
|
|
||||||
|
|
||||||
return artists;
|
return artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
//private Album MapAlbum(AlbumResource albumQuery)
|
//private Album MapAlbum(AlbumResource albumQuery)
|
||||||
|
|
|
@ -48,11 +48,11 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(newArtist.Path))
|
if (string.IsNullOrWhiteSpace(newArtist.Path))
|
||||||
{
|
{
|
||||||
var folderName = newArtist.ArtistName;// TODO: _fileNameBuilder.GetArtistFolder(newArtist);
|
var folderName = _fileNameBuilder.GetArtistFolder(newArtist);
|
||||||
newArtist.Path = Path.Combine(newArtist.RootFolderPath, folderName);
|
newArtist.Path = Path.Combine(newArtist.RootFolderPath, folderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
newArtist.CleanTitle = newArtist.ArtistName.CleanSeriesTitle();
|
newArtist.CleanName = newArtist.Name.CleanArtistTitle();
|
||||||
//newArtist.SortTitle = ArtistNameNormalizer.Normalize(newArtist.ArtistName, newArtist.ItunesId); // There is no Sort Title
|
//newArtist.SortTitle = ArtistNameNormalizer.Normalize(newArtist.ArtistName, newArtist.ItunesId); // There is no Sort Title
|
||||||
newArtist.Added = DateTime.UtcNow;
|
newArtist.Added = DateTime.UtcNow;
|
||||||
|
|
||||||
|
@ -71,19 +71,19 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
private Artist AddSkyhookData(Artist newArtist)
|
private Artist AddSkyhookData(Artist newArtist)
|
||||||
{
|
{
|
||||||
Tuple<Artist, List<Track>> tuple;
|
Tuple<Artist, List<Album>> tuple;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
tuple = _artistInfo.GetArtistInfo(newArtist.SpotifyId);
|
tuple = _artistInfo.GetArtistInfo(newArtist.ForeignArtistId);
|
||||||
}
|
}
|
||||||
catch (SeriesNotFoundException)
|
catch (ArtistNotFoundException)
|
||||||
{
|
{
|
||||||
_logger.Error("SpotifyId {1} was not found, it may have been removed from Spotify.", newArtist.SpotifyId);
|
_logger.Error("LidarrId {1} was not found, it may have been removed from Lidarr.", newArtist.ForeignArtistId);
|
||||||
|
|
||||||
throw new ValidationException(new List<ValidationFailure>
|
throw new ValidationException(new List<ValidationFailure>
|
||||||
{
|
{
|
||||||
new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.SpotifyId)
|
new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.ForeignArtistId)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ namespace NzbDrone.Core.Music
|
||||||
.SetValidator(droneFactoryValidator)
|
.SetValidator(droneFactoryValidator)
|
||||||
.SetValidator(seriesAncestorValidator);
|
.SetValidator(seriesAncestorValidator);
|
||||||
|
|
||||||
RuleFor(c => c.ArtistSlug).SetValidator(artistTitleSlugValidator);// TODO: Check if we are going to use a slug or artistName
|
RuleFor(c => c.NameSlug).SetValidator(artistTitleSlugValidator);// TODO: Check if we are going to use a slug or artistName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -7,24 +8,34 @@ using System.Text;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Music
|
namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
public class Album : IEmbeddedDocument
|
public class Album : ModelBase
|
||||||
{
|
{
|
||||||
public Album()
|
public Album()
|
||||||
{
|
{
|
||||||
Images = new List<MediaCover.MediaCover>();
|
Images = new List<MediaCover.MediaCover>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string AlbumId { get; set; }
|
public string ForeignAlbumId { get; set; }
|
||||||
|
public int ArtistId { get; set; }
|
||||||
public string Title { get; set; } // NOTE: This should be CollectionName in API
|
public string Title { get; set; } // NOTE: This should be CollectionName in API
|
||||||
public int Year { get; set; }
|
public string CleanTitle { get; set; }
|
||||||
public int TrackCount { get; set; }
|
public DateTime ReleaseDate { get; set; }
|
||||||
|
//public int TrackCount { get; set; }
|
||||||
|
public string Path { get; set; }
|
||||||
public List<Track> Tracks { get; set; }
|
public List<Track> Tracks { get; set; }
|
||||||
public int DiscCount { get; set; }
|
//public int DiscCount { get; set; }
|
||||||
public bool Monitored { get; set; }
|
public bool Monitored { get; set; }
|
||||||
public List<MediaCover.MediaCover> Images { get; set; }
|
public List<MediaCover.MediaCover> Images { get; set; }
|
||||||
public List<Actor> Actors { get; set; } // These are band members. TODO: Refactor
|
//public List<Actor> Actors { get; set; } // These are band members. TODO: Refactor
|
||||||
public List<string> Genres { get; set; }
|
public List<string> Genres { get; set; }
|
||||||
public string ArtworkUrl { get; set; }
|
public String AlbumType { get; set; } //Turn this into a type similar to Series Type in TV
|
||||||
public string Explicitness { get; set; }
|
//public string ArtworkUrl { get; set; }
|
||||||
|
//public string Explicitness { get; set; }
|
||||||
|
public AddSeriesOptions AddOptions { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format("[{0}][{1}]", ForeignAlbumId, Title.NullSafe());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
46
src/NzbDrone.Core/Music/AlbumRepository.cs
Normal file
46
src/NzbDrone.Core/Music/AlbumRepository.cs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music
|
||||||
|
{
|
||||||
|
public interface IAlbumRepository : IBasicRepository<Album>
|
||||||
|
{
|
||||||
|
bool AlbumPathExists(string path);
|
||||||
|
List<Album> GetAlbums(int artistId);
|
||||||
|
Album FindByName(string cleanTitle);
|
||||||
|
Album FindById(string spotifyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlbumRepository : BasicRepository<Album>, IAlbumRepository
|
||||||
|
{
|
||||||
|
public AlbumRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||||
|
: base(database, eventAggregator)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool AlbumPathExists(string path)
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.Path == path).Any();
|
||||||
|
}
|
||||||
|
public List<Album> GetAlbums(int artistId)
|
||||||
|
{
|
||||||
|
return Query.Where(s => s.ArtistId == artistId).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Album FindById(string foreignAlbumId)
|
||||||
|
{
|
||||||
|
return Query.Where(s => s.ForeignAlbumId == foreignAlbumId).SingleOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Album FindByName(string cleanTitle)
|
||||||
|
{
|
||||||
|
cleanTitle = cleanTitle.ToLowerInvariant();
|
||||||
|
|
||||||
|
return Query.Where(s => s.CleanTitle == cleanTitle)
|
||||||
|
.SingleOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
147
src/NzbDrone.Core/Music/AlbumService.cs
Normal file
147
src/NzbDrone.Core/Music/AlbumService.cs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Music.Events;
|
||||||
|
using NzbDrone.Core.Organizer;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music
|
||||||
|
{
|
||||||
|
public interface IAlbumService
|
||||||
|
{
|
||||||
|
Album GetAlbum(int albumid);
|
||||||
|
List<Album> GetAlbums(IEnumerable<int> albumIds);
|
||||||
|
List<Album> GetAlbumsByArtist(int artistId);
|
||||||
|
Album AddAlbum(Album newAlbum);
|
||||||
|
Album FindById(string spotifyId);
|
||||||
|
Album FindByTitleInexact(string title);
|
||||||
|
void DeleteAlbum(int albumId, bool deleteFiles);
|
||||||
|
List<Album> GetAllAlbums();
|
||||||
|
Album UpdateAlbum(Album album);
|
||||||
|
List<Album> UpdateAlbums(List<Album> album);
|
||||||
|
void InsertMany(List<Album> albums);
|
||||||
|
void UpdateMany(List<Album> albums);
|
||||||
|
void DeleteMany(List<Album> albums);
|
||||||
|
bool AlbumPathExists(string folder);
|
||||||
|
void RemoveAddOptions(Album album);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AlbumService : IAlbumService
|
||||||
|
{
|
||||||
|
private readonly IAlbumRepository _albumRepository;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private readonly ITrackService _trackService;
|
||||||
|
private readonly IBuildFileNames _fileNameBuilder;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public AlbumService(IAlbumRepository albumRepository,
|
||||||
|
IEventAggregator eventAggregator,
|
||||||
|
ITrackService trackService,
|
||||||
|
IBuildFileNames fileNameBuilder,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_albumRepository = albumRepository;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
_trackService = trackService;
|
||||||
|
_fileNameBuilder = fileNameBuilder;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Album AddAlbum(Album newAlbum)
|
||||||
|
{
|
||||||
|
_albumRepository.Insert(newAlbum);
|
||||||
|
_eventAggregator.PublishEvent(new AlbumAddedEvent(GetAlbum(newAlbum.Id)));
|
||||||
|
|
||||||
|
return newAlbum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AlbumPathExists(string folder)
|
||||||
|
{
|
||||||
|
return _albumRepository.AlbumPathExists(folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteAlbum(int albumId, bool deleteFiles)
|
||||||
|
{
|
||||||
|
var album = _albumRepository.Get(albumId);
|
||||||
|
_albumRepository.Delete(albumId);
|
||||||
|
_eventAggregator.PublishEvent(new AlbumDeletedEvent(album, deleteFiles));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Album FindById(string spotifyId)
|
||||||
|
{
|
||||||
|
return _albumRepository.FindById(spotifyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public Album FindByTitleInexact(string title)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Album> GetAllAlbums()
|
||||||
|
{
|
||||||
|
return _albumRepository.All().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Album GetAlbum(int albumId)
|
||||||
|
{
|
||||||
|
return _albumRepository.Get(albumId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Album> GetAlbums(IEnumerable<int> albumIds)
|
||||||
|
{
|
||||||
|
return _albumRepository.Get(albumIds).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Album> GetAlbumsByArtist(int artistId)
|
||||||
|
{
|
||||||
|
return _albumRepository.GetAlbums(artistId).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAddOptions(Album album)
|
||||||
|
{
|
||||||
|
_albumRepository.SetFields(album, s => s.AddOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InsertMany(List<Album> albums)
|
||||||
|
{
|
||||||
|
_albumRepository.InsertMany(albums);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateMany(List<Album> albums)
|
||||||
|
{
|
||||||
|
_albumRepository.UpdateMany(albums);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteMany(List<Album> albums)
|
||||||
|
{
|
||||||
|
_albumRepository.DeleteMany(albums);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Album UpdateAlbum(Album album)
|
||||||
|
{
|
||||||
|
var storedAlbum = GetAlbum(album.Id); // Is it Id or iTunesId?
|
||||||
|
|
||||||
|
var updatedAlbum = _albumRepository.Update(album);
|
||||||
|
_eventAggregator.PublishEvent(new AlbumEditedEvent(updatedAlbum, storedAlbum));
|
||||||
|
|
||||||
|
return updatedAlbum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Album> UpdateAlbums(List<Album> album)
|
||||||
|
{
|
||||||
|
_logger.Debug("Updating {0} album", album.Count);
|
||||||
|
|
||||||
|
_albumRepository.UpdateMany(album);
|
||||||
|
_logger.Debug("{0} albums updated", album.Count);
|
||||||
|
|
||||||
|
return album;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,21 +22,19 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SpotifyId { get; set; }
|
public string ForeignArtistId { get; set; }
|
||||||
public string ArtistName { get; set; }
|
public string Name { get; set; }
|
||||||
public string ArtistSlug { get; set; }
|
public string NameSlug { get; set; }
|
||||||
public string CleanTitle { get; set; }
|
public string CleanName { get; set; }
|
||||||
public string Overview { get; set; }
|
public string Overview { get; set; }
|
||||||
public bool Monitored { get; set; }
|
public bool Monitored { get; set; }
|
||||||
public bool AlbumFolder { get; set; }
|
public bool AlbumFolder { get; set; }
|
||||||
public bool ArtistFolder { get; set; }
|
|
||||||
public DateTime? LastInfoSync { get; set; }
|
public DateTime? LastInfoSync { get; set; }
|
||||||
public DateTime? LastDiskSync { get; set; }
|
public DateTime? LastDiskSync { get; set; }
|
||||||
public int Status { get; set; } // TODO: Figure out what this is, do we need it?
|
public int Status { get; set; } // TODO: Figure out what this is, do we need it?
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
public List<MediaCover.MediaCover> Images { get; set; }
|
public List<MediaCover.MediaCover> Images { get; set; }
|
||||||
public List<string> Genres { get; set; }
|
public List<string> Genres { get; set; }
|
||||||
public int QualityProfileId { get; set; }
|
|
||||||
public string RootFolderPath { get; set; }
|
public string RootFolderPath { get; set; }
|
||||||
public DateTime Added { get; set; }
|
public DateTime Added { get; set; }
|
||||||
public LazyLoaded<Profile> Profile { get; set; }
|
public LazyLoaded<Profile> Profile { get; set; }
|
||||||
|
@ -47,16 +45,16 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("[{0}][{1}]", SpotifyId, ArtistName.NullSafe());
|
return string.Format("[{0}][{1}]", ForeignArtistId, Name.NullSafe());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyChanges(Artist otherArtist)
|
public void ApplyChanges(Artist otherArtist)
|
||||||
{
|
{
|
||||||
|
|
||||||
SpotifyId = otherArtist.SpotifyId;
|
ForeignArtistId = otherArtist.ForeignArtistId;
|
||||||
ArtistName = otherArtist.ArtistName;
|
Name = otherArtist.Name;
|
||||||
ArtistSlug = otherArtist.ArtistSlug;
|
NameSlug = otherArtist.NameSlug;
|
||||||
CleanTitle = otherArtist.CleanTitle;
|
CleanName = otherArtist.CleanName;
|
||||||
Monitored = otherArtist.Monitored;
|
Monitored = otherArtist.Monitored;
|
||||||
AlbumFolder = otherArtist.AlbumFolder;
|
AlbumFolder = otherArtist.AlbumFolder;
|
||||||
LastInfoSync = otherArtist.LastInfoSync;
|
LastInfoSync = otherArtist.LastInfoSync;
|
||||||
|
@ -69,7 +67,6 @@ namespace NzbDrone.Core.Music
|
||||||
ProfileId = otherArtist.ProfileId;
|
ProfileId = otherArtist.ProfileId;
|
||||||
Albums = otherArtist.Albums;
|
Albums = otherArtist.Albums;
|
||||||
Tags = otherArtist.Tags;
|
Tags = otherArtist.Tags;
|
||||||
ArtistFolder = otherArtist.ArtistFolder;
|
|
||||||
AddOptions = otherArtist.AddOptions;
|
AddOptions = otherArtist.AddOptions;
|
||||||
|
|
||||||
Albums = otherArtist.Albums;
|
Albums = otherArtist.Albums;
|
||||||
|
|
|
@ -24,16 +24,16 @@ namespace NzbDrone.Core.Music
|
||||||
return Query.Where(c => c.Path == path).Any();
|
return Query.Where(c => c.Path == path).Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Artist FindById(string spotifyId)
|
public Artist FindById(string foreignArtistId)
|
||||||
{
|
{
|
||||||
return Query.Where(s => s.SpotifyId == spotifyId).SingleOrDefault();
|
return Query.Where(s => s.ForeignArtistId == foreignArtistId).SingleOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Artist FindByName(string cleanName)
|
public Artist FindByName(string cleanName)
|
||||||
{
|
{
|
||||||
cleanName = cleanName.ToLowerInvariant();
|
cleanName = cleanName.ToLowerInvariant();
|
||||||
|
|
||||||
return Query.Where(s => s.CleanTitle == cleanName)
|
return Query.Where(s => s.CleanName == cleanName)
|
||||||
.SingleOrDefault();
|
.SingleOrDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,11 +111,11 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
foreach (var album in artist.Albums)
|
foreach (var album in artist.Albums)
|
||||||
{
|
{
|
||||||
var storedAlbum = storedArtist.Albums.SingleOrDefault(s => s.AlbumId == album.AlbumId);
|
var storedAlbum = storedArtist.Albums.SingleOrDefault(s => s.ForeignAlbumId == album.ForeignAlbumId);
|
||||||
|
|
||||||
if (storedAlbum != null && album.Monitored != storedAlbum.Monitored)
|
if (storedAlbum != null && album.Monitored != storedAlbum.Monitored)
|
||||||
{
|
{
|
||||||
_trackService.SetTrackMonitoredByAlbum(artist.SpotifyId, album.AlbumId, album.Monitored);
|
_trackService.SetTrackMonitoredByAlbum(artist.ForeignArtistId, album.ForeignAlbumId, album.Monitored);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,17 +130,17 @@ namespace NzbDrone.Core.Music
|
||||||
_logger.Debug("Updating {0} artist", artist.Count);
|
_logger.Debug("Updating {0} artist", artist.Count);
|
||||||
foreach (var s in artist)
|
foreach (var s in artist)
|
||||||
{
|
{
|
||||||
_logger.Trace("Updating: {0}", s.ArtistName);
|
_logger.Trace("Updating: {0}", s.Name);
|
||||||
if (!s.RootFolderPath.IsNullOrWhiteSpace())
|
if (!s.RootFolderPath.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
var folderName = new DirectoryInfo(s.Path).Name;
|
var folderName = new DirectoryInfo(s.Path).Name;
|
||||||
s.Path = Path.Combine(s.RootFolderPath, folderName);
|
s.Path = Path.Combine(s.RootFolderPath, folderName);
|
||||||
_logger.Trace("Changing path for {0} to {1}", s.ArtistName, s.Path);
|
_logger.Trace("Changing path for {0} to {1}", s.Name, s.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Trace("Not changing path for: {0}", s.ArtistName);
|
_logger.Trace("Not changing path for: {0}", s.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace NzbDrone.Core.Music
|
||||||
dynamic instance = context.ParentContext.InstanceToValidate;
|
dynamic instance = context.ParentContext.InstanceToValidate;
|
||||||
var instanceId = (int)instance.Id;
|
var instanceId = (int)instance.Id;
|
||||||
|
|
||||||
return !_artistService.GetAllArtists().Exists(s => s.ArtistSlug.Equals(context.PropertyValue.ToString()) && s.Id != instanceId);
|
return !_artistService.GetAllArtists().Exists(s => s.NameSlug.Equals(context.PropertyValue.ToString()) && s.Id != instanceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
18
src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs
Normal file
18
src/NzbDrone.Core/Music/Events/AlbumAddedEvent.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 AlbumAddedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Album Album { get; private set; }
|
||||||
|
|
||||||
|
public AlbumAddedEvent(Album album)
|
||||||
|
{
|
||||||
|
Album = album;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs
Normal file
20
src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.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 AlbumDeletedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Album Album { get; private set; }
|
||||||
|
public bool DeleteFiles { get; private set; }
|
||||||
|
|
||||||
|
public AlbumDeletedEvent(Album album, bool deleteFiles)
|
||||||
|
{
|
||||||
|
Album = album;
|
||||||
|
DeleteFiles = deleteFiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/NzbDrone.Core/Music/Events/AlbumEditedEvent.cs
Normal file
20
src/NzbDrone.Core/Music/Events/AlbumEditedEvent.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 AlbumEditedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Album Album { get; private set; }
|
||||||
|
public Album OldAlbum { get; private set; }
|
||||||
|
|
||||||
|
public AlbumEditedEvent(Album album, Album oldAlbum)
|
||||||
|
{
|
||||||
|
Album = album;
|
||||||
|
OldAlbum = oldAlbum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
src/NzbDrone.Core/Music/Events/AlbumInfoRefreshedEvent.cs
Normal file
23
src/NzbDrone.Core/Music/Events/AlbumInfoRefreshedEvent.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music.Events
|
||||||
|
{
|
||||||
|
public class AlbumInfoRefreshedEvent : IEvent
|
||||||
|
{
|
||||||
|
public Artist Artist { get; set; }
|
||||||
|
public ReadOnlyCollection<Album> Added { get; private set; }
|
||||||
|
public ReadOnlyCollection<Album> Updated { get; private set; }
|
||||||
|
|
||||||
|
public AlbumInfoRefreshedEvent(Artist artist, IList<Album> added, IList<Album> updated)
|
||||||
|
{
|
||||||
|
Artist = artist;
|
||||||
|
Added = new ReadOnlyCollection<Album>(added);
|
||||||
|
Updated = new ReadOnlyCollection<Album>(updated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,13 +9,13 @@ namespace NzbDrone.Core.Music.Events
|
||||||
{
|
{
|
||||||
public class TrackInfoRefreshedEvent : IEvent
|
public class TrackInfoRefreshedEvent : IEvent
|
||||||
{
|
{
|
||||||
public Artist Artist { get; set; }
|
public Album Album { get; set; }
|
||||||
public ReadOnlyCollection<Track> Added { get; private set; }
|
public ReadOnlyCollection<Track> Added { get; private set; }
|
||||||
public ReadOnlyCollection<Track> Updated { get; private set; }
|
public ReadOnlyCollection<Track> Updated { get; private set; }
|
||||||
|
|
||||||
public TrackInfoRefreshedEvent(Artist artist, IList<Track> added, IList<Track> updated)
|
public TrackInfoRefreshedEvent(Album album, IList<Track> added, IList<Track> updated)
|
||||||
{
|
{
|
||||||
Artist = artist;
|
Album = album;
|
||||||
Added = new ReadOnlyCollection<Track>(added);
|
Added = new ReadOnlyCollection<Track>(added);
|
||||||
Updated = new ReadOnlyCollection<Track>(updated);
|
Updated = new ReadOnlyCollection<Track>(updated);
|
||||||
}
|
}
|
||||||
|
|
133
src/NzbDrone.Core/Music/RefreshAlbumService.cs
Normal file
133
src/NzbDrone.Core/Music/RefreshAlbumService.cs
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Music.Events;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NzbDrone.Core.Organizer;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Music
|
||||||
|
{
|
||||||
|
public interface IRefreshAlbumService
|
||||||
|
{
|
||||||
|
void RefreshAlbumInfo(Artist artist, IEnumerable<Album> remoteAlbums);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RefreshAlbumService : IRefreshAlbumService
|
||||||
|
{
|
||||||
|
private readonly IAlbumService _albumService;
|
||||||
|
private readonly IRefreshTrackService _refreshTrackService;
|
||||||
|
private readonly IEventAggregator _eventAggregator;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public RefreshAlbumService(IAlbumService albumService, IRefreshTrackService refreshTrackService, IEventAggregator eventAggregator, Logger logger)
|
||||||
|
{
|
||||||
|
_albumService = albumService;
|
||||||
|
_refreshTrackService = refreshTrackService;
|
||||||
|
_eventAggregator = eventAggregator;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RefreshAlbumInfo(Artist artist, IEnumerable<Album> remoteAlbums)
|
||||||
|
{
|
||||||
|
_logger.Info("Starting album info refresh for: {0}", artist);
|
||||||
|
var successCount = 0;
|
||||||
|
var failCount = 0;
|
||||||
|
|
||||||
|
var existingAlbums = _albumService.GetAlbumsByArtist(artist.Id);
|
||||||
|
var albums = artist.Albums;
|
||||||
|
|
||||||
|
var updateList = new List<Album>();
|
||||||
|
var newList = new List<Album>();
|
||||||
|
var dupeFreeRemoteAlbums = remoteAlbums.DistinctBy(m => new { m.ForeignAlbumId, m.ReleaseDate }).ToList();
|
||||||
|
|
||||||
|
foreach (var album in OrderAlbums(artist, dupeFreeRemoteAlbums))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var albumToUpdate = GetAlbumToUpdate(artist, album, existingAlbums);
|
||||||
|
|
||||||
|
if (albumToUpdate != null)
|
||||||
|
{
|
||||||
|
existingAlbums.Remove(albumToUpdate);
|
||||||
|
updateList.Add(albumToUpdate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
albumToUpdate = new Album();
|
||||||
|
albumToUpdate.Monitored = artist.Monitored;
|
||||||
|
newList.Add(albumToUpdate);
|
||||||
|
//var folderName = _fileNameBuilder.GetAlbumFolder(albumToUpdate); //This likely does not belong here, need to create AddAlbumService
|
||||||
|
//albumToUpdate.Path = Path.Combine(newArtist.RootFolderPath, folderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
albumToUpdate.ForeignAlbumId = album.ForeignAlbumId;
|
||||||
|
albumToUpdate.CleanTitle = album.CleanTitle;
|
||||||
|
//albumToUpdate.TrackNumber = album.TrackNumber;
|
||||||
|
albumToUpdate.Title = album.Title ?? "Unknown";
|
||||||
|
//albumToUpdate.AlbumId = album.AlbumId;
|
||||||
|
//albumToUpdate.Album = album.Album;
|
||||||
|
//albumToUpdate.Explicit = album.Explicit;
|
||||||
|
albumToUpdate.ArtistId = artist.Id;
|
||||||
|
albumToUpdate.Path = artist.Path + album.Title;
|
||||||
|
albumToUpdate.AlbumType = album.AlbumType;
|
||||||
|
//albumToUpdate.Compilation = album.Compilation;
|
||||||
|
|
||||||
|
_refreshTrackService.RefreshTrackInfo(album, album.Tracks);
|
||||||
|
|
||||||
|
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.Fatal(e, "An error has occurred while updating track info for artist {0}. {1}", artist, album);
|
||||||
|
failCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var allAlbums = new List<Album>();
|
||||||
|
allAlbums.AddRange(newList);
|
||||||
|
allAlbums.AddRange(updateList);
|
||||||
|
|
||||||
|
// TODO: See if anything needs to be done here
|
||||||
|
//AdjustMultiEpisodeAirTime(artist, allTracks);
|
||||||
|
//AdjustDirectToDvdAirDate(artist, allTracks);
|
||||||
|
|
||||||
|
_albumService.DeleteMany(existingAlbums);
|
||||||
|
_albumService.UpdateMany(updateList);
|
||||||
|
_albumService.InsertMany(newList);
|
||||||
|
|
||||||
|
_eventAggregator.PublishEvent(new AlbumInfoRefreshedEvent(artist, newList, updateList));
|
||||||
|
|
||||||
|
if (failCount != 0)
|
||||||
|
{
|
||||||
|
_logger.Info("Finished album refresh for artist: {0}. Successful: {1} - Failed: {2} ",
|
||||||
|
artist.Name, successCount, failCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Info("Finished album refresh for artist: {0}.", artist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool GetMonitoredStatus(Album album, IEnumerable<Artist> artists)
|
||||||
|
{
|
||||||
|
var artist = artists.SingleOrDefault(c => c.Id == album.ArtistId);
|
||||||
|
return album == null || album.Monitored;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Album GetAlbumToUpdate(Artist artist, Album album, List<Album> existingAlbums)
|
||||||
|
{
|
||||||
|
return existingAlbums.FirstOrDefault(e => e.ForeignAlbumId == album.ForeignAlbumId && e.ReleaseDate == album.ReleaseDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Album> OrderAlbums(Artist artist, List<Album> albums)
|
||||||
|
{
|
||||||
|
return albums.OrderBy(e => e.ForeignAlbumId).ThenBy(e => e.ReleaseDate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
private readonly IProvideArtistInfo _artistInfo;
|
private readonly IProvideArtistInfo _artistInfo;
|
||||||
private readonly IArtistService _artistService;
|
private readonly IArtistService _artistService;
|
||||||
private readonly IRefreshTrackService _refreshTrackService;
|
private readonly IRefreshAlbumService _refreshAlbumService;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly IDiskScanService _diskScanService;
|
private readonly IDiskScanService _diskScanService;
|
||||||
private readonly ICheckIfArtistShouldBeRefreshed _checkIfArtistShouldBeRefreshed;
|
private readonly ICheckIfArtistShouldBeRefreshed _checkIfArtistShouldBeRefreshed;
|
||||||
|
@ -28,7 +28,7 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
public RefreshArtistService(IProvideArtistInfo artistInfo,
|
public RefreshArtistService(IProvideArtistInfo artistInfo,
|
||||||
IArtistService artistService,
|
IArtistService artistService,
|
||||||
IRefreshTrackService refreshTrackService,
|
IRefreshAlbumService refreshAlbumService,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
IDiskScanService diskScanService,
|
IDiskScanService diskScanService,
|
||||||
ICheckIfArtistShouldBeRefreshed checkIfArtistShouldBeRefreshed,
|
ICheckIfArtistShouldBeRefreshed checkIfArtistShouldBeRefreshed,
|
||||||
|
@ -36,7 +36,7 @@ namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
_artistInfo = artistInfo;
|
_artistInfo = artistInfo;
|
||||||
_artistService = artistService;
|
_artistService = artistService;
|
||||||
_refreshTrackService = refreshTrackService;
|
_refreshAlbumService = refreshAlbumService;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_diskScanService = diskScanService;
|
_diskScanService = diskScanService;
|
||||||
_checkIfArtistShouldBeRefreshed = checkIfArtistShouldBeRefreshed;
|
_checkIfArtistShouldBeRefreshed = checkIfArtistShouldBeRefreshed;
|
||||||
|
@ -45,33 +45,33 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
private void RefreshArtistInfo(Artist artist)
|
private void RefreshArtistInfo(Artist artist)
|
||||||
{
|
{
|
||||||
_logger.ProgressInfo("Updating Info for {0}", artist.ArtistName);
|
_logger.ProgressInfo("Updating Info for {0}", artist.Name);
|
||||||
|
|
||||||
Tuple<Artist, List<Track>> tuple;
|
Tuple<Artist, List<Album>> tuple;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
tuple = _artistInfo.GetArtistInfo(artist.SpotifyId);
|
tuple = _artistInfo.GetArtistInfo(artist.ForeignArtistId);
|
||||||
}
|
}
|
||||||
catch (ArtistNotFoundException)
|
catch (ArtistNotFoundException)
|
||||||
{
|
{
|
||||||
_logger.Error("Artist '{0}' (SpotifyId {1}) was not found, it may have been removed from Spotify.", artist.ArtistName, artist.SpotifyId);
|
_logger.Error("Artist '{0}' (SpotifyId {1}) was not found, it may have been removed from Spotify.", artist.Name, artist.ForeignArtistId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var artistInfo = tuple.Item1;
|
var artistInfo = tuple.Item1;
|
||||||
|
|
||||||
if (artist.SpotifyId != artistInfo.SpotifyId)
|
if (artist.ForeignArtistId != artistInfo.ForeignArtistId)
|
||||||
{
|
{
|
||||||
_logger.Warn("Artist '{0}' (SpotifyId {1}) was replaced with '{2}' (SpotifyId {3}), because the original was a duplicate.", artist.ArtistName, artist.SpotifyId, artistInfo.ArtistName, artistInfo.SpotifyId);
|
_logger.Warn("Artist '{0}' (SpotifyId {1}) was replaced with '{2}' (SpotifyId {3}), because the original was a duplicate.", artist.Name, artist.ForeignArtistId, artistInfo.Name, artistInfo.ForeignArtistId);
|
||||||
artist.SpotifyId = artistInfo.SpotifyId;
|
artist.ForeignArtistId = artistInfo.ForeignArtistId;
|
||||||
}
|
}
|
||||||
|
|
||||||
artist.ArtistName = artistInfo.ArtistName;
|
artist.Name = artistInfo.Name;
|
||||||
artist.ArtistSlug = artistInfo.ArtistSlug;
|
artist.NameSlug = artistInfo.NameSlug;
|
||||||
artist.Overview = artistInfo.Overview;
|
artist.Overview = artistInfo.Overview;
|
||||||
artist.Status = artistInfo.Status;
|
artist.Status = artistInfo.Status;
|
||||||
artist.CleanTitle = artistInfo.CleanTitle;
|
artist.CleanName = artistInfo.CleanName;
|
||||||
artist.LastInfoSync = DateTime.UtcNow;
|
artist.LastInfoSync = DateTime.UtcNow;
|
||||||
artist.Images = artistInfo.Images;
|
artist.Images = artistInfo.Images;
|
||||||
artist.Genres = artistInfo.Genres;
|
artist.Genres = artistInfo.Genres;
|
||||||
|
@ -89,19 +89,20 @@ namespace NzbDrone.Core.Music
|
||||||
artist.Albums = UpdateAlbums(artist, artistInfo);
|
artist.Albums = UpdateAlbums(artist, artistInfo);
|
||||||
|
|
||||||
_artistService.UpdateArtist(artist);
|
_artistService.UpdateArtist(artist);
|
||||||
_refreshTrackService.RefreshTrackInfo(artist, tuple.Item2);
|
_refreshAlbumService.RefreshAlbumInfo(artist, tuple.Item2);
|
||||||
|
//_refreshTrackService.RefreshTrackInfo(artist, tuple.Item2);
|
||||||
|
|
||||||
_logger.Debug("Finished artist refresh for {0}", artist.ArtistName);
|
_logger.Debug("Finished artist refresh for {0}", artist.Name);
|
||||||
_eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist));
|
_eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Album> UpdateAlbums(Artist artist, Artist artistInfo)
|
private List<Album> UpdateAlbums(Artist artist, Artist artistInfo)
|
||||||
{
|
{
|
||||||
var albums = artistInfo.Albums.DistinctBy(s => s.AlbumId).ToList();
|
var albums = artistInfo.Albums.DistinctBy(s => s.ForeignAlbumId).ToList();
|
||||||
|
|
||||||
foreach (var album in albums)
|
foreach (var album in albums)
|
||||||
{
|
{
|
||||||
var existingAlbum = artist.Albums.FirstOrDefault(s => s.AlbumId == album.AlbumId);
|
var existingAlbum = artist.Albums.FirstOrDefault(s => s.ForeignAlbumId == album.ForeignAlbumId);
|
||||||
|
|
||||||
//Todo: Should this should use the previous season's monitored state?
|
//Todo: Should this should use the previous season's monitored state?
|
||||||
if (existingAlbum == null)
|
if (existingAlbum == null)
|
||||||
|
@ -112,7 +113,7 @@ namespace NzbDrone.Core.Music
|
||||||
// continue;
|
// continue;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
_logger.Debug("New album ({0}) for artist: [{1}] {2}, setting monitored to true", album.Title, artist.SpotifyId, artist.ArtistName);
|
_logger.Debug("New album ({0}) for artist: [{1}] {2}, setting monitored to true", album.Title, artist.ForeignArtistId, artist.Name);
|
||||||
album.Monitored = true;
|
album.Monitored = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +137,7 @@ namespace NzbDrone.Core.Music
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var allArtists = _artistService.GetAllArtists().OrderBy(c => c.ArtistName).ToList();
|
var allArtists = _artistService.GetAllArtists().OrderBy(c => c.Name).ToList();
|
||||||
|
|
||||||
foreach (var artist in allArtists)
|
foreach (var artist in allArtists)
|
||||||
{
|
{
|
||||||
|
@ -156,8 +157,8 @@ namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.Info("Skipping refresh of artist: {0}", artist.ArtistName);
|
_logger.Info("Skipping refresh of artist: {0}", artist.Name);
|
||||||
_diskScanService.Scan(artist);
|
_diskScanService.Scan(artist);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
public interface IRefreshTrackService
|
public interface IRefreshTrackService
|
||||||
{
|
{
|
||||||
void RefreshTrackInfo(Artist artist, IEnumerable<Track> remoteTracks);
|
void RefreshTrackInfo(Album album, IEnumerable<Track> remoteTracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RefreshTrackService : IRefreshTrackService
|
public class RefreshTrackService : IRefreshTrackService
|
||||||
|
@ -27,24 +27,24 @@ namespace NzbDrone.Core.Music
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshTrackInfo(Artist artist, IEnumerable<Track> remoteTracks)
|
public void RefreshTrackInfo(Album album, IEnumerable<Track> remoteTracks)
|
||||||
{
|
{
|
||||||
_logger.Info("Starting track info refresh for: {0}", artist);
|
_logger.Info("Starting track info refresh for: {0}", album);
|
||||||
var successCount = 0;
|
var successCount = 0;
|
||||||
var failCount = 0;
|
var failCount = 0;
|
||||||
|
|
||||||
var existingTracks = _trackService.GetTracksByArtist(artist.SpotifyId);
|
var existingTracks = _trackService.GetTracksByAlbum(album.ArtistId, album.Id);
|
||||||
var albums = artist.Albums;
|
//var albums = artist.Albums;
|
||||||
|
|
||||||
var updateList = new List<Track>();
|
var updateList = new List<Track>();
|
||||||
var newList = new List<Track>();
|
var newList = new List<Track>();
|
||||||
var dupeFreeRemoteTracks = remoteTracks.DistinctBy(m => new { m.AlbumId, m.TrackNumber }).ToList();
|
var dupeFreeRemoteTracks = remoteTracks.DistinctBy(m => new { m.AlbumId, m.TrackNumber }).ToList();
|
||||||
|
|
||||||
foreach (var track in OrderTracks(artist, dupeFreeRemoteTracks))
|
foreach (var track in OrderTracks(album, dupeFreeRemoteTracks))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var trackToUpdate = GetTrackToUpdate(artist, track, existingTracks);
|
var trackToUpdate = GetTrackToUpdate(album, track, existingTracks);
|
||||||
|
|
||||||
if (trackToUpdate != null)
|
if (trackToUpdate != null)
|
||||||
{
|
{
|
||||||
|
@ -54,24 +54,17 @@ namespace NzbDrone.Core.Music
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trackToUpdate = new Track();
|
trackToUpdate = new Track();
|
||||||
trackToUpdate.Monitored = GetMonitoredStatus(track, albums);
|
trackToUpdate.Monitored = album.Monitored;
|
||||||
newList.Add(trackToUpdate);
|
newList.Add(trackToUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
trackToUpdate.SpotifyTrackId = track.SpotifyTrackId;
|
trackToUpdate.ForeignTrackId = track.ForeignTrackId;
|
||||||
trackToUpdate.TrackNumber = track.TrackNumber;
|
trackToUpdate.TrackNumber = track.TrackNumber;
|
||||||
trackToUpdate.Title = track.Title ?? "Unknown";
|
trackToUpdate.Title = track.Title ?? "Unknown";
|
||||||
trackToUpdate.AlbumId = track.AlbumId;
|
trackToUpdate.AlbumId = album.Id;
|
||||||
trackToUpdate.Album = track.Album;
|
trackToUpdate.Album = track.Album;
|
||||||
trackToUpdate.Explict = track.Explict;
|
trackToUpdate.Explicit = track.Explicit;
|
||||||
if (track.ArtistSpotifyId.IsNullOrWhiteSpace())
|
trackToUpdate.ArtistId = album.ArtistId;
|
||||||
{
|
|
||||||
trackToUpdate.ArtistSpotifyId = artist.SpotifyId;
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
trackToUpdate.ArtistSpotifyId = track.ArtistSpotifyId;
|
|
||||||
}
|
|
||||||
trackToUpdate.ArtistId = track.ArtistId;
|
|
||||||
trackToUpdate.Compilation = track.Compilation;
|
trackToUpdate.Compilation = track.Compilation;
|
||||||
|
|
||||||
// TODO: Implement rest of [RefreshTrackService] fields
|
// TODO: Implement rest of [RefreshTrackService] fields
|
||||||
|
@ -82,7 +75,7 @@ namespace NzbDrone.Core.Music
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_logger.Fatal(e, "An error has occurred while updating track info for artist {0}. {1}", artist, track);
|
_logger.Fatal(e, "An error has occurred while updating track info for album {0}. {1}", album, track);
|
||||||
failCount++;
|
failCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,16 +92,16 @@ namespace NzbDrone.Core.Music
|
||||||
_trackService.UpdateMany(updateList);
|
_trackService.UpdateMany(updateList);
|
||||||
_trackService.InsertMany(newList);
|
_trackService.InsertMany(newList);
|
||||||
|
|
||||||
_eventAggregator.PublishEvent(new TrackInfoRefreshedEvent(artist, newList, updateList));
|
_eventAggregator.PublishEvent(new TrackInfoRefreshedEvent(album, newList, updateList));
|
||||||
|
|
||||||
if (failCount != 0)
|
if (failCount != 0)
|
||||||
{
|
{
|
||||||
_logger.Info("Finished track refresh for artist: {0}. Successful: {1} - Failed: {2} ",
|
_logger.Info("Finished track refresh for album: {0}. Successful: {1} - Failed: {2} ",
|
||||||
artist.ArtistName, successCount, failCount);
|
album.Title, successCount, failCount);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Info("Finished track refresh for artist: {0}.", artist);
|
_logger.Info("Finished track refresh for album: {0}.", album);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,17 +112,17 @@ namespace NzbDrone.Core.Music
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var album = albums.SingleOrDefault(c => c.AlbumId == track.AlbumId);
|
var album = albums.SingleOrDefault(c => c.Id == track.AlbumId);
|
||||||
return album == null || album.Monitored;
|
return album == null || album.Monitored;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Track GetTrackToUpdate(Artist artist, Track track, List<Track> existingTracks)
|
private Track GetTrackToUpdate(Album album, Track track, List<Track> existingTracks)
|
||||||
{
|
{
|
||||||
return existingTracks.FirstOrDefault(e => e.AlbumId == track.AlbumId && e.TrackNumber == track.TrackNumber);
|
return existingTracks.FirstOrDefault(e => e.AlbumId == track.AlbumId && e.TrackNumber == track.TrackNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<Track> OrderTracks(Artist artist, List<Track> tracks)
|
private IEnumerable<Track> OrderTracks(Album album, List<Track> tracks)
|
||||||
{
|
{
|
||||||
return tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber);
|
return tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,13 +26,13 @@ namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
if (artist.LastInfoSync < DateTime.UtcNow.AddDays(-30))
|
if (artist.LastInfoSync < DateTime.UtcNow.AddDays(-30))
|
||||||
{
|
{
|
||||||
_logger.Trace("Artist {0} last updated more than 30 days ago, should refresh.", artist.ArtistName);
|
_logger.Trace("Artist {0} last updated more than 30 days ago, should refresh.", artist.Name);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (artist.LastInfoSync >= DateTime.UtcNow.AddHours(-6))
|
if (artist.LastInfoSync >= DateTime.UtcNow.AddHours(-6))
|
||||||
{
|
{
|
||||||
_logger.Trace("Artist {0} last updated less than 6 hours ago, should not be refreshed.", artist.ArtistName);
|
_logger.Trace("Artist {0} last updated less than 6 hours ago, should not be refreshed.", artist.Name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,20 +17,20 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd";
|
public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd";
|
||||||
|
|
||||||
public string SpotifyTrackId { get; set; }
|
public string ForeignTrackId { get; set; }
|
||||||
public string AlbumId { get; set; }
|
public int AlbumId { get; set; }
|
||||||
public LazyLoaded<Artist> Artist { get; set; }
|
public LazyLoaded<Artist> Artist { get; set; }
|
||||||
public string ArtistSpotifyId { get; set; }
|
|
||||||
public long ArtistId { get; set; } // This is the DB Id of the Artist, not the SpotifyId
|
public int ArtistId { get; set; } // This is the DB Id of the Artist, not the SpotifyId
|
||||||
//public int CompilationId { get; set; }
|
//public int CompilationId { get; set; }
|
||||||
public bool Compilation { get; set; }
|
public bool Compilation { get; set; }
|
||||||
public int TrackNumber { get; set; }
|
public int TrackNumber { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public bool Ignored { get; set; }
|
//public bool Ignored { get; set; }
|
||||||
public bool Explict { get; set; }
|
public bool Explicit { get; set; }
|
||||||
public bool Monitored { get; set; }
|
public bool Monitored { get; set; }
|
||||||
public int TrackFileId { get; set; }
|
public int TrackFileId { get; set; }
|
||||||
public DateTime? ReleaseDate { get; set; }
|
//public DateTime? ReleaseDate { get; set; }
|
||||||
|
|
||||||
public LazyLoaded<TrackFile> TrackFile { get; set; }
|
public LazyLoaded<TrackFile> TrackFile { get; set; }
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format("[{0}]{1}", SpotifyTrackId, Title.NullSafe());
|
return string.Format("[{0}]{1}", ForeignTrackId, Title.NullSafe());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,11 @@ namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
public interface ITrackRepository : IBasicRepository<Track>
|
public interface ITrackRepository : IBasicRepository<Track>
|
||||||
{
|
{
|
||||||
Track Find(string artistId, string albumId, int trackNumber);
|
Track Find(int artistId, int albumId, int trackNumber);
|
||||||
List<Track> GetTracks(string artistId);
|
List<Track> GetTracks(int artistId);
|
||||||
List<Track> GetTracks(string artistId, string albumId);
|
List<Track> GetTracks(int artistId, int albumId);
|
||||||
List<Track> GetTracksByFileId(int fileId);
|
List<Track> GetTracksByFileId(int fileId);
|
||||||
List<Track> TracksWithFiles(string artistId);
|
List<Track> TracksWithFiles(int artistId);
|
||||||
PagingSpec<Track> TracksWithoutFiles(PagingSpec<Track> pagingSpec);
|
PagingSpec<Track> TracksWithoutFiles(PagingSpec<Track> pagingSpec);
|
||||||
PagingSpec<Track> TracksWhereCutoffUnmet(PagingSpec<Track> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff);
|
PagingSpec<Track> TracksWhereCutoffUnmet(PagingSpec<Track> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff);
|
||||||
void SetMonitoredFlat(Track episode, bool monitored);
|
void SetMonitoredFlat(Track episode, bool monitored);
|
||||||
|
@ -37,23 +37,23 @@ namespace NzbDrone.Core.Music
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Track Find(string artistId, string albumId, int trackNumber)
|
public Track Find(int artistId, int albumId, int trackNumber)
|
||||||
{
|
{
|
||||||
return Query.Where(s => s.ArtistSpotifyId == artistId)
|
return Query.Where(s => s.ArtistId == artistId)
|
||||||
.AndWhere(s => s.AlbumId == albumId)
|
.AndWhere(s => s.AlbumId == albumId)
|
||||||
.AndWhere(s => s.TrackNumber == trackNumber)
|
.AndWhere(s => s.TrackNumber == trackNumber)
|
||||||
.SingleOrDefault();
|
.SingleOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<Track> GetTracks(string artistId)
|
public List<Track> GetTracks(int artistId)
|
||||||
{
|
{
|
||||||
return Query.Where(s => s.ArtistSpotifyId == artistId).ToList();
|
return Query.Where(s => s.ArtistId == artistId).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Track> GetTracks(string artistId, string albumId)
|
public List<Track> GetTracks(int artistId, int albumId)
|
||||||
{
|
{
|
||||||
return Query.Where(s => s.ArtistSpotifyId == artistId)
|
return Query.Where(s => s.ArtistId == artistId)
|
||||||
.AndWhere(s => s.AlbumId == albumId)
|
.AndWhere(s => s.AlbumId == albumId)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
@ -63,10 +63,10 @@ namespace NzbDrone.Core.Music
|
||||||
return Query.Where(e => e.TrackFileId == fileId).ToList();
|
return Query.Where(e => e.TrackFileId == fileId).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Track> TracksWithFiles(string artistId)
|
public List<Track> TracksWithFiles(int artistId)
|
||||||
{
|
{
|
||||||
return Query.Join<Track, TrackFile>(JoinType.Inner, e => e.TrackFile, (e, ef) => e.TrackFileId == ef.Id)
|
return Query.Join<Track, TrackFile>(JoinType.Inner, e => e.TrackFile, (e, ef) => e.TrackFileId == ef.Id)
|
||||||
.Where(e => e.ArtistSpotifyId == artistId);
|
.Where(e => e.ArtistId == artistId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PagingSpec<Track> TracksWhereCutoffUnmet(PagingSpec<Track> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff)
|
public PagingSpec<Track> TracksWhereCutoffUnmet(PagingSpec<Track> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff)
|
||||||
|
@ -118,7 +118,7 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
private SortBuilder<Track> GetMissingEpisodesQuery(PagingSpec<Track> pagingSpec, DateTime currentTime)
|
private SortBuilder<Track> GetMissingEpisodesQuery(PagingSpec<Track> pagingSpec, DateTime currentTime)
|
||||||
{
|
{
|
||||||
return Query.Join<Track, Artist>(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistSpotifyId == s.SpotifyId)
|
return Query.Join<Track, Artist>(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistId == s.Id)
|
||||||
.Where(pagingSpec.FilterExpression)
|
.Where(pagingSpec.FilterExpression)
|
||||||
.AndWhere(e => e.TrackFileId == 0)
|
.AndWhere(e => e.TrackFileId == 0)
|
||||||
.AndWhere(BuildAirDateUtcCutoffWhereClause(currentTime))
|
.AndWhere(BuildAirDateUtcCutoffWhereClause(currentTime))
|
||||||
|
@ -130,7 +130,7 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
private SortBuilder<Track> EpisodesWhereCutoffUnmetQuery(PagingSpec<Track> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff)
|
private SortBuilder<Track> EpisodesWhereCutoffUnmetQuery(PagingSpec<Track> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff)
|
||||||
{
|
{
|
||||||
return Query.Join<Track, Artist>(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistSpotifyId == s.SpotifyId)
|
return Query.Join<Track, Artist>(JoinType.Inner, e => e.Artist, (e, s) => e.ArtistId == s.Id)
|
||||||
.Join<Track, TrackFile>(JoinType.Left, e => e.TrackFile, (e, s) => e.TrackFileId == s.Id)
|
.Join<Track, TrackFile>(JoinType.Left, e => e.TrackFile, (e, s) => e.TrackFileId == s.Id)
|
||||||
.Where(pagingSpec.FilterExpression)
|
.Where(pagingSpec.FilterExpression)
|
||||||
.AndWhere(e => e.TrackFileId != 0)
|
.AndWhere(e => e.TrackFileId != 0)
|
||||||
|
|
|
@ -15,12 +15,12 @@ namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
Track GetTrack(int id);
|
Track GetTrack(int id);
|
||||||
List<Track> GetTracks(IEnumerable<int> ids);
|
List<Track> GetTracks(IEnumerable<int> ids);
|
||||||
Track FindTrack(string artistId, string albumId, int trackNumber);
|
Track FindTrack(int artistId, int albumId, int trackNumber);
|
||||||
Track FindTrackByTitle(string artistId, string albumId, string releaseTitle);
|
Track FindTrackByTitle(int artistId, int albumId, string releaseTitle);
|
||||||
List<Track> GetTracksByArtist(string artistId);
|
List<Track> GetTracksByArtist(int artistId);
|
||||||
//List<Track> GetTracksByAlbum(string artistId, string albumId);
|
List<Track> GetTracksByAlbum(int artistId, int albumId);
|
||||||
//List<Track> GetTracksByAlbumTitle(string artistId, string albumTitle);
|
//List<Track> GetTracksByAlbumTitle(string artistId, string albumTitle);
|
||||||
List<Track> TracksWithFiles(string artistId);
|
List<Track> TracksWithFiles(int artistId);
|
||||||
//PagingSpec<Track> TracksWithoutFiles(PagingSpec<Track> pagingSpec);
|
//PagingSpec<Track> TracksWithoutFiles(PagingSpec<Track> pagingSpec);
|
||||||
List<Track> GetTracksByFileId(int trackFileId);
|
List<Track> GetTracksByFileId(int trackFileId);
|
||||||
void UpdateTrack(Track track);
|
void UpdateTrack(Track track);
|
||||||
|
@ -60,22 +60,17 @@ namespace NzbDrone.Core.Music
|
||||||
return _trackRepository.Find(artistId, albumId, trackNumber);
|
return _trackRepository.Find(artistId, albumId, trackNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
//public Track FindTrack(string artistId, int trackNumber)
|
|
||||||
//{
|
|
||||||
// return _trackRepository.Find(artistId, trackNumber);
|
|
||||||
//}
|
|
||||||
|
|
||||||
public List<Track> GetTracksByArtist(string artistId)
|
public List<Track> GetTracksByArtist(string artistId)
|
||||||
{
|
{
|
||||||
return _trackRepository.GetTracks(artistId).ToList();
|
return _trackRepository.GetTracks(artistId).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Track> GetTracksByAlbum(string artistId, string albumId)
|
public List<Track> GetTracksByAlbum(int artistId, int albumId)
|
||||||
{
|
{
|
||||||
return _trackRepository.GetTracks(artistId, albumId);
|
return _trackRepository.GetTracks(artistId, albumId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Track FindTrackByTitle(string artistId, string albumId, string releaseTitle)
|
public Track FindTrackByTitle(int artistId, int albumId, string releaseTitle)
|
||||||
{
|
{
|
||||||
// TODO: can replace this search mechanism with something smarter/faster/better
|
// TODO: can replace this search mechanism with something smarter/faster/better
|
||||||
var normalizedReleaseTitle = Parser.Parser.NormalizeEpisodeTitle(releaseTitle).Replace(".", " ");
|
var normalizedReleaseTitle = Parser.Parser.NormalizeEpisodeTitle(releaseTitle).Replace(".", " ");
|
||||||
|
@ -101,7 +96,7 @@ namespace NzbDrone.Core.Music
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Track> TracksWithFiles(string artistId)
|
public List<Track> TracksWithFiles(int artistId)
|
||||||
{
|
{
|
||||||
return _trackRepository.TracksWithFiles(artistId);
|
return _trackRepository.TracksWithFiles(artistId);
|
||||||
}
|
}
|
||||||
|
@ -159,7 +154,7 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
public void HandleAsync(ArtistDeletedEvent message)
|
public void HandleAsync(ArtistDeletedEvent message)
|
||||||
{
|
{
|
||||||
var tracks = GetTracksByArtist(message.Artist.SpotifyId);
|
var tracks = GetTracksByArtist(message.Artist.Id);
|
||||||
_trackRepository.DeleteMany(tracks);
|
_trackRepository.DeleteMany(tracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -288,7 +288,6 @@
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Datastore\Migration\105_rename_torrent_downloadstation.cs" />
|
<Compile Include="Datastore\Migration\105_rename_torrent_downloadstation.cs" />
|
||||||
<Compile Include="Datastore\Migration\111_setup_music.cs" />
|
<Compile Include="Datastore\Migration\111_setup_music.cs" />
|
||||||
<Compile Include="Datastore\Migration\112_add_music_fields_to_namingconfig.cs" />
|
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
|
||||||
<Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" />
|
<Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" />
|
||||||
|
@ -823,7 +822,7 @@
|
||||||
<Compile Include="Messaging\IProcessMessage.cs" />
|
<Compile Include="Messaging\IProcessMessage.cs" />
|
||||||
<Compile Include="MetadataSource\IProvideArtistInfo.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\AlbumInfoResource.cs" />
|
<Compile Include="MetadataSource\SkyHook\Resource\AlbumResource.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\Resource\ArtistInfoResource.cs" />
|
<Compile Include="MetadataSource\SkyHook\Resource\ArtistInfoResource.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" />
|
||||||
|
@ -832,7 +831,7 @@
|
||||||
<Compile Include="MetadataSource\SkyHook\Resource\SeasonResource.cs" />
|
<Compile Include="MetadataSource\SkyHook\Resource\SeasonResource.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\Resource\ShowResource.cs" />
|
<Compile Include="MetadataSource\SkyHook\Resource\ShowResource.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\Resource\TimeOfDayResource.cs" />
|
<Compile Include="MetadataSource\SkyHook\Resource\TimeOfDayResource.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\Resource\TrackInfoResource.cs" />
|
<Compile Include="MetadataSource\SkyHook\Resource\TrackResource.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\SkyHookProxy.cs" />
|
<Compile Include="MetadataSource\SkyHook\SkyHookProxy.cs" />
|
||||||
<Compile Include="MetadataSource\SearchSeriesComparer.cs" />
|
<Compile Include="MetadataSource\SearchSeriesComparer.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\SkyHookException.cs" />
|
<Compile Include="MetadataSource\SkyHook\SkyHookException.cs" />
|
||||||
|
@ -864,17 +863,24 @@
|
||||||
<Compile Include="Music\Artist.cs" />
|
<Compile Include="Music\Artist.cs" />
|
||||||
<Compile Include="Music\ArtistAddedHandler.cs" />
|
<Compile Include="Music\ArtistAddedHandler.cs" />
|
||||||
<Compile Include="Music\ArtistNameNormalizer.cs" />
|
<Compile Include="Music\ArtistNameNormalizer.cs" />
|
||||||
|
<Compile Include="Music\AlbumService.cs" />
|
||||||
|
<Compile Include="Music\AlbumRepository.cs" />
|
||||||
<Compile Include="Music\ArtistSlugValidator.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\Commands\RefreshArtistCommand.cs" />
|
<Compile Include="Music\Commands\RefreshArtistCommand.cs" />
|
||||||
|
<Compile Include="Music\Events\AlbumAddedEvent.cs" />
|
||||||
<Compile Include="Music\Events\ArtistAddedEvent.cs" />
|
<Compile Include="Music\Events\ArtistAddedEvent.cs" />
|
||||||
|
<Compile Include="Music\Events\AlbumDeletedEvent.cs" />
|
||||||
<Compile Include="Music\Events\ArtistDeletedEvent.cs" />
|
<Compile Include="Music\Events\ArtistDeletedEvent.cs" />
|
||||||
|
<Compile Include="Music\Events\AlbumEditedEvent.cs" />
|
||||||
<Compile Include="Music\Events\ArtistEditedEvent.cs" />
|
<Compile Include="Music\Events\ArtistEditedEvent.cs" />
|
||||||
<Compile Include="Music\Events\ArtistRefreshStartingEvent.cs" />
|
<Compile Include="Music\Events\ArtistRefreshStartingEvent.cs" />
|
||||||
<Compile Include="Music\Events\ArtistUpdatedEvent.cs" />
|
<Compile Include="Music\Events\ArtistUpdatedEvent.cs" />
|
||||||
|
<Compile Include="Music\Events\AlbumInfoRefreshedEvent.cs" />
|
||||||
<Compile Include="Music\Events\TrackInfoRefreshedEvent.cs" />
|
<Compile Include="Music\Events\TrackInfoRefreshedEvent.cs" />
|
||||||
<Compile Include="Music\RefreshArtistService.cs" />
|
<Compile Include="Music\RefreshArtistService.cs" />
|
||||||
|
<Compile Include="Music\RefreshAlbumService.cs" />
|
||||||
<Compile Include="Music\RefreshTrackService.cs" />
|
<Compile Include="Music\RefreshTrackService.cs" />
|
||||||
<Compile Include="Music\ShouldRefreshArtist.cs" />
|
<Compile Include="Music\ShouldRefreshArtist.cs" />
|
||||||
<Compile Include="Music\Track.cs" />
|
<Compile Include="Music\Track.cs" />
|
||||||
|
|
|
@ -18,10 +18,13 @@ namespace NzbDrone.Core.Organizer
|
||||||
public interface IBuildFileNames
|
public interface IBuildFileNames
|
||||||
{
|
{
|
||||||
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
|
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
|
||||||
|
string BuildTrackFileName(List<Track> tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null);
|
||||||
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
|
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
|
||||||
string BuildSeasonPath(Series series, int seasonNumber);
|
string BuildSeasonPath(Series series, int seasonNumber);
|
||||||
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
||||||
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
|
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
|
||||||
|
string GetArtistFolder(Artist artist, NamingConfig namingConfig = null);
|
||||||
|
string GetAlbumFolder(Artist artist, Album album, 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
|
// TODO: Implement Music functions
|
||||||
|
@ -42,6 +45,9 @@ namespace NzbDrone.Core.Organizer
|
||||||
private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})",
|
private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
private static readonly Regex TrackRegex = new Regex(@"(?<track>\{track(?:\:0+)?})",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly Regex SeasonRegex = new Regex(@"(?<season>\{season(?:\:0+)?})",
|
private static readonly Regex SeasonRegex = new Regex(@"(?<season>\{season(?:\:0+)?})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
@ -59,6 +65,12 @@ namespace NzbDrone.Core.Organizer
|
||||||
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
|
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
public static readonly Regex ArtistNameRegex = new Regex(@"(?<token>\{(?:Artist)(?<separator>[- ._])(Clean)?Name\})",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
public static readonly Regex AlbumTitleRegex = new Regex(@"(?<token>\{(?:Album)(?<separator>[- ._])(Clean)?Title\})",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);
|
private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);
|
||||||
private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled);
|
private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
@ -140,6 +152,47 @@ namespace NzbDrone.Core.Organizer
|
||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string BuildTrackFileName(List<Track> tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null)
|
||||||
|
{
|
||||||
|
if (namingConfig == null)
|
||||||
|
{
|
||||||
|
namingConfig = _namingConfigService.GetConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!namingConfig.RenameTracks)
|
||||||
|
{
|
||||||
|
return GetOriginalTitle(trackFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namingConfig.StandardTrackFormat.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new NamingFormatException("Standard track format cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
var pattern = namingConfig.StandardTrackFormat;
|
||||||
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
|
|
||||||
|
tracks = tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber).ToList();
|
||||||
|
|
||||||
|
//pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig);
|
||||||
|
|
||||||
|
pattern = FormatTrackNumberTokens(pattern, "", tracks);
|
||||||
|
//pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
|
||||||
|
|
||||||
|
AddArtistTokens(tokenHandlers, artist);
|
||||||
|
AddAlbumTokens(tokenHandlers, album);
|
||||||
|
AddTrackTokens(tokenHandlers, tracks);
|
||||||
|
AddTrackFileTokens(tokenHandlers, trackFile);
|
||||||
|
AddQualityTokens(tokenHandlers, artist, trackFile);
|
||||||
|
//AddMediaInfoTokens(tokenHandlers, trackFile); TODO ReWork MediaInfo for Tracks
|
||||||
|
|
||||||
|
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
||||||
|
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
||||||
|
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
|
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
|
||||||
{
|
{
|
||||||
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
||||||
|
@ -232,6 +285,20 @@ namespace NzbDrone.Core.Organizer
|
||||||
return CleanFolderName(ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers, namingConfig));
|
return CleanFolderName(ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers, namingConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(namingConfig.ArtistFolderFormat, tokenHandlers, namingConfig));
|
||||||
|
}
|
||||||
|
|
||||||
public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null)
|
public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null)
|
||||||
{
|
{
|
||||||
if (namingConfig == null)
|
if (namingConfig == null)
|
||||||
|
@ -247,6 +314,21 @@ namespace NzbDrone.Core.Organizer
|
||||||
return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
|
return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetAlbumFolder(Artist artist, Album album, NamingConfig namingConfig = null)
|
||||||
|
{
|
||||||
|
if (namingConfig == null)
|
||||||
|
{
|
||||||
|
namingConfig = _namingConfigService.GetConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
|
|
||||||
|
AddAlbumTokens(tokenHandlers, album);
|
||||||
|
AddArtistTokens(tokenHandlers, artist);
|
||||||
|
|
||||||
|
return CleanFolderName(ReplaceTokens(namingConfig.AlbumFolderFormat, tokenHandlers, namingConfig));
|
||||||
|
}
|
||||||
|
|
||||||
public static string CleanTitle(string title)
|
public static string CleanTitle(string title)
|
||||||
{
|
{
|
||||||
title = title.Replace("&", "and");
|
title = title.Replace("&", "and");
|
||||||
|
@ -284,8 +366,15 @@ namespace NzbDrone.Core.Organizer
|
||||||
|
|
||||||
private void AddArtistTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Artist artist)
|
private void AddArtistTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Artist artist)
|
||||||
{
|
{
|
||||||
tokenHandlers["{Artist Name}"] = m => artist.ArtistName;
|
tokenHandlers["{Artist Name}"] = m => artist.Name;
|
||||||
tokenHandlers["{Artist CleanTitle}"] = m => CleanTitle(artist.ArtistName);
|
tokenHandlers["{Artist CleanName}"] = m => CleanTitle(artist.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddAlbumTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Album album)
|
||||||
|
{
|
||||||
|
tokenHandlers["{Album Title}"] = m => album.Title;
|
||||||
|
tokenHandlers["{Album CleanTitle}"] = m => CleanTitle(album.Title);
|
||||||
|
tokenHandlers["{Release Year}"] = m => album.ReleaseDate.Year.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
@ -432,6 +521,12 @@ namespace NzbDrone.Core.Organizer
|
||||||
tokenHandlers["{Episode CleanTitle}"] = m => CleanTitle(GetEpisodeTitle(episodes, "and"));
|
tokenHandlers["{Episode CleanTitle}"] = m => CleanTitle(GetEpisodeTitle(episodes, "and"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddTrackTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, List<Track> tracks)
|
||||||
|
{
|
||||||
|
tokenHandlers["{Track Title}"] = m => GetTrackTitle(tracks, "+");
|
||||||
|
tokenHandlers["{Track CleanTitle}"] = m => CleanTitle(GetTrackTitle(tracks, "and"));
|
||||||
|
}
|
||||||
|
|
||||||
private void AddEpisodeFileTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
|
private void AddEpisodeFileTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
|
||||||
{
|
{
|
||||||
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile);
|
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile);
|
||||||
|
@ -439,6 +534,13 @@ namespace NzbDrone.Core.Organizer
|
||||||
tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Lidarr");
|
tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Lidarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddTrackFileTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, TrackFile trackFile)
|
||||||
|
{
|
||||||
|
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(trackFile);
|
||||||
|
tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(trackFile);
|
||||||
|
tokenHandlers["{Release Group}"] = m => trackFile.ReleaseGroup ?? m.DefaultValue("Lidarr");
|
||||||
|
}
|
||||||
|
|
||||||
private void AddQualityTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series, EpisodeFile episodeFile)
|
private void AddQualityTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series, EpisodeFile episodeFile)
|
||||||
{
|
{
|
||||||
var qualityTitle = _qualityDefinitionService.Get(episodeFile.Quality.Quality).Title;
|
var qualityTitle = _qualityDefinitionService.Get(episodeFile.Quality.Quality).Title;
|
||||||
|
@ -451,6 +553,18 @@ namespace NzbDrone.Core.Organizer
|
||||||
tokenHandlers["{Quality Real}"] = m => qualityReal;
|
tokenHandlers["{Quality Real}"] = m => qualityReal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddQualityTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Artist artist, TrackFile trackFile)
|
||||||
|
{
|
||||||
|
var qualityTitle = _qualityDefinitionService.Get(trackFile.Quality.Quality).Title;
|
||||||
|
//var qualityProper = GetQualityProper(artist, trackFile.Quality);
|
||||||
|
//var qualityReal = GetQualityReal(artist, trackFile.Quality);
|
||||||
|
|
||||||
|
tokenHandlers["{Quality Full}"] = m => String.Format("{0}", qualityTitle);
|
||||||
|
tokenHandlers["{Quality Title}"] = m => qualityTitle;
|
||||||
|
//tokenHandlers["{Quality Proper}"] = m => qualityProper;
|
||||||
|
//tokenHandlers["{Quality Real}"] = m => qualityReal;
|
||||||
|
}
|
||||||
|
|
||||||
private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
|
private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
|
||||||
{
|
{
|
||||||
if (episodeFile.MediaInfo == null) return;
|
if (episodeFile.MediaInfo == null) return;
|
||||||
|
@ -646,6 +760,20 @@ namespace NzbDrone.Core.Organizer
|
||||||
return ReplaceSeasonTokens(pattern, episodes.First().SeasonNumber);
|
return ReplaceSeasonTokens(pattern, episodes.First().SeasonNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string FormatTrackNumberTokens(string basePattern, string formatPattern, List<Track> tracks)
|
||||||
|
{
|
||||||
|
var pattern = string.Empty;
|
||||||
|
|
||||||
|
for (int i = 0; i < tracks.Count; i++)
|
||||||
|
{
|
||||||
|
var patternToReplace = i == 0 ? basePattern : formatPattern;
|
||||||
|
|
||||||
|
pattern += TrackRegex.Replace(patternToReplace, match => ReplaceNumberToken(match.Groups["track"].Value, tracks[i].TrackNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
private string FormatAbsoluteNumberTokens(string basePattern, string formatPattern, List<Episode> episodes)
|
private string FormatAbsoluteNumberTokens(string basePattern, string formatPattern, List<Episode> episodes)
|
||||||
{
|
{
|
||||||
var pattern = string.Empty;
|
var pattern = string.Empty;
|
||||||
|
@ -728,6 +856,30 @@ namespace NzbDrone.Core.Organizer
|
||||||
return string.Join(separator, titles);
|
return string.Join(separator, titles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetTrackTitle(List<Track> tracks, string separator)
|
||||||
|
{
|
||||||
|
separator = string.Format(" {0} ", separator.Trim());
|
||||||
|
|
||||||
|
if (tracks.Count == 1)
|
||||||
|
{
|
||||||
|
return tracks.First().Title.TrimEnd(EpisodeTitleTrimCharacters);
|
||||||
|
}
|
||||||
|
|
||||||
|
var titles = tracks.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters))
|
||||||
|
.Select(CleanupEpisodeTitle)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (titles.All(t => t.IsNullOrWhiteSpace()))
|
||||||
|
{
|
||||||
|
titles = tracks.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters))
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(separator, titles);
|
||||||
|
}
|
||||||
|
|
||||||
private string CleanupEpisodeTitle(string title)
|
private string CleanupEpisodeTitle(string title)
|
||||||
{
|
{
|
||||||
//this will remove (1),(2) from the end of multi part episodes.
|
//this will remove (1),(2) from the end of multi part episodes.
|
||||||
|
@ -769,6 +921,16 @@ namespace NzbDrone.Core.Organizer
|
||||||
return episodeFile.SceneName;
|
return episodeFile.SceneName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetOriginalTitle(TrackFile trackFile)
|
||||||
|
{
|
||||||
|
if (trackFile.SceneName.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return GetOriginalFileName(trackFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return trackFile.SceneName;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetOriginalFileName(EpisodeFile episodeFile)
|
private string GetOriginalFileName(EpisodeFile episodeFile)
|
||||||
{
|
{
|
||||||
if (episodeFile.RelativePath.IsNullOrWhiteSpace())
|
if (episodeFile.RelativePath.IsNullOrWhiteSpace())
|
||||||
|
@ -779,35 +941,16 @@ namespace NzbDrone.Core.Organizer
|
||||||
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
|
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
//public string GetArtistFolder(Artist artist, NamingConfig namingConfig = null)
|
private string GetOriginalFileName(TrackFile trackFile)
|
||||||
//{
|
{
|
||||||
// if (namingConfig == null)
|
if (trackFile.RelativePath.IsNullOrWhiteSpace())
|
||||||
// {
|
{
|
||||||
// namingConfig = _namingConfigService.GetConfig();
|
return Path.GetFileNameWithoutExtension(trackFile.Path);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
return Path.GetFileNameWithoutExtension(trackFile.RelativePath);
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
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;
|
||||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Organizer
|
namespace NzbDrone.Core.Organizer
|
||||||
|
@ -9,26 +10,34 @@ namespace NzbDrone.Core.Organizer
|
||||||
public interface IFilenameSampleService
|
public interface IFilenameSampleService
|
||||||
{
|
{
|
||||||
SampleResult GetStandardSample(NamingConfig nameSpec);
|
SampleResult GetStandardSample(NamingConfig nameSpec);
|
||||||
|
SampleResult GetStandardTrackSample(NamingConfig nameSpec);
|
||||||
SampleResult GetMultiEpisodeSample(NamingConfig nameSpec);
|
SampleResult GetMultiEpisodeSample(NamingConfig nameSpec);
|
||||||
SampleResult GetDailySample(NamingConfig nameSpec);
|
SampleResult GetDailySample(NamingConfig nameSpec);
|
||||||
SampleResult GetAnimeSample(NamingConfig nameSpec);
|
SampleResult GetAnimeSample(NamingConfig nameSpec);
|
||||||
SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec);
|
SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec);
|
||||||
string GetSeriesFolderSample(NamingConfig nameSpec);
|
string GetSeriesFolderSample(NamingConfig nameSpec);
|
||||||
string GetSeasonFolderSample(NamingConfig nameSpec);
|
string GetSeasonFolderSample(NamingConfig nameSpec);
|
||||||
|
string GetArtistFolderSample(NamingConfig nameSpec);
|
||||||
|
string GetAlbumFolderSample(NamingConfig nameSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileNameSampleService : IFilenameSampleService
|
public class FileNameSampleService : IFilenameSampleService
|
||||||
{
|
{
|
||||||
private readonly IBuildFileNames _buildFileNames;
|
private readonly IBuildFileNames _buildFileNames;
|
||||||
private static Series _standardSeries;
|
private static Series _standardSeries;
|
||||||
|
private static Artist _standardArtist;
|
||||||
|
private static Album _standardAlbum;
|
||||||
|
private static Track _track1;
|
||||||
private static Series _dailySeries;
|
private static Series _dailySeries;
|
||||||
private static Series _animeSeries;
|
private static Series _animeSeries;
|
||||||
private static Episode _episode1;
|
private static Episode _episode1;
|
||||||
private static Episode _episode2;
|
private static Episode _episode2;
|
||||||
private static Episode _episode3;
|
private static Episode _episode3;
|
||||||
private static List<Episode> _singleEpisode;
|
private static List<Episode> _singleEpisode;
|
||||||
|
private static List<Track> _singleTrack;
|
||||||
private static List<Episode> _multiEpisodes;
|
private static List<Episode> _multiEpisodes;
|
||||||
private static EpisodeFile _singleEpisodeFile;
|
private static EpisodeFile _singleEpisodeFile;
|
||||||
|
private static TrackFile _singleTrackFile;
|
||||||
private static EpisodeFile _multiEpisodeFile;
|
private static EpisodeFile _multiEpisodeFile;
|
||||||
private static EpisodeFile _dailyEpisodeFile;
|
private static EpisodeFile _dailyEpisodeFile;
|
||||||
private static EpisodeFile _animeEpisodeFile;
|
private static EpisodeFile _animeEpisodeFile;
|
||||||
|
@ -44,6 +53,17 @@ namespace NzbDrone.Core.Organizer
|
||||||
Title = "Series Title (2010)"
|
Title = "Series Title (2010)"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_standardArtist = new Artist
|
||||||
|
{
|
||||||
|
Name = "Artist Name"
|
||||||
|
};
|
||||||
|
|
||||||
|
_standardAlbum = new Album
|
||||||
|
{
|
||||||
|
Title = "Album Title",
|
||||||
|
ReleaseDate = System.DateTime.Today
|
||||||
|
};
|
||||||
|
|
||||||
_dailySeries = new Series
|
_dailySeries = new Series
|
||||||
{
|
{
|
||||||
SeriesType = SeriesTypes.Daily,
|
SeriesType = SeriesTypes.Daily,
|
||||||
|
@ -56,6 +76,14 @@ namespace NzbDrone.Core.Organizer
|
||||||
Title = "Series Title (2010)"
|
Title = "Series Title (2010)"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_track1 = new Track
|
||||||
|
{
|
||||||
|
TrackNumber = 3,
|
||||||
|
|
||||||
|
Title = "Track Title (1)",
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
_episode1 = new Episode
|
_episode1 = new Episode
|
||||||
{
|
{
|
||||||
SeasonNumber = 1,
|
SeasonNumber = 1,
|
||||||
|
@ -82,6 +110,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
};
|
};
|
||||||
|
|
||||||
_singleEpisode = new List<Episode> { _episode1 };
|
_singleEpisode = new List<Episode> { _episode1 };
|
||||||
|
_singleTrack = new List<Track> { _track1 };
|
||||||
_multiEpisodes = new List<Episode> { _episode1, _episode2, _episode3 };
|
_multiEpisodes = new List<Episode> { _episode1, _episode2, _episode3 };
|
||||||
|
|
||||||
var mediaInfo = new MediaInfoModel()
|
var mediaInfo = new MediaInfoModel()
|
||||||
|
@ -115,6 +144,15 @@ namespace NzbDrone.Core.Organizer
|
||||||
MediaInfo = mediaInfo
|
MediaInfo = mediaInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_singleTrackFile = new TrackFile
|
||||||
|
{
|
||||||
|
Quality = new QualityModel(Quality.MP3256, new Revision(2)),
|
||||||
|
RelativePath = "Artist.Name.Album.Name.TrackNum.Track.Title.MP3256.mp3",
|
||||||
|
SceneName = "Artist.Name.Album.Name.TrackNum.Track.Title.MP3256",
|
||||||
|
ReleaseGroup = "RlsGrp",
|
||||||
|
MediaInfo = mediaInfo
|
||||||
|
};
|
||||||
|
|
||||||
_multiEpisodeFile = new EpisodeFile
|
_multiEpisodeFile = new EpisodeFile
|
||||||
{
|
{
|
||||||
Quality = new QualityModel(Quality.MP3256, new Revision(2)),
|
Quality = new QualityModel(Quality.MP3256, new Revision(2)),
|
||||||
|
@ -165,6 +203,20 @@ namespace NzbDrone.Core.Organizer
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SampleResult GetStandardTrackSample(NamingConfig nameSpec)
|
||||||
|
{
|
||||||
|
var result = new SampleResult
|
||||||
|
{
|
||||||
|
FileName = BuildTrackSample(_singleTrack, _standardArtist, _standardAlbum, _singleTrackFile, nameSpec),
|
||||||
|
Artist = _standardArtist,
|
||||||
|
Album = _standardAlbum,
|
||||||
|
Tracks = _singleTrack,
|
||||||
|
TrackFile = _singleTrackFile
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public SampleResult GetMultiEpisodeSample(NamingConfig nameSpec)
|
public SampleResult GetMultiEpisodeSample(NamingConfig nameSpec)
|
||||||
{
|
{
|
||||||
var result = new SampleResult
|
var result = new SampleResult
|
||||||
|
@ -227,6 +279,16 @@ namespace NzbDrone.Core.Organizer
|
||||||
return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec);
|
return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetArtistFolderSample(NamingConfig nameSpec)
|
||||||
|
{
|
||||||
|
return _buildFileNames.GetArtistFolder(_standardArtist, nameSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetAlbumFolderSample(NamingConfig nameSpec)
|
||||||
|
{
|
||||||
|
return _buildFileNames.GetAlbumFolder(_standardArtist, _standardAlbum, nameSpec);
|
||||||
|
}
|
||||||
|
|
||||||
private string BuildSample(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec)
|
private string BuildSample(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -238,5 +300,17 @@ namespace NzbDrone.Core.Organizer
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string BuildTrackSample(List<Track> tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig nameSpec)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _buildFileNames.BuildTrackFileName(tracks, artist, album, trackFile, nameSpec);
|
||||||
|
}
|
||||||
|
catch (NamingFormatException)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,12 @@ namespace NzbDrone.Core.Organizer
|
||||||
return ruleBuilder.SetValidator(new ValidStandardEpisodeFormatValidator());
|
return ruleBuilder.SetValidator(new ValidStandardEpisodeFormatValidator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IRuleBuilderOptions<T, string> ValidTrackFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||||
|
{
|
||||||
|
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||||
|
return ruleBuilder.SetValidator(new ValidStandardTrackFormatValidator());
|
||||||
|
}
|
||||||
|
|
||||||
public static IRuleBuilderOptions<T, string> ValidDailyEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
|
public static IRuleBuilderOptions<T, string> ValidDailyEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||||
{
|
{
|
||||||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||||
|
@ -41,6 +47,17 @@ namespace NzbDrone.Core.Organizer
|
||||||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||||
return ruleBuilder.SetValidator(new RegularExpressionValidator(SeasonFolderRegex)).WithMessage("Must contain season number");
|
return ruleBuilder.SetValidator(new RegularExpressionValidator(SeasonFolderRegex)).WithMessage("Must contain season number");
|
||||||
}
|
}
|
||||||
|
public static IRuleBuilderOptions<T, string> ValidArtistFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||||
|
{
|
||||||
|
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||||
|
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.ArtistNameRegex)).WithMessage("Must contain Artist name");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IRuleBuilderOptions<T, string> ValidAlbumFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||||
|
{
|
||||||
|
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||||
|
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.AlbumTitleRegex)).WithMessage("Must contain Album name");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ValidStandardEpisodeFormatValidator : PropertyValidator
|
public class ValidStandardEpisodeFormatValidator : PropertyValidator
|
||||||
|
@ -65,6 +82,21 @@ namespace NzbDrone.Core.Organizer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ValidStandardTrackFormatValidator : PropertyValidator
|
||||||
|
{
|
||||||
|
public ValidStandardTrackFormatValidator()
|
||||||
|
: base("Must contain Album Title and Track numbers OR Original Title")
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsValid(PropertyValidatorContext context)
|
||||||
|
{
|
||||||
|
|
||||||
|
return true; //TODO Add Logic here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class ValidDailyEpisodeFormatValidator : PropertyValidator
|
public class ValidDailyEpisodeFormatValidator : PropertyValidator
|
||||||
{
|
{
|
||||||
public ValidDailyEpisodeFormatValidator()
|
public ValidDailyEpisodeFormatValidator()
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
public interface IFilenameValidationService
|
public interface IFilenameValidationService
|
||||||
{
|
{
|
||||||
ValidationFailure ValidateStandardFilename(SampleResult sampleResult);
|
ValidationFailure ValidateStandardFilename(SampleResult sampleResult);
|
||||||
|
ValidationFailure ValidateTrackFilename(SampleResult sampleResult);
|
||||||
ValidationFailure ValidateDailyFilename(SampleResult sampleResult);
|
ValidationFailure ValidateDailyFilename(SampleResult sampleResult);
|
||||||
ValidationFailure ValidateAnimeFilename(SampleResult sampleResult);
|
ValidationFailure ValidateAnimeFilename(SampleResult sampleResult);
|
||||||
}
|
}
|
||||||
|
@ -35,6 +36,27 @@ namespace NzbDrone.Core.Organizer
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValidationFailure ValidateTrackFilename(SampleResult sampleResult)
|
||||||
|
{
|
||||||
|
var validationFailure = new ValidationFailure("StandardTrackFormat", ERROR_MESSAGE);
|
||||||
|
|
||||||
|
//TODO Add Validation for TrackFilename
|
||||||
|
//var parsedEpisodeInfo = Parser.Parser.ParseTitle(sampleResult.FileName);
|
||||||
|
|
||||||
|
|
||||||
|
//if (parsedEpisodeInfo == null)
|
||||||
|
//{
|
||||||
|
// return validationFailure;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//if (!ValidateSeasonAndEpisodeNumbers(sampleResult.Episodes, parsedEpisodeInfo))
|
||||||
|
//{
|
||||||
|
// return validationFailure;
|
||||||
|
//}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public ValidationFailure ValidateDailyFilename(SampleResult sampleResult)
|
public ValidationFailure ValidateDailyFilename(SampleResult sampleResult)
|
||||||
{
|
{
|
||||||
var validationFailure = new ValidationFailure("DailyEpisodeFormat", ERROR_MESSAGE);
|
var validationFailure = new ValidationFailure("DailyEpisodeFormat", ERROR_MESSAGE);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Organizer
|
namespace NzbDrone.Core.Organizer
|
||||||
{
|
{
|
||||||
|
@ -7,21 +7,25 @@ namespace NzbDrone.Core.Organizer
|
||||||
public static NamingConfig Default => new NamingConfig
|
public static NamingConfig Default => new NamingConfig
|
||||||
{
|
{
|
||||||
RenameEpisodes = false,
|
RenameEpisodes = false,
|
||||||
|
RenameTracks = false,
|
||||||
ReplaceIllegalCharacters = true,
|
ReplaceIllegalCharacters = true,
|
||||||
MultiEpisodeStyle = 0,
|
MultiEpisodeStyle = 0,
|
||||||
StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
|
StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
|
||||||
|
StandardTrackFormat = "{Artist Name} - {track:00} - {Album Title} - {Track Title}",
|
||||||
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}",
|
ArtistFolderFormat = "{Artist Name}",
|
||||||
AlbumFolderFormat = "{Album Name} ({Year})"
|
AlbumFolderFormat = "{Album Title} ({Release Year})"
|
||||||
};
|
};
|
||||||
|
|
||||||
public bool RenameEpisodes { get; set; }
|
public bool RenameEpisodes { get; set; }
|
||||||
|
public bool RenameTracks { get; set; }
|
||||||
public bool ReplaceIllegalCharacters { get; set; }
|
public bool ReplaceIllegalCharacters { get; set; }
|
||||||
public int MultiEpisodeStyle { get; set; }
|
public int MultiEpisodeStyle { get; set; }
|
||||||
public string StandardEpisodeFormat { get; set; }
|
public string StandardEpisodeFormat { get; set; }
|
||||||
|
public string StandardTrackFormat { get; set; }
|
||||||
public string DailyEpisodeFormat { get; set; }
|
public string DailyEpisodeFormat { get; set; }
|
||||||
public string AnimeEpisodeFormat { get; set; }
|
public string AnimeEpisodeFormat { get; set; }
|
||||||
public string SeriesFolderFormat { get; set; }
|
public string SeriesFolderFormat { get; set; }
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Organizer
|
namespace NzbDrone.Core.Organizer
|
||||||
{
|
{
|
||||||
|
@ -8,7 +9,11 @@ namespace NzbDrone.Core.Organizer
|
||||||
{
|
{
|
||||||
public string FileName { get; set; }
|
public string FileName { get; set; }
|
||||||
public Series Series { get; set; }
|
public Series Series { get; set; }
|
||||||
|
public Artist Artist { get; set; }
|
||||||
|
public Album Album { get; set; }
|
||||||
public List<Episode> Episodes { get; set; }
|
public List<Episode> Episodes { get; set; }
|
||||||
public EpisodeFile EpisodeFile { get; set; }
|
public EpisodeFile EpisodeFile { get; set; }
|
||||||
|
public List<Track> Tracks { get; set; }
|
||||||
|
public TrackFile TrackFile { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,20 +19,12 @@ namespace NzbDrone.Core.Parser.Model
|
||||||
public long Size { get; set; }
|
public long Size { get; set; }
|
||||||
public ParsedTrackInfo ParsedTrackInfo { get; set; }
|
public ParsedTrackInfo ParsedTrackInfo { get; set; }
|
||||||
public Artist Artist { get; set; }
|
public Artist Artist { get; set; }
|
||||||
|
public Album Album { get; set; }
|
||||||
public List<Track> Tracks { get; set; }
|
public List<Track> Tracks { get; set; }
|
||||||
public QualityModel Quality { get; set; }
|
public QualityModel Quality { get; set; }
|
||||||
public MediaInfoModel MediaInfo { get; set; }
|
public MediaInfoModel MediaInfo { get; set; }
|
||||||
public bool ExistingFile { get; set; }
|
public bool ExistingFile { get; set; }
|
||||||
|
|
||||||
public string Album
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Tracks.Select(c => c.AlbumId).Distinct().Single();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsSpecial => Album != "";
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|
26
src/NzbDrone.Core/Parser/Model/RemoteAlbum.cs
Normal file
26
src/NzbDrone.Core/Parser/Model/RemoteAlbum.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Parser.Model
|
||||||
|
{
|
||||||
|
public class RemoteAlbum
|
||||||
|
{
|
||||||
|
public ReleaseInfo Release { get; set; }
|
||||||
|
public ParsedTrackInfo ParsedTrackInfo { get; set; }
|
||||||
|
public Artist Artist { get; set; }
|
||||||
|
public List<Album> Albums { get; set; }
|
||||||
|
public bool DownloadAllowed { get; set; }
|
||||||
|
|
||||||
|
public bool IsRecentAlbum()
|
||||||
|
{
|
||||||
|
return Albums.Any(e => e.ReleaseDate >= DateTime.UtcNow.Date.AddDays(-14));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Release.Title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,7 @@ namespace NzbDrone.Core.Validation.Paths
|
||||||
{
|
{
|
||||||
if (context.PropertyValue == null) return true;
|
if (context.PropertyValue == null) return true;
|
||||||
|
|
||||||
return (!_artistService.GetAllArtists().Exists(s => s.SpotifyId == context.PropertyValue.ToString()));
|
return (!_artistService.GetAllArtists().Exists(s => s.ForeignArtistId == context.PropertyValue.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<mapping url="http://localhost:8686/Quality" local-file="$PROJECT_DIR$/Quality" />
|
<mapping url="http://localhost:8686/Quality" local-file="$PROJECT_DIR$/Quality" />
|
||||||
<mapping url="http://localhost:8686/Config.js" local-file="$PROJECT_DIR$/Config.js" />
|
<mapping url="http://localhost:8686/Config.js" local-file="$PROJECT_DIR$/Config.js" />
|
||||||
<mapping url="http://localhost:8686/Shared" local-file="$PROJECT_DIR$/Shared" />
|
<mapping url="http://localhost:8686/Shared" local-file="$PROJECT_DIR$/Shared" />
|
||||||
<mapping url="http://localhost:8686/AddSeries" local-file="$PROJECT_DIR$/AddSeries" />
|
<mapping url="http://localhost:8686/AddArtist" local-file="$PROJECT_DIR$/AddArtist" />
|
||||||
<mapping url="http://localhost:8686/HeaderView.js" local-file="$PROJECT_DIR$/HeaderView.js" />
|
<mapping url="http://localhost:8686/HeaderView.js" local-file="$PROJECT_DIR$/HeaderView.js" />
|
||||||
<mapping url="http://localhost:8686" local-file="$PROJECT_DIR$" />
|
<mapping url="http://localhost:8686" local-file="$PROJECT_DIR$" />
|
||||||
<mapping url="http://localhost:8686/Routing.js" local-file="$PROJECT_DIR$/Routing.js" />
|
<mapping url="http://localhost:8686/Routing.js" local-file="$PROJECT_DIR$/Routing.js" />
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<mapping url="http://localhost:8686/Wanted" local-file="$PROJECT_DIR$/Wanted" />
|
<mapping url="http://localhost:8686/Wanted" local-file="$PROJECT_DIR$/Wanted" />
|
||||||
<mapping url="http://localhost:8686/Config.js" local-file="$PROJECT_DIR$/Config.js" />
|
<mapping url="http://localhost:8686/Config.js" local-file="$PROJECT_DIR$/Config.js" />
|
||||||
<mapping url="http://localhost:8686/Quality" local-file="$PROJECT_DIR$/Quality" />
|
<mapping url="http://localhost:8686/Quality" local-file="$PROJECT_DIR$/Quality" />
|
||||||
<mapping url="http://localhost:8686/AddSeries" local-file="$PROJECT_DIR$/AddSeries" />
|
<mapping url="http://localhost:8686/AddArtist" local-file="$PROJECT_DIR$/AddArtist" />
|
||||||
<mapping url="http://localhost:8686/Shared" local-file="$PROJECT_DIR$/Shared" />
|
<mapping url="http://localhost:8686/Shared" local-file="$PROJECT_DIR$/Shared" />
|
||||||
<mapping url="http://localhost:8686/HeaderView.js" local-file="$PROJECT_DIR$/HeaderView.js" />
|
<mapping url="http://localhost:8686/HeaderView.js" local-file="$PROJECT_DIR$/HeaderView.js" />
|
||||||
<mapping url="http://localhost:8686" local-file="$PROJECT_DIR$" />
|
<mapping url="http://localhost:8686" local-file="$PROJECT_DIR$" />
|
||||||
|
|
23
src/UI/AddArtist/AddArtistCollection.js
Normal file
23
src/UI/AddArtist/AddArtistCollection.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
var Backbone = require('backbone');
|
||||||
|
var ArtistModel = require('../Artist/ArtistModel');
|
||||||
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
module.exports = Backbone.Collection.extend({
|
||||||
|
url : window.NzbDrone.ApiRoot + '/artist/lookup',
|
||||||
|
model : ArtistModel,
|
||||||
|
|
||||||
|
parse : function(response) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
_.each(response, function(model) {
|
||||||
|
model.id = undefined;
|
||||||
|
|
||||||
|
if (self.unmappedFolderModel) {
|
||||||
|
model.path = self.unmappedFolderModel.get('folder').path;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('response: ', response);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
});
|
53
src/UI/AddArtist/AddArtistLayout.js
Normal file
53
src/UI/AddArtist/AddArtistLayout.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
var vent = require('vent');
|
||||||
|
var AppLayout = require('../AppLayout');
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
var RootFolderLayout = require('./RootFolders/RootFolderLayout');
|
||||||
|
var ExistingArtistCollectionView = require('./Existing/AddExistingArtistCollectionView');
|
||||||
|
var AddArtistView = require('./AddArtistView');
|
||||||
|
var ProfileCollection = require('../Profile/ProfileCollection');
|
||||||
|
var RootFolderCollection = require('./RootFolders/RootFolderCollection');
|
||||||
|
require('../Artist/ArtistCollection');
|
||||||
|
|
||||||
|
module.exports = Marionette.Layout.extend({
|
||||||
|
template : 'AddArtist/AddArtistLayoutTemplate',
|
||||||
|
|
||||||
|
regions : {
|
||||||
|
workspace : '#add-artist-workspace'
|
||||||
|
},
|
||||||
|
|
||||||
|
events : {
|
||||||
|
'click .x-import' : '_importArtist',
|
||||||
|
'click .x-add-new' : '_addArtist'
|
||||||
|
},
|
||||||
|
|
||||||
|
attributes : {
|
||||||
|
id : 'add-artist-screen'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize : function() {
|
||||||
|
ProfileCollection.fetch();
|
||||||
|
RootFolderCollection.fetch().done(function() {
|
||||||
|
RootFolderCollection.synced = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow : function() {
|
||||||
|
this.workspace.show(new AddArtistView());
|
||||||
|
},
|
||||||
|
|
||||||
|
_folderSelected : function(options) {
|
||||||
|
vent.trigger(vent.Commands.CloseModalCommand);
|
||||||
|
|
||||||
|
this.workspace.show(new ExistingArtistCollectionView({ model : options.model }));
|
||||||
|
},
|
||||||
|
|
||||||
|
_importArtist : function() {
|
||||||
|
this.rootFolderLayout = new RootFolderLayout();
|
||||||
|
this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected);
|
||||||
|
AppLayout.modalRegion.show(this.rootFolderLayout);
|
||||||
|
},
|
||||||
|
|
||||||
|
_addArtist : function() {
|
||||||
|
this.workspace.show(new AddArtistView());
|
||||||
|
}
|
||||||
|
});
|
17
src/UI/AddArtist/AddArtistLayoutTemplate.hbs
Normal file
17
src/UI/AddArtist/AddArtistLayoutTemplate.hbs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="btn-group add-artist-btn-group btn-group-lg btn-block">
|
||||||
|
<button type="button" class="btn btn-default col-md-10 col-xs-8 add-artist-import-btn x-import">
|
||||||
|
<i class="icon-lidarr-hdd"/>
|
||||||
|
Import existing artists on disk
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-default col-md-2 col-xs-4 x-add-new"><i class="icon-lidarr-active hidden-xs"></i> Add New Artist</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id="add-artist-workspace"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
183
src/UI/AddArtist/AddArtistView.js
Normal file
183
src/UI/AddArtist/AddArtistView.js
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
var vent = require('vent');
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
var AddArtistCollection = require('./AddArtistCollection');
|
||||||
|
var SearchResultCollectionView = require('./SearchResultCollectionView');
|
||||||
|
var EmptyView = require('./EmptyView');
|
||||||
|
var NotFoundView = require('./NotFoundView');
|
||||||
|
var ErrorView = require('./ErrorView');
|
||||||
|
var LoadingView = require('../Shared/LoadingView');
|
||||||
|
|
||||||
|
module.exports = Marionette.Layout.extend({
|
||||||
|
template : 'AddArtist/AddArtistViewTemplate',
|
||||||
|
|
||||||
|
regions : {
|
||||||
|
searchResult : '#search-result'
|
||||||
|
},
|
||||||
|
|
||||||
|
ui : {
|
||||||
|
artistSearch : '.x-artist-search',
|
||||||
|
searchBar : '.x-search-bar',
|
||||||
|
loadMore : '.x-load-more'
|
||||||
|
},
|
||||||
|
|
||||||
|
events : {
|
||||||
|
'click .x-load-more' : '_onLoadMore'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize : function(options) {
|
||||||
|
this.isExisting = options.isExisting;
|
||||||
|
this.collection = new AddArtistCollection();
|
||||||
|
console.log('this.collection:', this.collection);
|
||||||
|
|
||||||
|
if (this.isExisting) {
|
||||||
|
this.collection.unmappedFolderModel = this.model;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isExisting) {
|
||||||
|
this.className = 'existing-artist';
|
||||||
|
} else {
|
||||||
|
this.className = 'new-artist';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.listenTo(vent, vent.Events.ArtistAdded, this._onArtistAdded);
|
||||||
|
this.listenTo(this.collection, 'sync', this._showResults);
|
||||||
|
|
||||||
|
this.resultCollectionView = new SearchResultCollectionView({
|
||||||
|
collection : this.collection,
|
||||||
|
isExisting : this.isExisting
|
||||||
|
});
|
||||||
|
|
||||||
|
this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
onRender : function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.$el.addClass(this.className);
|
||||||
|
|
||||||
|
this.ui.artistSearch.keyup(function(e) {
|
||||||
|
|
||||||
|
if (_.contains([
|
||||||
|
9,
|
||||||
|
16,
|
||||||
|
17,
|
||||||
|
18,
|
||||||
|
19,
|
||||||
|
20,
|
||||||
|
33,
|
||||||
|
34,
|
||||||
|
35,
|
||||||
|
36,
|
||||||
|
37,
|
||||||
|
38,
|
||||||
|
39,
|
||||||
|
40,
|
||||||
|
91,
|
||||||
|
92,
|
||||||
|
93
|
||||||
|
], e.keyCode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self._abortExistingSearch();
|
||||||
|
self.throttledSearch({
|
||||||
|
term : self.ui.artistSearch.val()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this._clearResults();
|
||||||
|
|
||||||
|
if (this.isExisting) {
|
||||||
|
this.ui.searchBar.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow : function() {
|
||||||
|
this.ui.artistSearch.focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
search : function(options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.collection.reset();
|
||||||
|
|
||||||
|
if (!options.term || options.term === this.collection.term) {
|
||||||
|
return Marionette.$.Deferred().resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.searchResult.show(new LoadingView());
|
||||||
|
this.collection.term = options.term;
|
||||||
|
this.currentSearchPromise = this.collection.fetch({
|
||||||
|
data : { term : options.term }
|
||||||
|
});
|
||||||
|
|
||||||
|
this.currentSearchPromise.fail(function() {
|
||||||
|
self._showError();
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.currentSearchPromise;
|
||||||
|
},
|
||||||
|
|
||||||
|
_onArtistAdded : function(options) {
|
||||||
|
if (this.isExisting && options.artist.get('path') === this.model.get('folder').path) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (!this.isExisting) {
|
||||||
|
this.collection.term = '';
|
||||||
|
this.collection.reset();
|
||||||
|
this._clearResults();
|
||||||
|
this.ui.artistSearch.val('');
|
||||||
|
this.ui.artistSearch.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onLoadMore : function() {
|
||||||
|
var showingAll = this.resultCollectionView.showMore();
|
||||||
|
this.ui.searchBar.show();
|
||||||
|
|
||||||
|
if (showingAll) {
|
||||||
|
this.ui.loadMore.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_clearResults : function() {
|
||||||
|
if (!this.isExisting) {
|
||||||
|
this.searchResult.show(new EmptyView());
|
||||||
|
} else {
|
||||||
|
this.searchResult.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_showResults : function() {
|
||||||
|
if (!this.isClosed) {
|
||||||
|
if (this.collection.length === 0) {
|
||||||
|
this.ui.searchBar.show();
|
||||||
|
this.searchResult.show(new NotFoundView({ term : this.collection.term }));
|
||||||
|
} else {
|
||||||
|
this.searchResult.show(this.resultCollectionView);
|
||||||
|
if (!this.showingAll && this.isExisting) {
|
||||||
|
this.ui.loadMore.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_abortExistingSearch : function() {
|
||||||
|
if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) {
|
||||||
|
console.log('aborting previous pending search request.');
|
||||||
|
this.currentSearchPromise.abort();
|
||||||
|
} else {
|
||||||
|
this._clearResults();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_showError : function() {
|
||||||
|
if (!this.isClosed) {
|
||||||
|
this.ui.searchBar.show();
|
||||||
|
this.searchResult.show(new ErrorView({ term : this.collection.term }));
|
||||||
|
this.collection.term = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
24
src/UI/AddArtist/AddArtistViewTemplate.hbs
Normal file
24
src/UI/AddArtist/AddArtistViewTemplate.hbs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{{#if folder.path}}
|
||||||
|
<div class="unmapped-folder-path">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{{folder.path}}
|
||||||
|
</div>
|
||||||
|
</div>{{/if}}
|
||||||
|
<div class="x-search-bar">
|
||||||
|
<div class="input-group input-group-lg add-artist-search">
|
||||||
|
<span class="input-group-addon"><i class="icon-lidarr-search"/></span>
|
||||||
|
|
||||||
|
{{#if folder}}
|
||||||
|
<input type="text" class="form-control x-artist-search" value="{{folder.name}}">
|
||||||
|
{{else}}
|
||||||
|
<input type="text" class="form-control x-artist-search" placeholder="Start typing the name of an artist or album...">
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div id="search-result" class="result-list col-md-12"/>
|
||||||
|
</div>
|
||||||
|
<div class="btn btn-block text-center new-artist-loadmore x-load-more" style="display: none;">
|
||||||
|
<i class="icon-lidarr-load-more"/>
|
||||||
|
more
|
||||||
|
</div>
|
3
src/UI/AddArtist/ArtistTypeSelectionPartial.hbs
Normal file
3
src/UI/AddArtist/ArtistTypeSelectionPartial.hbs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<select class="form-control col-md-2 x-artist-type" name="artistType">
|
||||||
|
<option value="standard">Standard</option>
|
||||||
|
</select>
|
5
src/UI/AddArtist/EmptyView.js
Normal file
5
src/UI/AddArtist/EmptyView.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
|
||||||
|
module.exports = Marionette.CompositeView.extend({
|
||||||
|
template : 'AddArtist/EmptyViewTemplate'
|
||||||
|
});
|
3
src/UI/AddArtist/EmptyViewTemplate.hbs
Normal file
3
src/UI/AddArtist/EmptyViewTemplate.hbs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="text-center hint col-md-12">
|
||||||
|
<span>You can also search by MusicBrianzID using the MBID: prefixes.</span>
|
||||||
|
</div>
|
13
src/UI/AddArtist/ErrorView.js
Normal file
13
src/UI/AddArtist/ErrorView.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
|
||||||
|
module.exports = Marionette.CompositeView.extend({
|
||||||
|
template : 'AddArtist/ErrorViewTemplate',
|
||||||
|
|
||||||
|
initialize : function(options) {
|
||||||
|
this.options = options;
|
||||||
|
},
|
||||||
|
|
||||||
|
templateHelpers : function() {
|
||||||
|
return this.options;
|
||||||
|
}
|
||||||
|
});
|
7
src/UI/AddArtist/ErrorViewTemplate.hbs
Normal file
7
src/UI/AddArtist/ErrorViewTemplate.hbs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="text-center col-md-12">
|
||||||
|
<h3>
|
||||||
|
There was an error searching for '{{term}}'.
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
If the artist name contains non-alphanumeric characters try removing them, otherwise try your search again later.
|
||||||
|
</div>
|
51
src/UI/AddArtist/Existing/AddExistingArtistCollectionView.js
Normal file
51
src/UI/AddArtist/Existing/AddExistingArtistCollectionView.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
var AddArtistView = require('../AddArtistView');
|
||||||
|
var UnmappedFolderCollection = require('./UnmappedFolderCollection');
|
||||||
|
|
||||||
|
module.exports = Marionette.CompositeView.extend({
|
||||||
|
itemView : AddArtistView,
|
||||||
|
itemViewContainer : '.x-loading-folders',
|
||||||
|
template : 'AddArtist/Existing/AddExistingArtistCollectionViewTemplate',
|
||||||
|
|
||||||
|
ui : {
|
||||||
|
loadingFolders : '.x-loading-folders'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize : function() {
|
||||||
|
this.collection = new UnmappedFolderCollection();
|
||||||
|
this.collection.importItems(this.model);
|
||||||
|
},
|
||||||
|
|
||||||
|
showCollection : function() {
|
||||||
|
this._showAndSearch(0);
|
||||||
|
},
|
||||||
|
|
||||||
|
appendHtml : function(collectionView, itemView, index) {
|
||||||
|
collectionView.ui.loadingFolders.before(itemView.el);
|
||||||
|
},
|
||||||
|
|
||||||
|
_showAndSearch : function(index) {
|
||||||
|
var self = this;
|
||||||
|
var model = this.collection.at(index);
|
||||||
|
|
||||||
|
if (model) {
|
||||||
|
var currentIndex = index;
|
||||||
|
var folderName = model.get('folder').name;
|
||||||
|
this.addItemView(model, this.getItemView(), index);
|
||||||
|
this.children.findByModel(model).search({ term : folderName }).always(function() {
|
||||||
|
if (!self.isClosed) {
|
||||||
|
self._showAndSearch(currentIndex + 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
this.ui.loadingFolders.hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
itemViewOptions : {
|
||||||
|
isExisting : true
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="x-existing-folders">
|
||||||
|
<div class="loading-folders x-loading-folders">
|
||||||
|
Loading search results from server for your artists, this may take a few minutes.
|
||||||
|
</div>
|
||||||
|
</div>
|
20
src/UI/AddArtist/Existing/UnmappedFolderCollection.js
Normal file
20
src/UI/AddArtist/Existing/UnmappedFolderCollection.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
var Backbone = require('backbone');
|
||||||
|
var UnmappedFolderModel = require('./UnmappedFolderModel');
|
||||||
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
module.exports = Backbone.Collection.extend({
|
||||||
|
model : UnmappedFolderModel,
|
||||||
|
|
||||||
|
importItems : function(rootFolderModel) {
|
||||||
|
|
||||||
|
this.reset();
|
||||||
|
var rootFolder = rootFolderModel;
|
||||||
|
|
||||||
|
_.each(rootFolderModel.get('unmappedFolders'), function(folder) {
|
||||||
|
this.push(new UnmappedFolderModel({
|
||||||
|
rootFolder : rootFolder,
|
||||||
|
folder : folder
|
||||||
|
}));
|
||||||
|
}, this);
|
||||||
|
}
|
||||||
|
});
|
3
src/UI/AddArtist/Existing/UnmappedFolderModel.js
Normal file
3
src/UI/AddArtist/Existing/UnmappedFolderModel.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
var Backbone = require('backbone');
|
||||||
|
|
||||||
|
module.exports = Backbone.Model.extend({});
|
18
src/UI/AddArtist/MonitoringTooltipTemplate.hbs
Normal file
18
src/UI/AddArtist/MonitoringTooltipTemplate.hbs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<dl class="monitor-tooltip-contents">
|
||||||
|
<dt>All</dt>
|
||||||
|
<dd>Monitor all tracks except specials</dd>
|
||||||
|
<dt>Future</dt>
|
||||||
|
<dd>Monitor tracks that have not been released yet</dd>
|
||||||
|
<dt>Missing</dt>
|
||||||
|
<dd>Monitor tracks that do not have files or have not aired yet</dd>
|
||||||
|
<dt>Existing</dt>
|
||||||
|
<dd>Monitor tracks that have files or have not aired yet</dd>
|
||||||
|
<dt>First Season</dt>
|
||||||
|
<dd>Monitor all tracks of the first album. All other albums will be ignored</dd>
|
||||||
|
<dt>Latest Season</dt>
|
||||||
|
<dd>Monitor all tracks of the latest album and future albums</dd>
|
||||||
|
<dt>None</dt>
|
||||||
|
<dd>No tracks will be monitored.</dd>
|
||||||
|
<!--<dt>Latest Season</dt>-->
|
||||||
|
<!--<dd>Monitor all tracks the latest album only, previous albums will be ignored</dd>-->
|
||||||
|
</dl>
|
13
src/UI/AddArtist/NotFoundView.js
Normal file
13
src/UI/AddArtist/NotFoundView.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
|
||||||
|
module.exports = Marionette.CompositeView.extend({
|
||||||
|
template : 'AddArtist/NotFoundViewTemplate',
|
||||||
|
|
||||||
|
initialize : function(options) {
|
||||||
|
this.options = options;
|
||||||
|
},
|
||||||
|
|
||||||
|
templateHelpers : function() {
|
||||||
|
return this.options;
|
||||||
|
}
|
||||||
|
});
|
7
src/UI/AddArtist/NotFoundViewTemplate.hbs
Normal file
7
src/UI/AddArtist/NotFoundViewTemplate.hbs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<div class="text-center col-md-12">
|
||||||
|
<h3>
|
||||||
|
Sorry. We couldn't find any artist matching '{{term}}'
|
||||||
|
</h3>
|
||||||
|
<a href="https://github.com/mattman86/Lidarr/wiki/FAQ#wiki-why-cant-i-add-a-new-show-to-nzbdrone-its-on-thetvdb">Why can't I find my artist?</a>
|
||||||
|
|
||||||
|
</div>
|
10
src/UI/AddArtist/RootFolders/RootFolderCollection.js
Normal file
10
src/UI/AddArtist/RootFolders/RootFolderCollection.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
var Backbone = require('backbone');
|
||||||
|
var RootFolderModel = require('./RootFolderModel');
|
||||||
|
require('../../Mixins/backbone.signalr.mixin');
|
||||||
|
|
||||||
|
var RootFolderCollection = Backbone.Collection.extend({
|
||||||
|
url : window.NzbDrone.ApiRoot + '/rootfolder',
|
||||||
|
model : RootFolderModel
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = new RootFolderCollection();
|
8
src/UI/AddArtist/RootFolders/RootFolderCollectionView.js
Normal file
8
src/UI/AddArtist/RootFolders/RootFolderCollectionView.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
var RootFolderItemView = require('./RootFolderItemView');
|
||||||
|
|
||||||
|
module.exports = Marionette.CompositeView.extend({
|
||||||
|
template : 'AddArtist/RootFolders/RootFolderCollectionViewTemplate',
|
||||||
|
itemViewContainer : '.x-root-folders',
|
||||||
|
itemView : RootFolderItemView
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col-md-10 ">
|
||||||
|
Path
|
||||||
|
</th>
|
||||||
|
<th class="col-md-3">
|
||||||
|
Free Space
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="x-root-folders"></tbody>
|
||||||
|
</table>
|
28
src/UI/AddArtist/RootFolders/RootFolderItemView.js
Normal file
28
src/UI/AddArtist/RootFolders/RootFolderItemView.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
|
||||||
|
module.exports = Marionette.ItemView.extend({
|
||||||
|
template : 'AddArtist/RootFolders/RootFolderItemViewTemplate',
|
||||||
|
className : 'recent-folder',
|
||||||
|
tagName : 'tr',
|
||||||
|
|
||||||
|
initialize : function() {
|
||||||
|
this.listenTo(this.model, 'change', this.render);
|
||||||
|
},
|
||||||
|
|
||||||
|
events : {
|
||||||
|
'click .x-delete' : 'removeFolder',
|
||||||
|
'click .x-folder' : 'folderSelected'
|
||||||
|
},
|
||||||
|
|
||||||
|
removeFolder : function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.model.destroy().success(function() {
|
||||||
|
self.close();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
folderSelected : function() {
|
||||||
|
this.trigger('folderSelected', this.model);
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
<td class="col-md-10 x-folder folder-path">
|
||||||
|
{{path}}
|
||||||
|
</td>
|
||||||
|
<td class="col-md-3 x-folder folder-free-space">
|
||||||
|
<span>{{Bytes freeSpace}}</span>
|
||||||
|
</td>
|
||||||
|
<td class="col-md-1">
|
||||||
|
<i class="icon-lidarr-delete x-delete"></i>
|
||||||
|
</td>
|
80
src/UI/AddArtist/RootFolders/RootFolderLayout.js
Normal file
80
src/UI/AddArtist/RootFolders/RootFolderLayout.js
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
var RootFolderCollectionView = require('./RootFolderCollectionView');
|
||||||
|
var RootFolderCollection = require('./RootFolderCollection');
|
||||||
|
var RootFolderModel = require('./RootFolderModel');
|
||||||
|
var LoadingView = require('../../Shared/LoadingView');
|
||||||
|
var AsValidatedView = require('../../Mixins/AsValidatedView');
|
||||||
|
require('../../Mixins/FileBrowser');
|
||||||
|
|
||||||
|
var Layout = Marionette.Layout.extend({
|
||||||
|
template : 'AddArtist/RootFolders/RootFolderLayoutTemplate',
|
||||||
|
|
||||||
|
ui : {
|
||||||
|
pathInput : '.x-path'
|
||||||
|
},
|
||||||
|
|
||||||
|
regions : {
|
||||||
|
currentDirs : '#current-dirs'
|
||||||
|
},
|
||||||
|
|
||||||
|
events : {
|
||||||
|
'click .x-add' : '_addFolder',
|
||||||
|
'keydown .x-path input' : '_keydown'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize : function() {
|
||||||
|
this.collection = RootFolderCollection;
|
||||||
|
this.rootfolderListView = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow : function() {
|
||||||
|
this.listenTo(RootFolderCollection, 'sync', this._showCurrentDirs);
|
||||||
|
this.currentDirs.show(new LoadingView());
|
||||||
|
|
||||||
|
if (RootFolderCollection.synced) {
|
||||||
|
this._showCurrentDirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ui.pathInput.fileBrowser();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onFolderSelected : function(options) {
|
||||||
|
this.trigger('folderSelected', options);
|
||||||
|
},
|
||||||
|
|
||||||
|
_addFolder : function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var newDir = new RootFolderModel({
|
||||||
|
Path : this.ui.pathInput.val()
|
||||||
|
});
|
||||||
|
|
||||||
|
this.bindToModelValidation(newDir);
|
||||||
|
|
||||||
|
newDir.save().done(function() {
|
||||||
|
RootFolderCollection.add(newDir);
|
||||||
|
self.trigger('folderSelected', { model : newDir });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_showCurrentDirs : function() {
|
||||||
|
if (!this.rootfolderListView) {
|
||||||
|
this.rootfolderListView = new RootFolderCollectionView({ collection : RootFolderCollection });
|
||||||
|
this.currentDirs.show(this.rootfolderListView);
|
||||||
|
|
||||||
|
this.listenTo(this.rootfolderListView, 'itemview:folderSelected', this._onFolderSelected);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_keydown : function(e) {
|
||||||
|
if (e.keyCode !== 13) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._addFolder();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var Layout = AsValidatedView.apply(Layout);
|
||||||
|
|
||||||
|
module.exports = Layout;
|
36
src/UI/AddArtist/RootFolders/RootFolderLayoutTemplate.hbs
Normal file
36
src/UI/AddArtist/RootFolders/RootFolderLayoutTemplate.hbs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h3>Select Folder</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body root-folders-modal">
|
||||||
|
<div class="validation-errors"></div>
|
||||||
|
<div class="alert alert-info">Enter the path that contains some or all of your Music, you will be able to choose which artist you want to import<button type="button" class="close" data-dismiss="alert">×</button></div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="form-group">
|
||||||
|
|
||||||
|
<div class="col-md-12">
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon"> <i class="icon-lidarr-folder-open"></i></span>
|
||||||
|
<input class="form-control x-path" type="text" validation-name="path" placeholder="Enter path to folder that contains your music">
|
||||||
|
<span class="input-group-btn"><button class="btn btn-success x-add"><i class="icon-lidarr-ok"/></button></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row root-folders">
|
||||||
|
<div class="col-md-12">
|
||||||
|
{{#if items}}
|
||||||
|
<h4>Recent Folders</h4>
|
||||||
|
{{/if}}
|
||||||
|
<div id="current-dirs" class="root-folders-list"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn" data-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
8
src/UI/AddArtist/RootFolders/RootFolderModel.js
Normal file
8
src/UI/AddArtist/RootFolders/RootFolderModel.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
var Backbone = require('backbone');
|
||||||
|
|
||||||
|
module.exports = Backbone.Model.extend({
|
||||||
|
urlRoot : window.NzbDrone.ApiRoot + '/rootfolder',
|
||||||
|
defaults : {
|
||||||
|
freeSpace : 0
|
||||||
|
}
|
||||||
|
});
|
11
src/UI/AddArtist/RootFolders/RootFolderSelectionPartial.hbs
Normal file
11
src/UI/AddArtist/RootFolders/RootFolderSelectionPartial.hbs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<select class="col-md-4 form-control x-root-folder" validation-name="RootFolderPath">
|
||||||
|
{{#if this}}
|
||||||
|
{{#each this}}
|
||||||
|
<option value="{{id}}">{{path}}</option>
|
||||||
|
{{/each}}
|
||||||
|
{{else}}
|
||||||
|
<option value="">Select Path</option>
|
||||||
|
{{/if}}
|
||||||
|
<option value="addNew">Add a different path</option>
|
||||||
|
</select>
|
||||||
|
|
29
src/UI/AddArtist/SearchResultCollectionView.js
Normal file
29
src/UI/AddArtist/SearchResultCollectionView.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
var SearchResultView = require('./SearchResultView');
|
||||||
|
|
||||||
|
module.exports = Marionette.CollectionView.extend({
|
||||||
|
itemView : SearchResultView,
|
||||||
|
|
||||||
|
initialize : function(options) {
|
||||||
|
this.isExisting = options.isExisting;
|
||||||
|
this.showing = 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
showAll : function() {
|
||||||
|
this.showingAll = true;
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
showMore : function() {
|
||||||
|
this.showing += 5;
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
return this.showing >= this.collection.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
appendHtml : function(collectionView, itemView, index) {
|
||||||
|
if (!this.isExisting || index < this.showing || index === 0) {
|
||||||
|
collectionView.$el.append(itemView.el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
297
src/UI/AddArtist/SearchResultView.js
Normal file
297
src/UI/AddArtist/SearchResultView.js
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
var vent = require('vent');
|
||||||
|
var AppLayout = require('../AppLayout');
|
||||||
|
var Backbone = require('backbone');
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
var Profiles = require('../Profile/ProfileCollection');
|
||||||
|
var RootFolders = require('./RootFolders/RootFolderCollection');
|
||||||
|
var RootFolderLayout = require('./RootFolders/RootFolderLayout');
|
||||||
|
var ArtistCollection = require('../Artist/ArtistCollection');
|
||||||
|
var Config = require('../Config');
|
||||||
|
var Messenger = require('../Shared/Messenger');
|
||||||
|
var AsValidatedView = require('../Mixins/AsValidatedView');
|
||||||
|
|
||||||
|
require('jquery.dotdotdot');
|
||||||
|
|
||||||
|
var view = Marionette.ItemView.extend({
|
||||||
|
|
||||||
|
template : 'AddArtist/SearchResultViewTemplate',
|
||||||
|
|
||||||
|
ui : {
|
||||||
|
profile : '.x-profile',
|
||||||
|
rootFolder : '.x-root-folder',
|
||||||
|
albumFolder : '.x-album-folder',
|
||||||
|
artistType : '.x-artist-type',
|
||||||
|
monitor : '.x-monitor',
|
||||||
|
monitorTooltip : '.x-monitor-tooltip',
|
||||||
|
addButton : '.x-add',
|
||||||
|
addAlbumButton : '.x-add-album',
|
||||||
|
addSearchButton : '.x-add-search',
|
||||||
|
addAlbumSearchButton : '.x-add-album-search',
|
||||||
|
overview : '.x-overview'
|
||||||
|
},
|
||||||
|
|
||||||
|
events : {
|
||||||
|
'click .x-add' : '_addWithoutSearch',
|
||||||
|
'click .x-add-album' : '_addWithoutSearch',
|
||||||
|
'click .x-add-search' : '_addAndSearch',
|
||||||
|
'click .x-add-album-search' : '_addAndSearch',
|
||||||
|
'change .x-profile' : '_profileChanged',
|
||||||
|
'change .x-root-folder' : '_rootFolderChanged',
|
||||||
|
'change .x-album-folder' : '_albumFolderChanged',
|
||||||
|
'change .x-artist-type' : '_artistTypeChanged',
|
||||||
|
'change .x-monitor' : '_monitorChanged'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize : function() {
|
||||||
|
|
||||||
|
if (!this.model) {
|
||||||
|
throw 'model is required';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.templateHelpers = {};
|
||||||
|
this._configureTemplateHelpers();
|
||||||
|
|
||||||
|
this.listenTo(vent, Config.Events.ConfigUpdatedEvent, this._onConfigUpdated);
|
||||||
|
this.listenTo(this.model, 'change', this.render);
|
||||||
|
this.listenTo(RootFolders, 'all', this._rootFoldersUpdated);
|
||||||
|
},
|
||||||
|
|
||||||
|
onRender : function() {
|
||||||
|
|
||||||
|
var defaultProfile = Config.getValue(Config.Keys.DefaultProfileId);
|
||||||
|
var defaultRoot = Config.getValue(Config.Keys.DefaultRootFolderId);
|
||||||
|
var useSeasonFolder = Config.getValueBoolean(Config.Keys.UseSeasonFolder, true);
|
||||||
|
var defaultArtistType = Config.getValue(Config.Keys.DefaultSeriesType, 'standard');
|
||||||
|
var defaultMonitorEpisodes = Config.getValue(Config.Keys.MonitorEpisodes, 'missing');
|
||||||
|
|
||||||
|
if (Profiles.get(defaultProfile)) {
|
||||||
|
this.ui.profile.val(defaultProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RootFolders.get(defaultRoot)) {
|
||||||
|
this.ui.rootFolder.val(defaultRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ui.albumFolder.prop('checked', useSeasonFolder);
|
||||||
|
this.ui.artistType.val(defaultArtistType);
|
||||||
|
this.ui.monitor.val(defaultMonitorEpisodes);
|
||||||
|
|
||||||
|
//TODO: make this work via onRender, FM?
|
||||||
|
//works with onShow, but stops working after the first render
|
||||||
|
this.ui.overview.dotdotdot({
|
||||||
|
height : 120
|
||||||
|
});
|
||||||
|
|
||||||
|
this.templateFunction = Marionette.TemplateCache.get('AddArtist/MonitoringTooltipTemplate');
|
||||||
|
var content = this.templateFunction();
|
||||||
|
|
||||||
|
this.ui.monitorTooltip.popover({
|
||||||
|
content : content,
|
||||||
|
html : true,
|
||||||
|
trigger : 'hover',
|
||||||
|
title : 'Track Monitoring Options',
|
||||||
|
placement : 'right',
|
||||||
|
container : this.$el
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_configureTemplateHelpers : function() {
|
||||||
|
var existingArtist = ArtistCollection.where({ foreignArtistId : this.model.get('foreignArtistId') });
|
||||||
|
|
||||||
|
if (existingArtist.length > 0) {
|
||||||
|
this.templateHelpers.existing = existingArtist[0].toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.templateHelpers.profiles = Profiles.toJSON();
|
||||||
|
|
||||||
|
if (!this.model.get('isExisting')) {
|
||||||
|
this.templateHelpers.rootFolders = RootFolders.toJSON();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_onConfigUpdated : function(options) {
|
||||||
|
if (options.key === Config.Keys.DefaultProfileId) {
|
||||||
|
this.ui.profile.val(options.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (options.key === Config.Keys.DefaultRootFolderId) {
|
||||||
|
this.ui.rootFolder.val(options.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (options.key === Config.Keys.UseAlbumFolder) {
|
||||||
|
this.ui.seasonFolder.prop('checked', options.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (options.key === Config.Keys.DefaultArtistType) {
|
||||||
|
this.ui.artistType.val(options.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (options.key === Config.Keys.MonitorEpisodes) {
|
||||||
|
this.ui.monitor.val(options.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_profileChanged : function() {
|
||||||
|
Config.setValue(Config.Keys.DefaultProfileId, this.ui.profile.val());
|
||||||
|
},
|
||||||
|
|
||||||
|
_albumFolderChanged : function() {
|
||||||
|
Config.setValue(Config.Keys.UseAlbumFolder, this.ui.albumFolder.prop('checked'));
|
||||||
|
},
|
||||||
|
|
||||||
|
_rootFolderChanged : function() {
|
||||||
|
var rootFolderValue = this.ui.rootFolder.val();
|
||||||
|
if (rootFolderValue === 'addNew') {
|
||||||
|
var rootFolderLayout = new RootFolderLayout();
|
||||||
|
this.listenToOnce(rootFolderLayout, 'folderSelected', this._setRootFolder);
|
||||||
|
AppLayout.modalRegion.show(rootFolderLayout);
|
||||||
|
} else {
|
||||||
|
Config.setValue(Config.Keys.DefaultRootFolderId, rootFolderValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_artistTypeChanged : function() {
|
||||||
|
Config.setValue(Config.Keys.DefaultArtistType, this.ui.artistType.val());
|
||||||
|
},
|
||||||
|
|
||||||
|
_monitorChanged : function() {
|
||||||
|
Config.setValue(Config.Keys.MonitorEpisodes, this.ui.monitor.val());
|
||||||
|
},
|
||||||
|
|
||||||
|
_setRootFolder : function(options) {
|
||||||
|
vent.trigger(vent.Commands.CloseModalCommand);
|
||||||
|
this.ui.rootFolder.val(options.model.id);
|
||||||
|
this._rootFolderChanged();
|
||||||
|
},
|
||||||
|
|
||||||
|
_addWithoutSearch : function(evt) {
|
||||||
|
console.log(evt);
|
||||||
|
this._addArtist(false);
|
||||||
|
},
|
||||||
|
|
||||||
|
_addAndSearch : function() {
|
||||||
|
this._addArtist(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
_addArtist : function(searchForMissing) {
|
||||||
|
// TODO: Refactor to handle multiple add buttons/albums
|
||||||
|
var addButton = this.ui.addButton;
|
||||||
|
var addSearchButton = this.ui.addSearchButton;
|
||||||
|
console.log('_addArtist, searchForMissing=', searchForMissing);
|
||||||
|
|
||||||
|
addButton.addClass('disabled');
|
||||||
|
addSearchButton.addClass('disabled');
|
||||||
|
|
||||||
|
var profile = this.ui.profile.val();
|
||||||
|
var rootFolderPath = this.ui.rootFolder.children(':selected').text();
|
||||||
|
var artistType = this.ui.artistType.val(); // Perhaps make this a differnitator between artist or Album?
|
||||||
|
var albumFolder = this.ui.albumFolder.prop('checked');
|
||||||
|
|
||||||
|
var options = this._getAddArtistOptions();
|
||||||
|
options.searchForMissing = searchForMissing;
|
||||||
|
|
||||||
|
this.model.set({
|
||||||
|
profileId : profile,
|
||||||
|
rootFolderPath : rootFolderPath,
|
||||||
|
albumFolder : albumFolder,
|
||||||
|
artistType : artistType,
|
||||||
|
addOptions : options,
|
||||||
|
monitored : true
|
||||||
|
}, { silent : true });
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var promise = this.model.save();
|
||||||
|
|
||||||
|
if (searchForMissing) {
|
||||||
|
this.ui.addSearchButton.spinForPromise(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
this.ui.addButton.spinForPromise(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.always(function() {
|
||||||
|
addButton.removeClass('disabled');
|
||||||
|
addSearchButton.removeClass('disabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.done(function() {
|
||||||
|
console.log('[SearchResultView] _addArtist promise resolve:', self.model);
|
||||||
|
ArtistCollection.add(self.model);
|
||||||
|
|
||||||
|
self.close();
|
||||||
|
|
||||||
|
Messenger.show({
|
||||||
|
message : 'Added: ' + self.model.get('name'),
|
||||||
|
actions : {
|
||||||
|
goToArtist : {
|
||||||
|
label : 'Go to Artist',
|
||||||
|
action : function() {
|
||||||
|
Backbone.history.navigate('/artist/' + self.model.get('nameSlug'), { trigger : true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideAfter : 8,
|
||||||
|
hideOnNavigate : true
|
||||||
|
});
|
||||||
|
|
||||||
|
vent.trigger(vent.Events.ArtistAdded, { artist : self.model });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_rootFoldersUpdated : function() {
|
||||||
|
this._configureTemplateHelpers();
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
_getAddArtistOptions : function() {
|
||||||
|
var monitor = this.ui.monitor.val();
|
||||||
|
//[TODO]: Refactor for albums
|
||||||
|
var lastSeason = _.max(this.model.get('seasons'), 'seasonNumber');
|
||||||
|
var firstSeason = _.min(_.reject(this.model.get('seasons'), { seasonNumber : 0 }), 'seasonNumber');
|
||||||
|
|
||||||
|
//this.model.setSeasonPass(firstSeason.seasonNumber); // TODO
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
ignoreTracksWithFiles : false,
|
||||||
|
ignoreTracksWithoutFiles : false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (monitor === 'all') {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (monitor === 'future') {
|
||||||
|
options.ignoreTracksWithFiles = true;
|
||||||
|
options.ignoreTracksWithoutFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*else if (monitor === 'latest') {
|
||||||
|
this.model.setSeasonPass(lastSeason.seasonNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (monitor === 'first') {
|
||||||
|
this.model.setSeasonPass(lastSeason.seasonNumber + 1);
|
||||||
|
this.model.setSeasonMonitored(firstSeason.seasonNumber);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
else if (monitor === 'missing') {
|
||||||
|
options.ignoreTracksWithFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (monitor === 'existing') {
|
||||||
|
options.ignoreTracksWithoutFiles = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*else if (monitor === 'none') {
|
||||||
|
this.model.setSeasonPass(lastSeason.seasonNumber + 1);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AsValidatedView.apply(view);
|
||||||
|
|
||||||
|
module.exports = view;
|
146
src/UI/AddArtist/SearchResultViewTemplate.hbs
Normal file
146
src/UI/AddArtist/SearchResultViewTemplate.hbs
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
<div class="search-item {{#unless isExisting}}search-item-new{{/unless}}">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h2 class="artist-title">
|
||||||
|
<!--{{titleWithYear}}-->
|
||||||
|
{{name}}
|
||||||
|
|
||||||
|
<!--<span class="labels">
|
||||||
|
<span class="label label-default">{{network}}</span>
|
||||||
|
{{#unless_eq status compare="continuing"}}
|
||||||
|
<span class="label label-danger">Ended</span>
|
||||||
|
{{/unless_eq}}
|
||||||
|
</span>-->
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row new-artist-overview x-overview">
|
||||||
|
<div class="col-md-12 overview-internal">
|
||||||
|
{{overview}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{#unless existing}}
|
||||||
|
{{#unless path}}
|
||||||
|
<div class="form-group col-md-4">
|
||||||
|
<label>Path</label>
|
||||||
|
{{> RootFolderSelectionPartial rootFolders}}
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
<div class="form-group col-md-2">
|
||||||
|
<label>Monitor <i class="icon-lidarr-form-info monitor-tooltip x-monitor-tooltip"></i></label>
|
||||||
|
<select class="form-control col-md-2 x-monitor">
|
||||||
|
<option value="all">All</option>
|
||||||
|
<option value="future">Future</option>
|
||||||
|
<option value="missing">Missing</option>
|
||||||
|
<option value="existing">Existing</option>
|
||||||
|
<option value="none">None</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group col-md-2">
|
||||||
|
<label>Profile</label>
|
||||||
|
{{> ProfileSelectionPartial profiles}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--<div class="form-group col-md-2">
|
||||||
|
|
||||||
|
</div>-->
|
||||||
|
|
||||||
|
<div class="form-group col-md-2">
|
||||||
|
<label>Album Folders</label>
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<label class="checkbox toggle well">
|
||||||
|
<input type="checkbox" class="x-season-folder"/>
|
||||||
|
<p>
|
||||||
|
<span>Yes</span>
|
||||||
|
<span>No</span>
|
||||||
|
</p>
|
||||||
|
<div class="btn btn-primary slide-button"/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{#unless existing}}
|
||||||
|
{{#if name}}
|
||||||
|
<div class="form-group col-md-2 col-md-offset-10">
|
||||||
|
<!--Uncomment if we need to add even more controls to add artist-->
|
||||||
|
<!--<label style="visibility: hidden">Add</label>-->
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-success add x-add" title="Add" data-artist="{{name}}">
|
||||||
|
<i class="icon-lidarr-add"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-success add x-add-search" title="Add and Search for missing tracks" data-artist="{{name}}">
|
||||||
|
<i class="icon-lidarr-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="col-md-2 col-md-offset-10">
|
||||||
|
<button class="btn add-artist disabled">
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
<div class="col-md-2 col-md-offset-10">
|
||||||
|
<a class="btn btn-default" href="{{route}}">
|
||||||
|
Already Exists
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{{#each albums}}
|
||||||
|
<div class="col-md-12" style="border:1px dashed black;">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<a href="{{artworkUrl}}" target="_blank">
|
||||||
|
<!-- {{poster}} -->
|
||||||
|
<img class="album-poster" src="{{artworkUrl}}">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h2>{{albumName}} ({{year}})</h2>
|
||||||
|
{{#unless existing}}
|
||||||
|
{{#if albumName}}
|
||||||
|
<div class="form-group col-md-offset-10">
|
||||||
|
<!--Uncomment if we need to add even more controls to add artist-->
|
||||||
|
<!--<label style="visibility: hidden">Add</label>-->
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-success add x-add-album" title="Add" data-album="{{albumName}}">
|
||||||
|
<i class="icon-lidarr-add"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button class="btn btn-success add x-add-album-search" title="Add and Search for missing tracks" data-album="{{albumName}}">
|
||||||
|
<i class="icon-lidarr-search"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="col-md-2 col-md-offset-10">
|
||||||
|
<button class="btn add-artist disabled">
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
<div class="col-md-2 col-md-offset-10">
|
||||||
|
<a class="btn btn-default" href="{{route}}">
|
||||||
|
Already Exists
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
13
src/UI/AddArtist/StartingAlbumSelectionPartial.hbs
Normal file
13
src/UI/AddArtist/StartingAlbumSelectionPartial.hbs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<select class="form-control col-md-2 starting-album x-starting-album">
|
||||||
|
|
||||||
|
|
||||||
|
{{#each this}}
|
||||||
|
{{#if_eq seasonNumber compare="0"}}
|
||||||
|
<option value="{{seasonNumber}}">Specials</option>
|
||||||
|
{{else}}
|
||||||
|
<option value="{{seasonNumber}}">Album {{seasonNumber}}</option>
|
||||||
|
{{/if_eq}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
<option value="5000000">None</option>
|
||||||
|
</select>
|
181
src/UI/AddArtist/addArtist.less
Normal file
181
src/UI/AddArtist/addArtist.less
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
@import "../Shared/Styles/card.less";
|
||||||
|
@import "../Shared/Styles/clickable.less";
|
||||||
|
|
||||||
|
#add-artist-screen {
|
||||||
|
.existing-artist {
|
||||||
|
|
||||||
|
.card();
|
||||||
|
margin : 30px 0px;
|
||||||
|
|
||||||
|
.unmapped-folder-path {
|
||||||
|
padding: 20px;
|
||||||
|
margin-left : 0px;
|
||||||
|
font-weight : 100;
|
||||||
|
font-size : 25px;
|
||||||
|
text-align : center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-artist-loadmore {
|
||||||
|
font-size : 30px;
|
||||||
|
font-weight : 300;
|
||||||
|
padding-top : 10px;
|
||||||
|
padding-bottom : 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-artist {
|
||||||
|
.search-item {
|
||||||
|
.card();
|
||||||
|
margin : 40px 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-artist-search {
|
||||||
|
margin-top : 20px;
|
||||||
|
margin-bottom : 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-item {
|
||||||
|
|
||||||
|
padding-bottom : 20px;
|
||||||
|
|
||||||
|
.artist-title {
|
||||||
|
margin-top : 5px;
|
||||||
|
|
||||||
|
.labels {
|
||||||
|
margin-left : 10px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size : 12px;
|
||||||
|
vertical-align : middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.year {
|
||||||
|
font-style : italic;
|
||||||
|
color : #aaaaaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-artist-overview {
|
||||||
|
overflow : hidden;
|
||||||
|
height : 103px;
|
||||||
|
|
||||||
|
.overview-internal {
|
||||||
|
overflow : hidden;
|
||||||
|
height : 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.artist-poster {
|
||||||
|
min-width : 138px;
|
||||||
|
min-height : 203px;
|
||||||
|
max-width : 138px;
|
||||||
|
max-height : 203px;
|
||||||
|
margin : 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.album-poster {
|
||||||
|
min-width : 100px;
|
||||||
|
min-height : 100px;
|
||||||
|
max-width : 138px;
|
||||||
|
max-height : 203px;
|
||||||
|
margin : 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color : #343434;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration : none;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
font-size : 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
margin-top : 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add {
|
||||||
|
i {
|
||||||
|
&:before {
|
||||||
|
color : #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-tooltip {
|
||||||
|
margin-left : 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-folders {
|
||||||
|
margin : 30px 0px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hint {
|
||||||
|
color : #999999;
|
||||||
|
font-style : italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monitor-tooltip-contents {
|
||||||
|
padding-bottom : 0px;
|
||||||
|
|
||||||
|
dd {
|
||||||
|
padding-bottom : 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.add-new {
|
||||||
|
.clickable;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
padding: 3px 20px;
|
||||||
|
clear: both;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 20px;
|
||||||
|
color: rgb(51, 51, 51);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.add-new:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
background-color: rgb(0, 129, 194);
|
||||||
|
}
|
||||||
|
|
||||||
|
.root-folders-modal {
|
||||||
|
overflow : visible;
|
||||||
|
|
||||||
|
.root-folders-list {
|
||||||
|
overflow-y : auto;
|
||||||
|
max-height : 300px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
.clickable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.validation-errors {
|
||||||
|
display : none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
.form-control {
|
||||||
|
background-color : white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.root-folders {
|
||||||
|
margin-top : 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-folder {
|
||||||
|
.clickable();
|
||||||
|
}
|
||||||
|
}
|
10
src/UI/Artist/AlbumCollection.js
Normal file
10
src/UI/Artist/AlbumCollection.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
var Backbone = require('backbone');
|
||||||
|
var AlbumModel = require('./AlbumModel');
|
||||||
|
|
||||||
|
module.exports = Backbone.Collection.extend({
|
||||||
|
model : AlbumModel,
|
||||||
|
|
||||||
|
comparator : function(season) {
|
||||||
|
return -season.get('seasonNumber');
|
||||||
|
}
|
||||||
|
});
|
11
src/UI/Artist/AlbumModel.js
Normal file
11
src/UI/Artist/AlbumModel.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
var Backbone = require('backbone');
|
||||||
|
|
||||||
|
module.exports = Backbone.Model.extend({
|
||||||
|
defaults : {
|
||||||
|
seasonNumber : 0
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize : function() {
|
||||||
|
this.set('id', this.get('seasonNumber'));
|
||||||
|
}
|
||||||
|
});
|
|
@ -77,7 +77,7 @@ var Collection = PageableCollection.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
artistName: {
|
artistName: {
|
||||||
sortKey : 'artistName'
|
sortKey : 'name'
|
||||||
},
|
},
|
||||||
|
|
||||||
nextAiring : {
|
nextAiring : {
|
||||||
|
@ -119,6 +119,6 @@ Collection = AsFilteredCollection.call(Collection);
|
||||||
Collection = AsSortedCollection.call(Collection);
|
Collection = AsSortedCollection.call(Collection);
|
||||||
Collection = AsPersistedStateCollection.call(Collection);
|
Collection = AsPersistedStateCollection.call(Collection);
|
||||||
|
|
||||||
var data = ApiData.get('series'); // TOOD: Build backend for artist
|
var data = ApiData.get('artist'); // TOOD: Build backend for artist
|
||||||
|
|
||||||
module.exports = new Collection(data, { full : true }).bindSignalR();
|
module.exports = new Collection(data, { full : true }).bindSignalR();
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
var NzbDroneController = require('../Shared/NzbDroneController');
|
var NzbDroneController = require('../Shared/NzbDroneController');
|
||||||
var AppLayout = require('../AppLayout');
|
var AppLayout = require('../AppLayout');
|
||||||
var ArtistCollection = require('./ArtistCollection');
|
var ArtistCollection = require('./ArtistCollection');
|
||||||
var SeriesIndexLayout = require('../Series/Index/SeriesIndexLayout');
|
var ArtistIndexLayout = require('./Index/ArtistIndexLayout');
|
||||||
var SeriesDetailsLayout = require('../Series/Details/SeriesDetailsLayout');
|
var ArtistDetailsLayout = require('./Details/ArtistDetailsLayout');
|
||||||
|
|
||||||
module.exports = NzbDroneController.extend({
|
module.exports = NzbDroneController.extend({
|
||||||
_originalInit : NzbDroneController.prototype.initialize,
|
_originalInit : NzbDroneController.prototype.initialize,
|
||||||
|
@ -17,18 +17,18 @@ module.exports = NzbDroneController.extend({
|
||||||
|
|
||||||
artist : function() {
|
artist : function() {
|
||||||
this.setTitle('Lidarr');
|
this.setTitle('Lidarr');
|
||||||
this.showMainRegion(new SeriesIndexLayout());
|
this.showMainRegion(new ArtistIndexLayout());
|
||||||
},
|
},
|
||||||
|
|
||||||
artistDetails : function(query) {
|
artistDetails : function(query) {
|
||||||
var artists = ArtistCollection.where({ artistNameSlug : query });
|
var artists = ArtistCollection.where({ nameSlug : query });
|
||||||
console.log('artistDetails, artists: ', artists);
|
console.log('artistDetails, artists: ', artists);
|
||||||
if (artists.length !== 0) {
|
if (artists.length !== 0) {
|
||||||
var targetSeries = artists[0];
|
var targetArtist = artists[0];
|
||||||
console.log("[ArtistController] targetSeries: ", targetSeries);
|
console.log("[ArtistController] targetArtist: ", targetArtist);
|
||||||
this.setTitle(targetSeries.get('artistName')); // TODO: Update NzbDroneController
|
this.setTitle(targetArtist.get('name')); // TODO: Update NzbDroneController
|
||||||
//this.setArtistName(targetSeries.get('artistName'));
|
//this.setArtistName(targetSeries.get('artistName'));
|
||||||
this.showMainRegion(new SeriesDetailsLayout({ model : targetSeries }));
|
this.showMainRegion(new ArtistDetailsLayout({ model : targetArtist }));
|
||||||
} else {
|
} else {
|
||||||
this.showNotFound();
|
this.showNotFound();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ module.exports = Backbone.Model.extend({
|
||||||
|
|
||||||
setAlbumsMonitored : function(albumName) {
|
setAlbumsMonitored : function(albumName) {
|
||||||
_.each(this.get('albums'), function(album) {
|
_.each(this.get('albums'), function(album) {
|
||||||
if (season.albumName === albumName) {
|
if (album.albumName === albumName) {
|
||||||
album.monitored = !album.monitored;
|
album.monitored = !album.monitored;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
50
src/UI/Artist/Delete/DeleteArtistTemplate.hbs
Normal file
50
src/UI/Artist/Delete/DeleteArtistTemplate.hbs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h3>Delete {{title}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body delete-artist-modal">
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-3 hidden-xs">
|
||||||
|
{{poster}}
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="form-horizontal">
|
||||||
|
<h3 class="path">{{path}}</h3>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-4 control-label">Delete all files</label>
|
||||||
|
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<div class="input-group">
|
||||||
|
<label class="checkbox toggle well">
|
||||||
|
<input type="checkbox" class="x-delete-files"/>
|
||||||
|
<p>
|
||||||
|
<span>Yes</span>
|
||||||
|
<span>No</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="btn slide-button btn-danger"/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<span class="help-inline-checkbox">
|
||||||
|
<i class="icon-lidarr-form-info" title="Do you want to delete all files from disk?"/>
|
||||||
|
<i class="icon-lidarr-form-warning" title="This option is irreversible, use with extreme caution"/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-offset-1 col-md-5 delete-files-info x-delete-files-info">
|
||||||
|
{{episodeFileCount}} track files will be deleted
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<span class="indicator x-indicator"><i class="icon-lidarr-spinner fa-spin"></i></span>
|
||||||
|
<button class="btn" data-dismiss="modal">Cancel</button>
|
||||||
|
<button class="btn btn-danger x-confirm-delete">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
41
src/UI/Artist/Delete/DeleteArtistView.js
Normal file
41
src/UI/Artist/Delete/DeleteArtistView.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
var vent = require('vent');
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
|
||||||
|
module.exports = Marionette.ItemView.extend({
|
||||||
|
template : 'Series/Delete/DeleteSeriesTemplate',
|
||||||
|
|
||||||
|
events : {
|
||||||
|
'click .x-confirm-delete' : 'removeSeries',
|
||||||
|
'change .x-delete-files' : 'changeDeletedFiles'
|
||||||
|
},
|
||||||
|
|
||||||
|
ui : {
|
||||||
|
deleteFiles : '.x-delete-files',
|
||||||
|
deleteFilesInfo : '.x-delete-files-info',
|
||||||
|
indicator : '.x-indicator'
|
||||||
|
},
|
||||||
|
|
||||||
|
removeSeries : function() {
|
||||||
|
var self = this;
|
||||||
|
var deleteFiles = this.ui.deleteFiles.prop('checked');
|
||||||
|
this.ui.indicator.show();
|
||||||
|
|
||||||
|
this.model.destroy({
|
||||||
|
data : { 'deleteFiles' : deleteFiles },
|
||||||
|
wait : true
|
||||||
|
}).done(function() {
|
||||||
|
vent.trigger(vent.Events.SeriesDeleted, { series : self.model });
|
||||||
|
vent.trigger(vent.Commands.CloseModalCommand);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
changeDeletedFiles : function() {
|
||||||
|
var deleteFiles = this.ui.deleteFiles.prop('checked');
|
||||||
|
|
||||||
|
if (deleteFiles) {
|
||||||
|
this.ui.deleteFilesInfo.show();
|
||||||
|
} else {
|
||||||
|
this.ui.deleteFilesInfo.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue