Merge pull request #7 from lidarr/feature/trackParse

New Album Database design and restabilizing add artist flow with LidarrAPI.Metadata.
This commit is contained in:
Joseph Milazzo 2017-06-21 20:47:15 -05:00 committed by GitHub
commit 2c7398ac66
132 changed files with 3582 additions and 1343 deletions

View file

@ -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);

View file

@ -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
}; };
} }
} }

View file

@ -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; }
} }
} }

View file

@ -11,6 +11,7 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using System;
namespace NzbDrone.Api.EpisodeFiles namespace NzbDrone.Api.EpisodeFiles
{ {
@ -47,24 +48,26 @@ namespace NzbDrone.Api.EpisodeFiles
private EpisodeFileResource GetEpisodeFile(int id) private EpisodeFileResource GetEpisodeFile(int id)
{ {
var episodeFile = _mediaFileService.Get(id); throw new NotImplementedException();
var series = _seriesService.GetSeries(episodeFile.SeriesId); //var episodeFile = _mediaFileService.Get(id);
//var series = _seriesService.GetSeries(episodeFile.SeriesId);
return episodeFile.ToResource(series, _qualityUpgradableSpecification); //return episodeFile.ToResource(series, _qualityUpgradableSpecification);
} }
private List<EpisodeFileResource> GetEpisodeFiles() private List<EpisodeFileResource> GetEpisodeFiles()
{ {
if (!Request.Query.SeriesId.HasValue) throw new NotImplementedException();
{ //if (!Request.Query.SeriesId.HasValue)
throw new BadRequestException("seriesId is missing"); //{
} // throw new BadRequestException("seriesId is missing");
//}
var seriesId = (int)Request.Query.SeriesId; //var seriesId = (int)Request.Query.SeriesId;
var series = _seriesService.GetSeries(seriesId); //var series = _seriesService.GetSeries(seriesId);
return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification)); //return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification));
} }
private void SetQuality(EpisodeFileResource episodeFileResource) private void SetQuality(EpisodeFileResource episodeFileResource)
@ -76,14 +79,15 @@ namespace NzbDrone.Api.EpisodeFiles
private void DeleteEpisodeFile(int id) private void DeleteEpisodeFile(int id)
{ {
var episodeFile = _mediaFileService.Get(id); throw new NotImplementedException();
var series = _seriesService.GetSeries(episodeFile.SeriesId); //var episodeFile = _mediaFileService.Get(id);
var fullPath = Path.Combine(series.Path, episodeFile.RelativePath); //var series = _seriesService.GetSeries(episodeFile.SeriesId);
var subfolder = _diskProvider.GetParentFolder(series.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath)); //var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);
//var subfolder = _diskProvider.GetParentFolder(series.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath));
_logger.Info("Deleting episode file: {0}", fullPath); //_logger.Info("Deleting episode file: {0}", fullPath);
_recycleBinProvider.DeleteFile(fullPath, subfolder); //_recycleBinProvider.DeleteFile(fullPath, subfolder);
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual); //_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual);
} }
public void Handle(EpisodeFileAddedEvent message) public void Handle(EpisodeFileAddedEvent message)

View file

@ -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
}; };
} }

View file

@ -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();
} }
@ -144,14 +144,14 @@ namespace NzbDrone.Api.Music
public void Handle(TrackImportedEvent message) public void Handle(TrackImportedEvent message)
{ {
BroadcastResourceChange(ModelAction.Updated, message.ImportedTrack.ItunesTrackId); BroadcastResourceChange(ModelAction.Updated, message.ImportedTrack.Id); // TODO: Ensure we can pass DB ID instead of Metadata ID (SpotifyID)
} }
public void Handle(TrackFileDeletedEvent message) public void Handle(TrackFileDeletedEvent message)
{ {
if (message.Reason == DeleteMediaFileReason.Upgrade) return; if (message.Reason == DeleteMediaFileReason.Upgrade) return;
BroadcastResourceChange(ModelAction.Updated, message.TrackFile.ItunesTrackId); BroadcastResourceChange(ModelAction.Updated, message.TrackFile.Id); // TODO: Ensure we can pass DB ID instead of Metadata ID (SpotifyID)
} }
public void Handle(ArtistUpdatedEvent message) public void Handle(ArtistUpdatedEvent message)

View file

@ -18,8 +18,12 @@ 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 MBId { get; set; }
public int TADBId { get; set; }
public int DiscogsId { get; set; }
public string AMId { get; set; }
public string Overview { get; set; } public string Overview { get; set; }
public int AlbumCount public int AlbumCount
@ -53,13 +57,13 @@ namespace NzbDrone.Api.Music
public bool Monitored { get; set; } public bool Monitored { get; set; }
public string RootFolderPath { get; set; } public string RootFolderPath { get; set; }
public string Certification { get; set; } //public string Certification { get; set; }
public List<string> Genres { get; set; } public List<string> Genres { get; set; }
public HashSet<int> Tags { get; set; } public HashSet<int> Tags { get; set; }
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
@ -71,8 +75,11 @@ namespace NzbDrone.Api.Music
return new ArtistResource return new ArtistResource
{ {
Id = model.Id, Id = model.Id,
MBId = model.MBId,
ArtistName = model.ArtistName, TADBId = model.TADBId,
DiscogsId = model.DiscogsId,
AMId = model.AMId,
Name = model.Name,
//AlternateTitles //AlternateTitles
//SortTitle = resource.SortTitle, //SortTitle = resource.SortTitle,
@ -94,7 +101,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 +111,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,10 +131,13 @@ namespace NzbDrone.Api.Music
{ {
Id = resource.Id, Id = resource.Id,
ArtistName = resource.ArtistName, Name = resource.Name,
//AlternateTitles //AlternateTitles
//SortTitle = resource.SortTitle, //SortTitle = resource.SortTitle,
MBId = resource.MBId,
TADBId = resource.TADBId,
DiscogsId = resource.DiscogsId,
AMId = resource.AMId,
//TotalEpisodeCount //TotalEpisodeCount
//EpisodeCount //EpisodeCount
//EpisodeFileCount //EpisodeFileCount
@ -147,11 +156,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,

View file

@ -104,6 +104,13 @@
<Compile Include="ClientSchema\SelectOption.cs" /> <Compile Include="ClientSchema\SelectOption.cs" />
<Compile Include="Commands\CommandModule.cs" /> <Compile Include="Commands\CommandModule.cs" />
<Compile Include="Commands\CommandResource.cs" /> <Compile Include="Commands\CommandResource.cs" />
<Compile Include="TrackFiles\TrackFileModule.cs" />
<Compile Include="TrackFiles\TrackFileResource.cs" />
<Compile Include="Tracks\TrackModule.cs" />
<Compile Include="Tracks\TrackModuleWithSignalR.cs" />
<Compile Include="Tracks\TrackResource.cs" />
<Compile Include="Tracks\RenameTrackModule.cs" />
<Compile Include="Tracks\RenameTrackResource.cs" />
<Compile Include="Extensions\AccessControlHeaders.cs" /> <Compile Include="Extensions\AccessControlHeaders.cs" />
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" /> <Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
<Compile Include="Extensions\Pipelines\RequestLoggingPipeline.cs" /> <Compile Include="Extensions\Pipelines\RequestLoggingPipeline.cs" />

View file

@ -0,0 +1,101 @@
using System.Collections.Generic;
using System.IO;
using NLog;
using NzbDrone.Api.REST;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.SignalR;
using System;
namespace NzbDrone.Api.TrackFiles
{
public class TrackFileModule : NzbDroneRestModuleWithSignalR<TrackFileResource, TrackFile>,
IHandle<TrackFileAddedEvent>
{
private readonly IMediaFileService _mediaFileService;
private readonly IDiskProvider _diskProvider;
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly ISeriesService _seriesService;
private readonly IArtistService _artistService;
private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
private readonly Logger _logger;
public TrackFileModule(IBroadcastSignalRMessage signalRBroadcaster,
IMediaFileService mediaFileService,
IDiskProvider diskProvider,
IRecycleBinProvider recycleBinProvider,
ISeriesService seriesService,
IArtistService artistService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
Logger logger)
: base(signalRBroadcaster)
{
_mediaFileService = mediaFileService;
_diskProvider = diskProvider;
_recycleBinProvider = recycleBinProvider;
_seriesService = seriesService;
_artistService = artistService;
_qualityUpgradableSpecification = qualityUpgradableSpecification;
_logger = logger;
GetResourceById = GetTrackFile;
GetResourceAll = GetTrackFiles;
UpdateResource = SetQuality;
DeleteResource = DeleteTrackFile;
}
private TrackFileResource GetTrackFile(int id)
{
throw new NotImplementedException();
//var episodeFile = _mediaFileService.Get(id);
//var series = _seriesService.GetSeries(episodeFile.SeriesId);
//return episodeFile.ToResource(series, _qualityUpgradableSpecification);
}
private List<TrackFileResource> GetTrackFiles()
{
if (!Request.Query.ArtistId.HasValue)
{
throw new BadRequestException("artistId is missing");
}
var artistId = (int)Request.Query.ArtistId;
var artist = _artistService.GetArtist(artistId);
return _mediaFileService.GetFilesByArtist(artistId).ConvertAll(f => f.ToResource(artist, _qualityUpgradableSpecification));
}
private void SetQuality(TrackFileResource trackFileResource)
{
var trackFile = _mediaFileService.Get(trackFileResource.Id);
trackFile.Quality = trackFileResource.Quality;
_mediaFileService.Update(trackFile);
}
private void DeleteTrackFile(int id)
{
throw new NotImplementedException();
//var episodeFile = _mediaFileService.Get(id);
//var series = _seriesService.GetSeries(episodeFile.SeriesId);
//var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);
//var subfolder = _diskProvider.GetParentFolder(series.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath));
//_logger.Info("Deleting episode file: {0}", fullPath);
//_recycleBinProvider.DeleteFile(fullPath, subfolder);
//_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual);
}
public void Handle(TrackFileAddedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.TrackFile.Id);
}
}
}

View file

@ -0,0 +1,64 @@
using System;
using System.IO;
using NzbDrone.Api.REST;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.TrackFiles
{
public class TrackFileResource : RestResource
{
public int ArtistId { get; set; }
public int AlbumId { get; set; }
public string RelativePath { get; set; }
public string Path { get; set; }
public long Size { get; set; }
public DateTime DateAdded { get; set; }
//public string SceneName { get; set; }
public QualityModel Quality { get; set; }
public bool QualityCutoffNotMet { get; set; }
}
public static class TrackFileResourceMapper
{
private static TrackFileResource ToResource(this Core.MediaFiles.TrackFile model)
{
if (model == null) return null;
return new TrackFileResource
{
Id = model.Id,
ArtistId = model.ArtistId,
AlbumId = model.AlbumId,
RelativePath = model.RelativePath,
//Path
Size = model.Size,
DateAdded = model.DateAdded,
//SceneName = model.SceneName,
Quality = model.Quality,
//QualityCutoffNotMet
};
}
public static TrackFileResource ToResource(this Core.MediaFiles.TrackFile model, Core.Music.Artist artist, Core.DecisionEngine.IQualityUpgradableSpecification qualityUpgradableSpecification)
{
if (model == null) return null;
return new TrackFileResource
{
Id = model.Id,
ArtistId = model.ArtistId,
AlbumId = model.AlbumId,
RelativePath = model.RelativePath,
Path = Path.Combine(artist.Path, model.RelativePath),
Size = model.Size,
DateAdded = model.DateAdded,
//SceneName = model.SceneName,
Quality = model.Quality,
QualityCutoffNotMet = qualityUpgradableSpecification.CutoffNotMet(artist.Profile.Value, model.Quality)
};
}
}
}

View file

@ -0,0 +1,37 @@
using System.Collections.Generic;
using NzbDrone.Api.REST;
using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Api.Tracks
{
public class RenameTrackModule : NzbDroneRestModule<RenameTrackResource>
{
private readonly IRenameTrackFileService _renameTrackFileService;
public RenameTrackModule(IRenameTrackFileService renameTrackFileService)
: base("rename")
{
_renameTrackFileService = renameTrackFileService;
GetResourceAll = GetTracks;
}
private List<RenameTrackResource> GetTracks()
{
if (!Request.Query.ArtistId.HasValue)
{
throw new BadRequestException("artistId is missing");
}
var artistId = (int)Request.Query.ArtistId;
if (Request.Query.AlbumId.HasValue)
{
var albumId = (int)Request.Query.AlbumId;
return _renameTrackFileService.GetRenamePreviews(artistId, albumId).ToResource();
}
return _renameTrackFileService.GetRenamePreviews(artistId).ToResource();
}
}
}

View file

@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Tracks
{
public class RenameTrackResource : RestResource
{
public int ArtistId { get; set; }
public int AlbumId { get; set; }
public List<int> TrackNumbers { get; set; }
public int TrackFileId { get; set; }
public string ExistingPath { get; set; }
public string NewPath { get; set; }
}
public static class RenameTrackResourceMapper
{
public static RenameTrackResource ToResource(this Core.MediaFiles.RenameTrackFilePreview model)
{
if (model == null) return null;
return new RenameTrackResource
{
ArtistId = model.ArtistId,
AlbumId = model.AlbumId,
TrackNumbers = model.TrackNumbers.ToList(),
TrackFileId = model.TrackFileId,
ExistingPath = model.ExistingPath,
NewPath = model.NewPath
};
}
public static List<RenameTrackResource> ToResource(this IEnumerable<Core.MediaFiles.RenameTrackFilePreview> models)
{
return models.Select(ToResource).ToList();
}
}
}

View file

@ -0,0 +1,40 @@
using System.Collections.Generic;
using NzbDrone.Api.REST;
using NzbDrone.Core.Music;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.SignalR;
namespace NzbDrone.Api.Tracks
{
public class TrackModule : TrackModuleWithSignalR
{
public TrackModule(IArtistService artistService,
ITrackService trackService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster)
: base(trackService, artistService, qualityUpgradableSpecification, signalRBroadcaster)
{
GetResourceAll = GetTracks;
UpdateResource = SetMonitored;
}
private List<TrackResource> GetTracks()
{
if (!Request.Query.ArtistId.HasValue)
{
throw new BadRequestException("artistId is missing");
}
var artistId = (int)Request.Query.ArtistId;
var resources = MapToResource(_trackService.GetTracksByArtist(artistId), false, true);
return resources;
}
private void SetMonitored(TrackResource trackResource)
{
_trackService.SetTrackMonitored(trackResource.Id, trackResource.Monitored);
}
}
}

View file

@ -0,0 +1,126 @@
using System.Collections.Generic;
using NzbDrone.Common.Extensions;
using NzbDrone.Api.TrackFiles;
using NzbDrone.Api.Music;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
using NzbDrone.SignalR;
namespace NzbDrone.Api.Tracks
{
public abstract class TrackModuleWithSignalR : NzbDroneRestModuleWithSignalR<TrackResource, Track>,
IHandle<TrackDownloadedEvent>
{
protected readonly ITrackService _trackService;
protected readonly IArtistService _artistService;
protected readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
protected TrackModuleWithSignalR(ITrackService trackService,
IArtistService artistService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster)
: base(signalRBroadcaster)
{
_trackService = trackService;
_artistService = artistService;
_qualityUpgradableSpecification = qualityUpgradableSpecification;
GetResourceById = GetTrack;
}
protected TrackModuleWithSignalR(ITrackService trackService,
IArtistService artistService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
IBroadcastSignalRMessage signalRBroadcaster,
string resource)
: base(signalRBroadcaster, resource)
{
_trackService = trackService;
_artistService = artistService;
_qualityUpgradableSpecification = qualityUpgradableSpecification;
GetResourceById = GetTrack;
}
protected TrackResource GetTrack(int id)
{
var track = _trackService.GetTrack(id);
var resource = MapToResource(track, true, true);
return resource;
}
protected TrackResource MapToResource(Track track, bool includeArtist, bool includeTrackFile)
{
var resource = track.ToResource();
if (includeArtist || includeTrackFile)
{
var artist = track.Artist ?? _artistService.GetArtist(track.ArtistId);
if (includeArtist)
{
resource.Artist = artist.ToResource();
}
if (includeTrackFile && track.TrackFileId != 0)
{
resource.TrackFile = track.TrackFile.Value.ToResource(artist, _qualityUpgradableSpecification);
}
}
return resource;
}
protected List<TrackResource> MapToResource(List<Track> tracks, bool includeArtist, bool includeTrackFile)
{
var result = tracks.ToResource();
if (includeArtist || includeTrackFile)
{
var artistDict = new Dictionary<int, Core.Music.Artist>();
for (var i = 0; i < tracks.Count; i++)
{
var track = tracks[i];
var resource = result[i];
var artist = track.Artist ?? artistDict.GetValueOrDefault(tracks[i].ArtistId) ?? _artistService.GetArtist(tracks[i].ArtistId);
artistDict[artist.Id] = artist;
if (includeArtist)
{
resource.Artist = artist.ToResource();
}
if (includeTrackFile && tracks[i].TrackFileId != 0)
{
resource.TrackFile = tracks[i].TrackFile.Value.ToResource(artist, _qualityUpgradableSpecification);
}
}
}
return result;
}
//public void Handle(TrackGrabbedEvent message)
//{
// foreach (var track in message.Track.Tracks)
// {
// var resource = track.ToResource();
// resource.Grabbed = true;
// BroadcastResourceChange(ModelAction.Updated, resource);
// }
//}
public void Handle(TrackDownloadedEvent message)
{
foreach (var track in message.Track.Tracks)
{
BroadcastResourceChange(ModelAction.Updated, track.Id);
}
}
}
}

View file

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using NzbDrone.Api.TrackFiles;
using NzbDrone.Api.REST;
using NzbDrone.Api.Music;
using NzbDrone.Core.Music;
namespace NzbDrone.Api.Tracks
{
public class TrackResource : RestResource
{
public int ArtistId { get; set; }
public int TrackFileId { get; set; }
public int AlbumId { get; set; }
//public int EpisodeNumber { get; set; }
public string Title { get; set; }
//public string AirDate { get; set; }
//public DateTime? AirDateUtc { get; set; }
//public string Overview { get; set; }
public TrackFileResource TrackFile { get; set; }
public bool HasFile { get; set; }
public bool Monitored { get; set; }
//public int? AbsoluteEpisodeNumber { get; set; }
//public int? SceneAbsoluteEpisodeNumber { get; set; }
//public int? SceneEpisodeNumber { get; set; }
//public int? SceneSeasonNumber { get; set; }
//public bool UnverifiedSceneNumbering { get; set; }
//public string SeriesTitle { get; set; }
public ArtistResource Artist { get; set; }
//Hiding this so people don't think its usable (only used to set the initial state)
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public bool Grabbed { get; set; }
}
public static class TrackResourceMapper
{
public static TrackResource ToResource(this Track model)
{
if (model == null) return null;
return new TrackResource
{
Id = model.Id,
ArtistId = model.ArtistId,
TrackFileId = model.TrackFileId,
AlbumId = model.AlbumId,
//EpisodeNumber = model.EpisodeNumber,
Title = model.Title,
//AirDate = model.AirDate,
//AirDateUtc = model.AirDateUtc,
//Overview = model.Overview,
//EpisodeFile
HasFile = model.HasFile,
Monitored = model.Monitored,
//AbsoluteEpisodeNumber = model.AbsoluteEpisodeNumber,
//SceneAbsoluteEpisodeNumber = model.SceneAbsoluteEpisodeNumber,
//SceneEpisodeNumber = model.SceneEpisodeNumber,
//SceneSeasonNumber = model.SceneSeasonNumber,
//UnverifiedSceneNumbering = model.UnverifiedSceneNumbering,
//SeriesTitle = model.SeriesTitle,
//Series = model.Series.MapToResource(),
};
}
public static List<TrackResource> ToResource(this IEnumerable<Track> models)
{
if (models == null) return null;
return models.Select(ToResource).ToList();
}
}
}

View file

@ -17,14 +17,9 @@ 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("http://localhost:3000/{route}/") // TODO: Add {version} once LidarrAPI.Metadata is released.
// .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
.CreateFactory();
SkyHookTvdb = new HttpRequestBuilder("http://skyhook.lidarr.tv/v1/tvdb/{route}/{language}/") SkyHookTvdb = new HttpRequestBuilder("http://skyhook.lidarr.tv/v1/tvdb/{route}/{language}/")
.SetSegment("language", "en") .SetSegment("language", "en")

View file

@ -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();
} }
} }

View file

@ -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();
}
}
}

View file

@ -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()

View 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;
}
}
}

View file

@ -13,6 +13,7 @@ using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.Download namespace NzbDrone.Core.Download
{ {
@ -130,7 +131,7 @@ namespace NzbDrone.Core.Download
{ {
var statusMessages = importResults var statusMessages = importResults
.Where(v => v.Result != ImportResultType.Imported) .Where(v => v.Result != ImportResultType.Imported)
.Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), v.Errors)) .Select(v => new TrackedDownloadStatusMessage(Path.GetFileName(v.ImportDecision.LocalTrack.Path), v.Errors))
.ToArray(); .ToArray();
trackedDownload.Warn(statusMessages); trackedDownload.Warn(statusMessages);

View file

@ -16,6 +16,7 @@ using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Extras namespace NzbDrone.Core.Extras
{ {
// NOTE: Majora: ExtraService can be reserved for Music Videos, lyric files, etc for Plex. TODO: Implement Extras for Music
public interface IExtraService public interface IExtraService
{ {
void ImportExtraFiles(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly); void ImportExtraFiles(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly);
@ -136,16 +137,17 @@ namespace NzbDrone.Core.Extras
private List<EpisodeFile> GetEpisodeFiles(int seriesId) private List<EpisodeFile> GetEpisodeFiles(int seriesId)
{ {
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId); //var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
var episodes = _episodeService.GetEpisodeBySeries(seriesId); //var episodes = _episodeService.GetEpisodeBySeries(seriesId);
foreach (var episodeFile in episodeFiles) //foreach (var episodeFile in episodeFiles)
{ //{
var localEpisodeFile = episodeFile; // var localEpisodeFile = episodeFile;
episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id)); // episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
} //}
return episodeFiles; //return episodeFiles;
return new List<EpisodeFile>();
} }
} }
} }

View file

@ -4,6 +4,7 @@ using System.Text.RegularExpressions;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.IndexerSearch.Definitions namespace NzbDrone.Core.IndexerSearch.Definitions
{ {
@ -19,6 +20,9 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public virtual bool MonitoredEpisodesOnly { get; set; } public virtual bool MonitoredEpisodesOnly { get; set; }
public virtual bool UserInvokedSearch { get; set; } public virtual bool UserInvokedSearch { get; set; }
public Artist Artist { get; set; }
public List<Track> Tracks { get; set; }
public List<string> QueryTitles => SceneTitles.Select(GetQueryTitle).ToList(); public List<string> QueryTitles => SceneTitles.Select(GetQueryTitle).ToList();
public static string GetQueryTitle(string title) public static string GetQueryTitle(string title)

View file

@ -1,4 +1,5 @@
using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.MediaFiles.TrackImport;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.MediaFiles.Commands namespace NzbDrone.Core.MediaFiles.Commands

View file

@ -0,0 +1,16 @@
using System.Collections.Generic;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.MediaFiles.Commands
{
public class RenameArtistCommand : Command
{
public List<int> ArtistIds { get; set; }
public override bool SendUpdatesToClient => true;
public RenameArtistCommand()
{
}
}
}

View file

@ -0,0 +1,26 @@
using NzbDrone.Core.Messaging.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.Events
{
public class RescanArtistCommand : Command
{
public string ArtistId { get; set; }
public override bool SendUpdatesToClient => true;
public RescanArtistCommand()
{
ArtistId = "";
}
public RescanArtistCommand(string artistId)
{
ArtistId = artistId;
}
}
}

View file

@ -16,12 +16,15 @@ using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events; using NzbDrone.Core.Tv.Events;
using NzbDrone.Core.Music;
using NzbDrone.Core.Music.Events;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
{ {
public interface IDiskScanService public interface IDiskScanService
{ {
void Scan(Series series); void Scan(Artist artist);
string[] GetVideoFiles(string path, bool allDirectories = true); string[] GetVideoFiles(string path, bool allDirectories = true);
string[] GetNonVideoFiles(string path, bool allDirectories = true); string[] GetNonVideoFiles(string path, bool allDirectories = true);
List<string> FilterFiles(Series series, IEnumerable<string> files); List<string> FilterFiles(Series series, IEnumerable<string> files);
@ -29,32 +32,35 @@ namespace NzbDrone.Core.MediaFiles
public class DiskScanService : public class DiskScanService :
IDiskScanService, IDiskScanService,
IHandle<SeriesUpdatedEvent>, IHandle<ArtistUpdatedEvent>,
IExecute<RescanSeriesCommand> IExecute<RescanArtistCommand>
{ {
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IMakeImportDecision _importDecisionMaker; private readonly IMakeImportDecision _importDecisionMaker;
private readonly IImportApprovedEpisodes _importApprovedEpisodes; private readonly IImportApprovedTracks _importApprovedTracks;
private readonly IConfigService _configService; private readonly IConfigService _configService;
private readonly ISeriesService _seriesService; private readonly ISeriesService _seriesService;
private readonly IArtistService _artistService;
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService; private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService;
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger; private readonly Logger _logger;
public DiskScanService(IDiskProvider diskProvider, public DiskScanService(IDiskProvider diskProvider,
IMakeImportDecision importDecisionMaker, IMakeImportDecision importDecisionMaker,
IImportApprovedEpisodes importApprovedEpisodes, IImportApprovedTracks importApprovedTracks,
IConfigService configService, IConfigService configService,
ISeriesService seriesService, ISeriesService seriesService,
IArtistService artistService,
IMediaFileTableCleanupService mediaFileTableCleanupService, IMediaFileTableCleanupService mediaFileTableCleanupService,
IEventAggregator eventAggregator, IEventAggregator eventAggregator,
Logger logger) Logger logger)
{ {
_diskProvider = diskProvider; _diskProvider = diskProvider;
_importDecisionMaker = importDecisionMaker; _importDecisionMaker = importDecisionMaker;
_importApprovedEpisodes = importApprovedEpisodes; _importApprovedTracks = importApprovedTracks;
_configService = configService; _configService = configService;
_seriesService = seriesService; _seriesService = seriesService;
_artistService = artistService;
_mediaFileTableCleanupService = mediaFileTableCleanupService; _mediaFileTableCleanupService = mediaFileTableCleanupService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_logger = logger; _logger = logger;
@ -63,69 +69,75 @@ namespace NzbDrone.Core.MediaFiles
private static readonly Regex ExcludedSubFoldersRegex = new Regex(@"(?:\\|\/|^)(extras|@eadir|extrafanart|plex\sversions|\..+)(?:\\|\/)", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex ExcludedSubFoldersRegex = new Regex(@"(?:\\|\/|^)(extras|@eadir|extrafanart|plex\sversions|\..+)(?:\\|\/)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ExcludedFilesRegex = new Regex(@"^\._|Thumbs\.db", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex ExcludedFilesRegex = new Regex(@"^\._|Thumbs\.db", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public void Scan(Series series) public void Scan(Artist artist)
{ {
var rootFolder = _diskProvider.GetParentFolder(series.Path); var rootFolder = _diskProvider.GetParentFolder(artist.Path);
if (!_diskProvider.FolderExists(rootFolder)) if (!_diskProvider.FolderExists(rootFolder))
{ {
_logger.Warn("Series' root folder ({0}) doesn't exist.", rootFolder); _logger.Warn("Artist' root folder ({0}) doesn't exist.", rootFolder);
_eventAggregator.PublishEvent(new SeriesScanSkippedEvent(series, SeriesScanSkippedReason.RootFolderDoesNotExist)); _eventAggregator.PublishEvent(new ArtistScanSkippedEvent(artist, ArtistScanSkippedReason.RootFolderDoesNotExist));
return; return;
} }
if (_diskProvider.GetDirectories(rootFolder).Empty()) if (_diskProvider.GetDirectories(rootFolder).Empty())
{ {
_logger.Warn("Series' root folder ({0}) is empty.", rootFolder); _logger.Warn("Artist' root folder ({0}) is empty.", rootFolder);
_eventAggregator.PublishEvent(new SeriesScanSkippedEvent(series, SeriesScanSkippedReason.RootFolderIsEmpty)); _eventAggregator.PublishEvent(new ArtistScanSkippedEvent(artist, ArtistScanSkippedReason.RootFolderIsEmpty));
return; return;
} }
_logger.ProgressInfo("Scanning disk for {0}", series.Title); _logger.ProgressInfo("Scanning disk for {0}", artist.Name);
if (!_diskProvider.FolderExists(series.Path)) if (!_diskProvider.FolderExists(artist.Path))
{ {
if (_configService.CreateEmptySeriesFolders) if (_configService.CreateEmptySeriesFolders)
{ {
_logger.Debug("Creating missing series folder: {0}", series.Path); _logger.Debug("Creating missing artist folder: {0}", artist.Path);
_diskProvider.CreateFolder(series.Path); _diskProvider.CreateFolder(artist.Path);
SetPermissions(series.Path); SetPermissions(artist.Path);
} }
else else
{ {
_logger.Debug("Series folder doesn't exist: {0}", series.Path); _logger.Debug("Artist folder doesn't exist: {0}", artist.Path);
} }
CleanMediaFiles(series, new List<string>()); CleanMediaFiles(artist, new List<string>());
CompletedScanning(series); CompletedScanning(artist);
return; return;
} }
var videoFilesStopwatch = Stopwatch.StartNew(); var musicFilesStopwatch = Stopwatch.StartNew();
var mediaFileList = FilterFiles(series, GetVideoFiles(series.Path)).ToList(); var mediaFileList = FilterFiles(artist, GetMusicFiles(artist.Path)).ToList();
videoFilesStopwatch.Stop(); musicFilesStopwatch.Stop();
_logger.Trace("Finished getting episode files for: {0} [{1}]", series, videoFilesStopwatch.Elapsed); _logger.Trace("Finished getting track files for: {0} [{1}]", artist, musicFilesStopwatch.Elapsed);
CleanMediaFiles(series, mediaFileList); CleanMediaFiles(artist, mediaFileList);
var decisionsStopwatch = Stopwatch.StartNew(); var decisionsStopwatch = Stopwatch.StartNew();
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, series); var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, artist);
decisionsStopwatch.Stop(); decisionsStopwatch.Stop();
_logger.Trace("Import decisions complete for: {0} [{1}]", series, decisionsStopwatch.Elapsed); _logger.Trace("Import decisions complete for: {0} [{1}]", artist, decisionsStopwatch.Elapsed);
_importApprovedEpisodes.Import(decisions, false); _importApprovedTracks.Import(decisions, false);
CompletedScanning(series); CompletedScanning(artist);
} }
private void CleanMediaFiles(Series series, List<string> mediaFileList) private void CleanMediaFiles(Artist artist, List<string> mediaFileList)
{ {
_logger.Debug("{0} Cleaning up media files in DB", series); _logger.Debug("{0} Cleaning up media files in DB", artist);
_mediaFileTableCleanupService.Clean(series, mediaFileList); _mediaFileTableCleanupService.Clean(artist, mediaFileList);
} }
private void CompletedScanning(Series series) //private void CompletedScanning(Series series)
//{
// _logger.Info("Completed scanning disk for {0}", series.Title);
// _eventAggregator.PublishEvent(new SeriesScannedEvent(series));
//}
private void CompletedScanning(Artist artist)
{ {
_logger.Info("Completed scanning disk for {0}", series.Title); _logger.Info("Completed scanning disk for {0}", artist.Name);
_eventAggregator.PublishEvent(new SeriesScannedEvent(series)); _eventAggregator.PublishEvent(new ArtistScannedEvent(artist));
} }
public string[] GetVideoFiles(string path, bool allDirectories = true) public string[] GetVideoFiles(string path, bool allDirectories = true)
@ -143,9 +155,24 @@ namespace NzbDrone.Core.MediaFiles
return mediaFileList.ToArray(); return mediaFileList.ToArray();
} }
public string[] GetMusicFiles(string path, bool allDirectories = true)
{
_logger.Debug("Scanning '{0}' for music files", path);
var searchOption = allDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
var filesOnDisk = _diskProvider.GetFiles(path, searchOption).ToList();
var mediaFileList = filesOnDisk.Where(file => MediaFileExtensions.Extensions.Contains(Path.GetExtension(file).ToLower()))
.ToList();
_logger.Trace("{0} files were found in {1}", filesOnDisk.Count, path);
_logger.Debug("{0} video files were found in {1}", mediaFileList.Count, path);
return mediaFileList.ToArray();
}
public string[] GetNonVideoFiles(string path, bool allDirectories = true) public string[] GetNonVideoFiles(string path, bool allDirectories = true)
{ {
_logger.Debug("Scanning '{0}' for non-video files", path); _logger.Debug("Scanning '{0}' for non-music files", path);
var searchOption = allDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var searchOption = allDirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
var filesOnDisk = _diskProvider.GetFiles(path, searchOption).ToList(); var filesOnDisk = _diskProvider.GetFiles(path, searchOption).ToList();
@ -154,7 +181,7 @@ namespace NzbDrone.Core.MediaFiles
.ToList(); .ToList();
_logger.Trace("{0} files were found in {1}", filesOnDisk.Count, path); _logger.Trace("{0} files were found in {1}", filesOnDisk.Count, path);
_logger.Debug("{0} non-video files were found in {1}", mediaFileList.Count, path); _logger.Debug("{0} non-music files were found in {1}", mediaFileList.Count, path);
return mediaFileList.ToArray(); return mediaFileList.ToArray();
} }
@ -165,6 +192,13 @@ namespace NzbDrone.Core.MediaFiles
.ToList(); .ToList();
} }
public List<string> FilterFiles(Artist artist, IEnumerable<string> files)
{
return files.Where(file => !ExcludedSubFoldersRegex.IsMatch(artist.Path.GetRelativePath(file)))
.Where(file => !ExcludedFilesRegex.IsMatch(Path.GetFileName(file)))
.ToList();
}
private void SetPermissions(string path) private void SetPermissions(string path)
{ {
if (!_configService.SetPermissionsLinux) if (!_configService.SetPermissionsLinux)
@ -186,26 +220,26 @@ namespace NzbDrone.Core.MediaFiles
} }
} }
public void Handle(SeriesUpdatedEvent message) public void Handle(ArtistUpdatedEvent message)
{ {
Scan(message.Series); Scan(message.Artist);
} }
public void Execute(RescanSeriesCommand message) public void Execute(RescanArtistCommand message)
{ {
if (message.SeriesId.HasValue) if (message.ArtistId.IsNotNullOrWhiteSpace())
{ {
var series = _seriesService.GetSeries(message.SeriesId.Value); var artist = _artistService.FindById(message.ArtistId);
Scan(series); Scan(artist);
} }
else else
{ {
var allSeries = _seriesService.GetAllSeries(); var allArtists = _artistService.GetAllArtists();
foreach (var series in allSeries) foreach (var artist in allArtists)
{ {
Scan(series); Scan(artist);
} }
} }
} }

View file

@ -9,6 +9,7 @@ using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
{ {

View file

@ -9,6 +9,7 @@ using NzbDrone.Core.Parser;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
{ {
@ -59,7 +60,7 @@ namespace NzbDrone.Core.MediaFiles
results.AddRange(folderResults); results.AddRange(folderResults);
} }
foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false)) foreach (var videoFile in _diskScanService.GetNonVideoFiles(directoryInfo.FullName, false))
{ {
var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null); var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null);
results.AddRange(fileResults); results.AddRange(fileResults);
@ -100,7 +101,7 @@ namespace NzbDrone.Core.MediaFiles
public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Series series) public bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Series series)
{ {
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); var videoFiles = _diskScanService.GetNonVideoFiles(directoryInfo.FullName);
var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar"); var rarFiles = _diskProvider.GetFiles(directoryInfo.FullName, SearchOption.AllDirectories).Where(f => Path.GetExtension(f) == ".rar");
foreach (var videoFile in videoFiles) foreach (var videoFile in videoFiles)
@ -152,48 +153,50 @@ namespace NzbDrone.Core.MediaFiles
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Series series, DownloadClientItem downloadClientItem) private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Series series, DownloadClientItem downloadClientItem)
{ {
if (_seriesService.SeriesPathExists(directoryInfo.FullName)) throw new System.NotImplementedException("Will be removed");
{
_logger.Warn("Unable to process folder that is mapped to an existing show");
return new List<ImportResult>();
}
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); //if (_seriesService.SeriesPathExists(directoryInfo.FullName))
var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name); //{
// _logger.Warn("Unable to process folder that is mapped to an existing show");
// return new List<ImportResult>();
//}
if (folderInfo != null) //var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
{ //var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
}
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); //if (folderInfo != null)
//{
// _logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
//}
if (downloadClientItem == null) //var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
{
foreach (var videoFile in videoFiles)
{
if (_diskProvider.IsFileLocked(videoFile))
{
return new List<ImportResult>
{
FileIsLockedResult(videoFile)
};
}
}
}
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, folderInfo, true); //if (downloadClientItem == null)
var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode); //{
// foreach (var videoFile in videoFiles)
// {
// if (_diskProvider.IsFileLocked(videoFile))
// {
// return new List<ImportResult>
// {
// FileIsLockedResult(videoFile)
// };
// }
// }
//}
if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && //var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, folderInfo, true);
importResults.Any(i => i.Result == ImportResultType.Imported) && //var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode);
ShouldDeleteFolder(directoryInfo, series))
{
_logger.Debug("Deleting folder after importing valid files");
_diskProvider.DeleteFolder(directoryInfo.FullName, true);
}
return importResults; //if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) &&
// importResults.Any(i => i.Result == ImportResultType.Imported) &&
// ShouldDeleteFolder(directoryInfo, series))
//{
// _logger.Debug("Deleting folder after importing valid files");
// _diskProvider.DeleteFolder(directoryInfo.FullName, true);
//}
//return importResults;
} }
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem) private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem)
@ -215,30 +218,31 @@ namespace NzbDrone.Core.MediaFiles
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, Series series, DownloadClientItem downloadClientItem) private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, Series series, DownloadClientItem downloadClientItem)
{ {
if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._")) throw new System.NotImplementedException("Will be removed");
{ //if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
_logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName); //{
// _logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);
return new List<ImportResult> // return new List<ImportResult>
{ // {
new ImportResult(new ImportDecision(new LocalEpisode { Path = fileInfo.FullName }, new Rejection("Invalid video file, filename starts with '._'")), "Invalid video file, filename starts with '._'") // new ImportResult(new ImportDecision(new LocalTrack { Path = fileInfo.FullName }, new Rejection("Invalid music file, filename starts with '._'")), "Invalid music file, filename starts with '._'")
}; // };
} //}
if (downloadClientItem == null) //if (downloadClientItem == null)
{ //{
if (_diskProvider.IsFileLocked(fileInfo.FullName)) // if (_diskProvider.IsFileLocked(fileInfo.FullName))
{ // {
return new List<ImportResult> // return new List<ImportResult>
{ // {
FileIsLockedResult(fileInfo.FullName) // FileIsLockedResult(fileInfo.FullName)
}; // };
} // }
} //}
var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, series, null, true); //var decisions = _importDecisionMaker.GetImportDecisions(new List<string>() { fileInfo.FullName }, series, null, true);
return _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode); //return _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode);
} }
private string GetCleanedUpFolderName(string folder) private string GetCleanedUpFolderName(string folder)
@ -251,15 +255,17 @@ namespace NzbDrone.Core.MediaFiles
private ImportResult FileIsLockedResult(string videoFile) private ImportResult FileIsLockedResult(string videoFile)
{ {
_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile); throw new System.NotImplementedException("Will be removed");
return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later"); //_logger.Debug("[{0}] is currently locked by another process, skipping", videoFile);
//return new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, new Rejection("Locked file, try again later")), "Locked file, try again later");
} }
private ImportResult UnknownSeriesResult(string message, string videoFile = null) private ImportResult UnknownSeriesResult(string message, string videoFile = null)
{ {
var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile }; throw new System.NotImplementedException("Will be removed");
//var localEpisode = videoFile == null ? null : new LocalEpisode { Path = videoFile };
return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Series")), message); //return new ImportResult(new ImportDecision(localEpisode, new Rejection("Unknown Series")), message);
} }
} }
} }

View file

@ -6,5 +6,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
public interface IImportDecisionEngineSpecification public interface IImportDecisionEngineSpecification
{ {
Decision IsSatisfiedBy(LocalEpisode localEpisode); Decision IsSatisfiedBy(LocalEpisode localEpisode);
Decision IsSatisfiedBy(LocalTrack localTrack);
} }
} }

View file

@ -12,7 +12,7 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Extras; using NzbDrone.Core.Extras;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles.EpisodeImport namespace NzbDrone.Core.MediaFiles.EpisodeImport
{ {
@ -47,105 +47,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto) public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto)
{ {
var qualifiedImports = decisions.Where(c => c.Approved) throw new NotImplementedException("This will be removed");
.GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s
.OrderByDescending(c => c.LocalEpisode.Quality, new QualityModelComparer(s.First().LocalEpisode.Series.Profile))
.ThenByDescending(c => c.LocalEpisode.Size))
.SelectMany(c => c)
.ToList();
var importResults = new List<ImportResult>();
foreach (var importDecision in qualifiedImports.OrderBy(e => e.LocalEpisode.Episodes.Select(episode => episode.EpisodeNumber).MinOrDefault())
.ThenByDescending(e => e.LocalEpisode.Size))
{
var localEpisode = importDecision.LocalEpisode;
var oldFiles = new List<EpisodeFile>();
try
{
//check if already imported
if (importResults.SelectMany(r => r.ImportDecision.LocalEpisode.Episodes)
.Select(e => e.Id)
.Intersect(localEpisode.Episodes.Select(e => e.Id))
.Any())
{
importResults.Add(new ImportResult(importDecision, "Episode has already been imported"));
continue;
}
var episodeFile = new EpisodeFile();
episodeFile.DateAdded = DateTime.UtcNow;
episodeFile.SeriesId = localEpisode.Series.Id;
episodeFile.Path = localEpisode.Path.CleanFilePath();
episodeFile.Size = _diskProvider.GetFileSize(localEpisode.Path);
episodeFile.Quality = localEpisode.Quality;
episodeFile.MediaInfo = localEpisode.MediaInfo;
episodeFile.SeasonNumber = localEpisode.SeasonNumber;
episodeFile.Episodes = localEpisode.Episodes;
episodeFile.ReleaseGroup = localEpisode.ParsedEpisodeInfo.ReleaseGroup;
bool copyOnly;
switch (importMode)
{
default:
case ImportMode.Auto:
copyOnly = downloadClientItem != null && downloadClientItem.IsReadOnly;
break;
case ImportMode.Move:
copyOnly = false;
break;
case ImportMode.Copy:
copyOnly = true;
break;
}
if (newDownload)
{
episodeFile.SceneName = GetSceneName(downloadClientItem, localEpisode);
var moveResult = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly);
oldFiles = moveResult.OldFiles;
}
else
{
episodeFile.RelativePath = localEpisode.Series.Path.GetRelativePath(episodeFile.Path);
}
_mediaFileService.Add(episodeFile);
importResults.Add(new ImportResult(importDecision));
if (newDownload)
{
_extraService.ImportExtraFiles(localEpisode, episodeFile, copyOnly);
}
if (downloadClientItem != null)
{
_eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly));
}
else
{
_eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload));
}
if (newDownload)
{
_eventAggregator.PublishEvent(new EpisodeDownloadedEvent(localEpisode, episodeFile, oldFiles));
}
}
catch (Exception e)
{
_logger.Warn(e, "Couldn't import episode " + localEpisode);
importResults.Add(new ImportResult(importDecision, "Failed to import episode"));
}
}
//Adding all the rejected decisions
importResults.AddRange(decisions.Where(c => !c.Approved)
.Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray())));
return importResults;
} }
private string GetSceneName(DownloadClientItem downloadClientItem, LocalEpisode localEpisode) private string GetSceneName(DownloadClientItem downloadClientItem, LocalEpisode localEpisode)

View file

@ -1,22 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.EpisodeImport
{
public class ImportDecision
{
public LocalEpisode LocalEpisode { get; private set; }
public IEnumerable<Rejection> Rejections { get; private set; }
public bool Approved => Rejections.Empty();
public ImportDecision(LocalEpisode localEpisode, params Rejection[] rejections)
{
LocalEpisode = localEpisode;
Rejections = rejections.ToList();
}
}
}

View file

@ -11,14 +11,17 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.MediaFiles.MediaInfo;
using NzbDrone.Core.Music;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles.EpisodeImport namespace NzbDrone.Core.MediaFiles.EpisodeImport
{ {
public interface IMakeImportDecision public interface IMakeImportDecision
{ {
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series); //List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series);
List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource); List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist);
//List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo);
} }
public class ImportDecisionMaker : IMakeImportDecision public class ImportDecisionMaker : IMakeImportDecision
@ -48,84 +51,124 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
_logger = logger; _logger = logger;
} }
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series) //public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series)
//{
// return GetImportDecisions(videoFiles, series, null, false);
//}
//public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Artist series, ParsedEpisodeInfo folderInfo, bool sceneSource)
//{
// var newFiles = _mediaFileService.FilterExistingFiles(videoFiles.ToList(), series);
// _logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count());
// var shouldUseFolderName = ShouldUseFolderName(videoFiles, series, folderInfo);
// var decisions = new List<ImportDecision>();
// foreach (var file in newFiles)
// {
// decisions.AddIfNotNull(GetDecision(file, series, folderInfo, sceneSource, shouldUseFolderName));
// }
// return decisions;
//}
public List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist)
{ {
return GetImportDecisions(videoFiles, series, null, false); return GetImportDecisions(musicFiles, artist, null);
} }
public List<ImportDecision> GetImportDecisions(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource) public List<ImportDecision> GetImportDecisions(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo)
{ {
var newFiles = _mediaFileService.FilterExistingFiles(videoFiles.ToList(), series); var newFiles = _mediaFileService.FilterExistingFiles(musicFiles.ToList(), artist);
_logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, videoFiles.Count()); _logger.Debug("Analyzing {0}/{1} files.", newFiles.Count, musicFiles.Count());
var shouldUseFolderName = ShouldUseFolderName(videoFiles, series, folderInfo); var shouldUseFolderName = ShouldUseFolderName(musicFiles, artist, folderInfo);
var decisions = new List<ImportDecision>(); var decisions = new List<ImportDecision>();
foreach (var file in newFiles) foreach (var file in newFiles)
{ {
decisions.AddIfNotNull(GetDecision(file, series, folderInfo, sceneSource, shouldUseFolderName)); decisions.AddIfNotNull(GetDecision(file, artist, folderInfo, shouldUseFolderName));
} }
return decisions; return decisions;
} }
private ImportDecision GetDecision(string file, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource, bool shouldUseFolderName) private ImportDecision GetDecision(string file, Artist artist, ParsedTrackInfo folderInfo, bool shouldUseFolderName)
{ {
ImportDecision decision = null; ImportDecision decision = null;
try try
{ {
var localEpisode = _parsingService.GetLocalEpisode(file, series, shouldUseFolderName ? folderInfo : null, sceneSource); var localTrack = _parsingService.GetLocalTrack(file, artist, shouldUseFolderName ? folderInfo : null);
if (localEpisode != null) if (localTrack != null)
{ {
localEpisode.Quality = GetQuality(folderInfo, localEpisode.Quality, series); localTrack.Quality = GetQuality(folderInfo, localTrack.Quality, artist);
localEpisode.Size = _diskProvider.GetFileSize(file); localTrack.Size = _diskProvider.GetFileSize(file);
_logger.Debug("Size: {0}", localEpisode.Size); _logger.Debug("Size: {0}", localTrack.Size);
//TODO: make it so media info doesn't ruin the import process of a new series //TODO: make it so media info doesn't ruin the import process of a new artist
if (sceneSource)
if (localTrack.Tracks.Empty())
{ {
localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file); decision = new ImportDecision(localTrack, new Rejection("Invalid album or track"));
}
if (localEpisode.Episodes.Empty())
{
decision = new ImportDecision(localEpisode, new Rejection("Invalid season or episode"));
} }
else else
{ {
decision = GetDecision(localEpisode); decision = GetDecision(localTrack);
} }
} }
else else
{ {
localEpisode = new LocalEpisode(); localTrack = new LocalTrack();
localEpisode.Path = file; localTrack.Path = file;
decision = new ImportDecision(localEpisode, new Rejection("Unable to parse file")); decision = new ImportDecision(localTrack, new Rejection("Unable to parse file"));
} }
} }
catch (Exception e) catch (Exception e)
{ {
_logger.Error(e, "Couldn't import file. {0}", file); _logger.Error(e, "Couldn't import file. {0}", file);
var localEpisode = new LocalEpisode { Path = file }; var localTrack = new LocalTrack { Path = file };
decision = new ImportDecision(localEpisode, new Rejection("Unexpected error processing file")); decision = new ImportDecision(localTrack, new Rejection("Unexpected error processing file"));
} }
return decision; return decision;
} }
private ImportDecision GetDecision(LocalEpisode localEpisode) private ImportDecision GetDecision(LocalTrack localTrack)
{ {
var reasons = _specifications.Select(c => EvaluateSpec(c, localEpisode)) var reasons = _specifications.Select(c => EvaluateSpec(c, localTrack))
.Where(c => c != null); .Where(c => c != null);
return new ImportDecision(localEpisode, reasons.ToArray()); return new ImportDecision(localTrack, reasons.ToArray());
}
private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalTrack localTrack)
{
try
{
var result = spec.IsSatisfiedBy(localTrack);
if (!result.Accepted)
{
return new Rejection(result.Reason);
}
}
catch (Exception e)
{
//e.Data.Add("report", remoteEpisode.Report.ToJson());
//e.Data.Add("parsed", remoteEpisode.ParsedEpisodeInfo.ToJson());
_logger.Error(e, "Couldn't evaluate decision on {0}", localTrack.Path);
return new Rejection($"{spec.GetType().Name}: {e.Message}");
}
return null;
} }
private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalEpisode localEpisode) private Rejection EvaluateSpec(IImportDecisionEngineSpecification spec, LocalEpisode localEpisode)
@ -150,28 +193,28 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
return null; return null;
} }
private bool ShouldUseFolderName(List<string> videoFiles, Series series, ParsedEpisodeInfo folderInfo) private bool ShouldUseFolderName(List<string> musicFiles, Artist artist, ParsedTrackInfo folderInfo)
{ {
if (folderInfo == null) if (folderInfo == null)
{ {
return false; return false;
} }
if (folderInfo.FullSeason) //if (folderInfo.FullSeason)
{ //{
return false; // return false;
} //}
return videoFiles.Count(file => return musicFiles.Count(file =>
{ {
var size = _diskProvider.GetFileSize(file); var size = _diskProvider.GetFileSize(file);
var fileQuality = QualityParser.ParseQuality(file); var fileQuality = QualityParser.ParseQuality(file);
var sample = _detectSample.IsSample(series, GetQuality(folderInfo, fileQuality, series), file, size, folderInfo.IsPossibleSpecialEpisode); //var sample = _detectSample.IsSample(artist, GetQuality(folderInfo, fileQuality, artist), file, size, folderInfo.IsPossibleSpecialEpisode);
if (sample) //if (sample)
{ //{
return false; // return false;
} //}
if (SceneChecker.IsSceneTitle(Path.GetFileName(file))) if (SceneChecker.IsSceneTitle(Path.GetFileName(file)))
{ {
@ -182,9 +225,9 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
}) == 1; }) == 1;
} }
private QualityModel GetQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series) private QualityModel GetQuality(ParsedTrackInfo folderInfo, QualityModel fileQuality, Artist artist)
{ {
if (UseFolderQuality(folderInfo, fileQuality, series)) if (UseFolderQuality(folderInfo, fileQuality, artist))
{ {
_logger.Debug("Using quality from folder: {0}", folderInfo.Quality); _logger.Debug("Using quality from folder: {0}", folderInfo.Quality);
return folderInfo.Quality; return folderInfo.Quality;
@ -193,7 +236,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
return fileQuality; return fileQuality;
} }
private bool UseFolderQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series) private bool UseFolderQuality(ParsedTrackInfo folderInfo, QualityModel fileQuality, Artist artist)
{ {
if (folderInfo == null) if (folderInfo == null)
{ {
@ -210,7 +253,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
return true; return true;
} }
if (new QualityModelComparer(series.Profile).Compare(folderInfo.Quality, fileQuality) > 0) if (new QualityModelComparer(artist.Profile).Compare(folderInfo.Quality, fileQuality) > 0)
{ {
return true; return true;
} }

View file

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
{ {

View file

@ -15,6 +15,7 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
{ {
@ -94,65 +95,67 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
private List<ManualImportItem> ProcessFolder(string folder, string downloadId) private List<ManualImportItem> ProcessFolder(string folder, string downloadId)
{ {
var directoryInfo = new DirectoryInfo(folder); throw new System.NotImplementedException("TODO: This will be rewritten for Music");
var series = _parsingService.GetSeries(directoryInfo.Name); //var directoryInfo = new DirectoryInfo(folder);
//var series = _parsingService.GetSeries(directoryInfo.Name);
if (series == null && downloadId.IsNotNullOrWhiteSpace()) //if (series == null && downloadId.IsNotNullOrWhiteSpace())
{ //{
var trackedDownload = _trackedDownloadService.Find(downloadId); // var trackedDownload = _trackedDownloadService.Find(downloadId);
series = trackedDownload.RemoteEpisode.Series; // series = trackedDownload.RemoteEpisode.Series;
} //}
if (series == null) //if (series == null)
{ //{
var files = _diskScanService.GetVideoFiles(folder); // var files = _diskScanService.GetVideoFiles(folder);
return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList(); // return files.Select(file => ProcessFile(file, downloadId, folder)).Where(i => i != null).ToList();
} //}
var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name); //var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList(); //var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList();
var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder)); //var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder));
return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList(); //return decisions.Select(decision => MapItem(decision, folder, downloadId)).ToList();
} }
private ManualImportItem ProcessFile(string file, string downloadId, string folder = null) private ManualImportItem ProcessFile(string file, string downloadId, string folder = null)
{ {
if (folder.IsNullOrWhiteSpace()) throw new System.NotImplementedException("TODO: This will be rewritten for Music");
{ //if (folder.IsNullOrWhiteSpace())
folder = new FileInfo(file).Directory.FullName; //{
} // folder = new FileInfo(file).Directory.FullName;
//}
var relativeFile = folder.GetRelativePath(file); //var relativeFile = folder.GetRelativePath(file);
var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]); //var series = _parsingService.GetSeries(relativeFile.Split('\\', '/')[0]);
if (series == null) //if (series == null)
{ //{
series = _parsingService.GetSeries(relativeFile); // series = _parsingService.GetSeries(relativeFile);
} //}
if (series == null && downloadId.IsNotNullOrWhiteSpace()) //if (series == null && downloadId.IsNotNullOrWhiteSpace())
{ //{
var trackedDownload = _trackedDownloadService.Find(downloadId); // var trackedDownload = _trackedDownloadService.Find(downloadId);
series = trackedDownload.RemoteEpisode.Series; // series = trackedDownload.RemoteEpisode.Series;
} //}
if (series == null) //if (series == null)
{ //{
var localEpisode = new LocalEpisode(); // var localEpisode = new LocalEpisode();
localEpisode.Path = file; // localEpisode.Path = file;
localEpisode.Quality = QualityParser.ParseQuality(file); // localEpisode.Quality = QualityParser.ParseQuality(file);
localEpisode.Size = _diskProvider.GetFileSize(file); // localEpisode.Size = _diskProvider.GetFileSize(file);
return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId); // return MapItem(new ImportDecision(localEpisode, new Rejection("Unknown Series")), folder, downloadId);
} //}
var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file}, //var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
series, null, SceneSource(series, folder)); // series, null, SceneSource(series, folder));
return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null; //return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null;
} }
private bool SceneSource(Series series, string folder) private bool SceneSource(Series series, string folder)
@ -162,107 +165,109 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId) private ManualImportItem MapItem(ImportDecision decision, string folder, string downloadId)
{ {
var item = new ManualImportItem(); throw new System.NotImplementedException("TODO: This will be rewritten for Music");
//var item = new ManualImportItem();
item.Path = decision.LocalEpisode.Path; //item.Path = decision.LocalEpisode.Path;
item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path); //item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path); //item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
item.DownloadId = downloadId; //item.DownloadId = downloadId;
if (decision.LocalEpisode.Series != null) //if (decision.LocalEpisode.Series != null)
{ //{
item.Series = decision.LocalEpisode.Series; // item.Series = decision.LocalEpisode.Series;
} //}
if (decision.LocalEpisode.Episodes.Any()) //if (decision.LocalEpisode.Episodes.Any())
{ //{
item.SeasonNumber = decision.LocalEpisode.SeasonNumber; // item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
item.Episodes = decision.LocalEpisode.Episodes; // item.Episodes = decision.LocalEpisode.Episodes;
} //}
item.Quality = decision.LocalEpisode.Quality; //item.Quality = decision.LocalEpisode.Quality;
item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path); //item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
item.Rejections = decision.Rejections; //item.Rejections = decision.Rejections;
return item; //return item;
} }
public void Execute(ManualImportCommand message) public void Execute(ManualImportCommand message)
{ {
_logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode); _logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode);
throw new System.NotImplementedException("TODO: This will be rewritten for Music");
var imported = new List<ImportResult>(); //var imported = new List<ImportResult>();
var importedTrackedDownload = new List<ManuallyImportedFile>(); //var importedTrackedDownload = new List<ManuallyImportedFile>();
for (int i = 0; i < message.Files.Count; i++) //for (int i = 0; i < message.Files.Count; i++)
{ //{
_logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count); // _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
var file = message.Files[i]; // var file = message.Files[i];
var series = _seriesService.GetSeries(file.SeriesId); // var series = _seriesService.GetSeries(file.SeriesId);
var episodes = _episodeService.GetEpisodes(file.EpisodeIds); // var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo(); // var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path); // var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
var existingFile = series.Path.IsParentPath(file.Path); // var existingFile = series.Path.IsParentPath(file.Path);
var localEpisode = new LocalEpisode // var localEpisode = new LocalEpisode
{ // {
ExistingFile = false, // ExistingFile = false,
Episodes = episodes, // Episodes = episodes,
MediaInfo = mediaInfo, // MediaInfo = mediaInfo,
ParsedEpisodeInfo = parsedEpisodeInfo, // ParsedEpisodeInfo = parsedEpisodeInfo,
Path = file.Path, // Path = file.Path,
Quality = file.Quality, // Quality = file.Quality,
Series = series, // Series = series,
Size = 0 // Size = 0
}; // };
//TODO: Cleanup non-tracked downloads // //TODO: Cleanup non-tracked downloads
var importDecision = new ImportDecision(localEpisode); // var importDecision = new ImportDecision(localEpisode);
if (file.DownloadId.IsNullOrWhiteSpace()) // if (file.DownloadId.IsNullOrWhiteSpace())
{ // {
imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode)); // imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
} // }
else // else
{ // {
var trackedDownload = _trackedDownloadService.Find(file.DownloadId); // var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); // var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
imported.Add(importResult); // imported.Add(importResult);
importedTrackedDownload.Add(new ManuallyImportedFile // importedTrackedDownload.Add(new ManuallyImportedFile
{ // {
TrackedDownload = trackedDownload, // TrackedDownload = trackedDownload,
ImportResult = importResult // ImportResult = importResult
}); // });
} // }
} //}
_logger.ProgressTrace("Manually imported {0} files", imported.Count); //_logger.ProgressTrace("Manually imported {0} files", imported.Count);
foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList()) //foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
{ //{
var trackedDownload = groupedTrackedDownload.First().TrackedDownload; // var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath)) // if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
{ // {
if (_downloadedEpisodesImportService.ShouldDeleteFolder( // if (_downloadedEpisodesImportService.ShouldDeleteFolder(
new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath), // new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly) // trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
{ // {
_diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true); // _diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
} // }
} // }
if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count)) // if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
{ // {
trackedDownload.State = TrackedDownloadStage.Imported; // trackedDownload.State = TrackedDownloadStage.Imported;
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload)); // _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
} // }
} //}
} }
} }
} }

View file

@ -1,4 +1,5 @@
using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.MediaFiles.TrackImport;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
{ {

View file

@ -63,5 +63,48 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
return Decision.Accept(); return Decision.Accept();
} }
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
if (_configService.SkipFreeSpaceCheckWhenImporting)
{
_logger.Debug("Skipping free space check when importing");
return Decision.Accept();
}
try
{
if (localTrack.ExistingFile)
{
_logger.Debug("Skipping free space check for existing episode");
return Decision.Accept();
}
var path = Directory.GetParent(localTrack.Artist.Path);
var freeSpace = _diskProvider.GetAvailableSpace(path.FullName);
if (!freeSpace.HasValue)
{
_logger.Debug("Free space check returned an invalid result for: {0}", path);
return Decision.Accept();
}
if (freeSpace < localTrack.Size + 100.Megabytes())
{
_logger.Warn("Not enough free space ({0}) to import: {1} ({2})", freeSpace, localTrack, localTrack.Size);
return Decision.Reject("Not enough free space");
}
}
catch (DirectoryNotFoundException ex)
{
_logger.Error(ex, "Unable to check free disk space while importing.");
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to check free disk space while importing. {0}", localTrack.Path);
}
return Decision.Accept();
}
} }
} }

View file

@ -1,4 +1,5 @@
using NLog; using System;
using NLog;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -13,6 +14,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_logger = logger; _logger = logger;
} }
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
throw new NotImplementedException("Interface will be removed");
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode) public Decision IsSatisfiedBy(LocalEpisode localEpisode)
{ {
if (localEpisode.ParsedEpisodeInfo.FullSeason) if (localEpisode.ParsedEpisodeInfo.FullSeason)

View file

@ -1,4 +1,5 @@
using System.IO; using System;
using System.IO;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
@ -14,6 +15,53 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
{ {
_logger = logger; _logger = logger;
} }
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
if (localTrack.ExistingFile)
{
return Decision.Accept();
}
var dirInfo = new FileInfo(localTrack.Path).Directory;
if (dirInfo == null)
{
return Decision.Accept();
}
throw new System.NotImplementedException("Needs to be implemented");
//var folderInfo = Parser.Parser.ParseTitle(dirInfo.Name);
//if (folderInfo == null)
//{
// return Decision.Accept();
//}
//if (!folderInfo.TrackNumbers.Any())
//{
// return Decision.Accept();
//}
//var unexpected = localTrack.ParsedTrackInfo.TrackNumbers.Where(f => !folderInfo.TrackNumbers.Contains(f)).ToList();
//// TODO: Implement MatchesFolderSpecification
//if (unexpected.Any())
//{
// _logger.Debug("Unexpected track number(s) in file: {0}", string.Join(", ", unexpected));
// if (unexpected.Count == 1)
// {
// return Decision.Reject("Track Number {0} was unexpected considering the {1} folder name", unexpected.First(), dirInfo.Name);
// }
// return Decision.Reject("Episode Numbers {0} were unexpected considering the {1} folder name", string.Join(", ", unexpected), dirInfo.Name);
//}
return Decision.Accept();
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode) public Decision IsSatisfiedBy(LocalEpisode localEpisode)
{ {
if (localEpisode.ExistingFile) if (localEpisode.ExistingFile)

View file

@ -1,4 +1,5 @@
using NLog; using System;
using NLog;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -16,6 +17,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_logger = logger; _logger = logger;
} }
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
throw new NotImplementedException();
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode) public Decision IsSatisfiedBy(LocalEpisode localEpisode)
{ {
if (localEpisode.ExistingFile) if (localEpisode.ExistingFile)

View file

@ -56,5 +56,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
return Decision.Accept(); return Decision.Accept();
} }
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
throw new NotImplementedException();
}
} }
} }

View file

@ -1,3 +1,4 @@
using System;
using NLog; using NLog;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -27,5 +28,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_logger.Debug("Episode file on disk contains more episodes than this file contains"); _logger.Debug("Episode file on disk contains more episodes than this file contains");
return Decision.Reject("Episode file on disk contains more episodes than this file contains"); return Decision.Reject("Episode file on disk contains more episodes than this file contains");
} }
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
throw new NotImplementedException();
}
} }
} }

View file

@ -1,4 +1,5 @@
using System.Linq; using System;
using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -13,6 +14,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_logger = logger; _logger = logger;
} }
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
throw new NotImplementedException("This is not needed, Interface will be removed");
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode) public Decision IsSatisfiedBy(LocalEpisode localEpisode)
{ {
if (localEpisode.ExistingFile) if (localEpisode.ExistingFile)

View file

@ -1,4 +1,5 @@
using System.Linq; using System;
using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
@ -15,6 +16,18 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
_logger = logger; _logger = logger;
} }
public Decision IsSatisfiedBy(LocalTrack localTrack)
{
var qualityComparer = new QualityModelComparer(localTrack.Artist.Profile);
if (localTrack.Tracks.Any(e => e.TrackFileId != 0 && qualityComparer.Compare(e.TrackFile.Value.Quality, localTrack.Quality) > 0))
{
_logger.Debug("This file isn't an upgrade for all tracks. Skipping {0}", localTrack.Path);
return Decision.Reject("Not an upgrade for existing track file(s)");
}
return Decision.Accept();
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode) public Decision IsSatisfiedBy(LocalEpisode localEpisode)
{ {
var qualityComparer = new QualityModelComparer(localEpisode.Series.Profile); var qualityComparer = new QualityModelComparer(localEpisode.Series.Profile);

View file

@ -0,0 +1,27 @@
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Music;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.Events
{
public class ArtistScanSkippedEvent : IEvent
{
public Artist Artist { get; private set; }
public ArtistScanSkippedReason Reason { get; private set; }
public ArtistScanSkippedEvent(Artist artist, ArtistScanSkippedReason reason)
{
Artist = artist;
Reason = reason;
}
}
public enum ArtistScanSkippedReason
{
RootFolderDoesNotExist,
RootFolderIsEmpty
}
}

View file

@ -0,0 +1,19 @@
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Music;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.Events
{
public class ArtistScannedEvent : IEvent
{
public Artist Artist { get; private set; }
public ArtistScannedEvent(Artist artist)
{
Artist = artist;
}
}
}

View file

@ -0,0 +1,23 @@
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Parser.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.Events
{
public class TrackDownloadedEvent : IEvent
{
public LocalTrack Track { get; private set; }
public TrackFile TrackFile { get; private set; }
public List<TrackFile> OldFiles { get; private set; }
public TrackDownloadedEvent(LocalTrack track, TrackFile trackFile, List<TrackFile> oldFiles)
{
Track = track;
TrackFile = trackFile;
OldFiles = oldFiles;
}
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@ -5,36 +6,28 @@ using NzbDrone.Core.Messaging.Events;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
{ {
public interface IMediaFileRepository : IBasicRepository<EpisodeFile> public interface IMediaFileRepository : IBasicRepository<TrackFile>
{ {
List<EpisodeFile> GetFilesBySeries(int seriesId); List<TrackFile> GetFilesByArtist(int artistId);
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber); List<TrackFile> GetFilesWithoutMediaInfo();
List<EpisodeFile> GetFilesWithoutMediaInfo();
} }
public class MediaFileRepository : BasicRepository<EpisodeFile>, IMediaFileRepository public class MediaFileRepository : BasicRepository<TrackFile>, IMediaFileRepository
{ {
public MediaFileRepository(IMainDatabase database, IEventAggregator eventAggregator) public MediaFileRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator) : base(database, eventAggregator)
{ {
} }
public List<EpisodeFile> GetFilesBySeries(int seriesId) public List<TrackFile> GetFilesWithoutMediaInfo()
{
return Query.Where(c => c.SeriesId == seriesId).ToList();
}
public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
{
return Query.Where(c => c.SeriesId == seriesId)
.AndWhere(c => c.SeasonNumber == seasonNumber)
.ToList();
}
public List<EpisodeFile> GetFilesWithoutMediaInfo()
{ {
return Query.Where(c => c.MediaInfo == null).ToList(); return Query.Where(c => c.MediaInfo == null).ToList();
} }
public List<TrackFile> GetFilesByArtist(int artistId)
{
return Query.Where(c => c.ArtistId == artistId).ToList();
}
} }
} }

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using NLog; using NLog;
@ -7,24 +7,27 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events; using NzbDrone.Core.Tv.Events;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Core.Music;
using System;
using NzbDrone.Core.Music.Events;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
{ {
public interface IMediaFileService public interface IMediaFileService
{ {
EpisodeFile Add(EpisodeFile episodeFile); TrackFile Add(TrackFile trackFile);
void Update(EpisodeFile episodeFile); void Update(TrackFile trackFile);
void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason); void Delete(TrackFile trackFile, DeleteMediaFileReason reason);
List<EpisodeFile> GetFilesBySeries(int seriesId); List<TrackFile> GetFilesByArtist(int artistId);
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber); List<TrackFile> GetFilesByAlbum(int artistId, int albumId);
List<EpisodeFile> GetFilesWithoutMediaInfo(); List<TrackFile> GetFilesWithoutMediaInfo();
List<string> FilterExistingFiles(List<string> files, Series series); List<string> FilterExistingFiles(List<string> files, Artist artist);
EpisodeFile Get(int id); TrackFile Get(int id);
List<EpisodeFile> Get(IEnumerable<int> ids); List<TrackFile> Get(IEnumerable<int> ids);
} }
public class MediaFileService : IMediaFileService, IHandleAsync<SeriesDeletedEvent> public class MediaFileService : IMediaFileService, IHandleAsync<ArtistDeletedEvent>
{ {
private readonly IEventAggregator _eventAggregator; private readonly IEventAggregator _eventAggregator;
private readonly IMediaFileRepository _mediaFileRepository; private readonly IMediaFileRepository _mediaFileRepository;
@ -37,66 +40,69 @@ namespace NzbDrone.Core.MediaFiles
_logger = logger; _logger = logger;
} }
public EpisodeFile Add(EpisodeFile episodeFile) public TrackFile Add(TrackFile trackFile)
{ {
var addedFile = _mediaFileRepository.Insert(episodeFile); var addedFile = _mediaFileRepository.Insert(trackFile);
_eventAggregator.PublishEvent(new EpisodeFileAddedEvent(addedFile)); _eventAggregator.PublishEvent(new TrackFileAddedEvent(addedFile));
return addedFile; return addedFile;
} }
public void Update(EpisodeFile episodeFile) public void Update(TrackFile trackFile)
{ {
_mediaFileRepository.Update(episodeFile); _mediaFileRepository.Update(trackFile);
} }
public void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason) public void Delete(TrackFile trackFile, DeleteMediaFileReason reason)
{ {
//Little hack so we have the episodes and series attached for the event consumers //Little hack so we have the tracks and artist attached for the event consumers
episodeFile.Episodes.LazyLoad(); trackFile.Episodes.LazyLoad();
episodeFile.Path = Path.Combine(episodeFile.Series.Value.Path, episodeFile.RelativePath); trackFile.Path = Path.Combine(trackFile.Artist.Value.Path, trackFile.RelativePath);
_mediaFileRepository.Delete(episodeFile); _mediaFileRepository.Delete(trackFile);
_eventAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile, reason)); _eventAggregator.PublishEvent(new TrackFileDeletedEvent(trackFile, reason));
} }
public List<EpisodeFile> GetFilesBySeries(int seriesId)
{
return _mediaFileRepository.GetFilesBySeries(seriesId);
}
public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber) public List<TrackFile> GetFilesWithoutMediaInfo()
{
return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber);
}
public List<EpisodeFile> GetFilesWithoutMediaInfo()
{ {
return _mediaFileRepository.GetFilesWithoutMediaInfo(); return _mediaFileRepository.GetFilesWithoutMediaInfo();
} }
public List<string> FilterExistingFiles(List<string> files, Series series) public List<string> FilterExistingFiles(List<string> files, Artist artist)
{ {
var seriesFiles = GetFilesBySeries(series.Id).Select(f => Path.Combine(series.Path, f.RelativePath)).ToList(); //var artistFiles = GetFilesByArtist(artist.ForeignArtistId).Select(f => Path.Combine(artist.Path, f.RelativePath)).ToList();
var artistFiles = GetFilesByArtist(artist.Id).Select(f => Path.Combine(artist.Path, f.RelativePath)).ToList();
if (!seriesFiles.Any()) return files; if (!artistFiles.Any()) return files;
return files.Except(seriesFiles, PathEqualityComparer.Instance).ToList(); return files.Except(artistFiles, PathEqualityComparer.Instance).ToList();
} }
public EpisodeFile Get(int id) public TrackFile Get(int id)
{ {
// TODO: Should this be spotifyID or DB Id?
return _mediaFileRepository.Get(id); return _mediaFileRepository.Get(id);
} }
public List<EpisodeFile> Get(IEnumerable<int> ids) public List<TrackFile> Get(IEnumerable<int> ids)
{ {
return _mediaFileRepository.Get(ids).ToList(); return _mediaFileRepository.Get(ids).ToList();
} }
public void HandleAsync(SeriesDeletedEvent message) public void HandleAsync(ArtistDeletedEvent message)
{ {
var files = GetFilesBySeries(message.Series.Id); var files = GetFilesByArtist(message.Artist.Id);
_mediaFileRepository.DeleteMany(files); _mediaFileRepository.DeleteMany(files);
} }
public List<TrackFile> GetFilesByArtist(int artistId)
{
return _mediaFileRepository.GetFilesByArtist(artistId);
}
public List<TrackFile> GetFilesByAlbum(int artistId, int albumId)
{
return _mediaFileRepository.GetFilesByArtist(artistId);
}
} }
} }

View file

@ -1,85 +1,78 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using NLog; using NLog;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.MediaFiles namespace NzbDrone.Core.MediaFiles
{ {
public interface IMediaFileTableCleanupService public interface IMediaFileTableCleanupService
{ {
void Clean(Series series, List<string> filesOnDisk); void Clean(Artist artist, List<string> filesOnDisk);
} }
public class MediaFileTableCleanupService : IMediaFileTableCleanupService public class MediaFileTableCleanupService : IMediaFileTableCleanupService
{ {
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IEpisodeService _episodeService; private readonly ITrackService _trackService;
private readonly Logger _logger; private readonly Logger _logger;
public MediaFileTableCleanupService(IMediaFileService mediaFileService, public MediaFileTableCleanupService(IMediaFileService mediaFileService,
IEpisodeService episodeService, ITrackService trackService,
Logger logger) Logger logger)
{ {
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_episodeService = episodeService; _trackService = trackService;
_logger = logger; _logger = logger;
} }
public void Clean(Series series, List<string> filesOnDisk) public void Clean(Artist artist, List<string> filesOnDisk)
{ {
var seriesFiles = _mediaFileService.GetFilesBySeries(series.Id); var artistFiles = _mediaFileService.GetFilesByArtist(artist.Id);
var episodes = _episodeService.GetEpisodeBySeries(series.Id); var tracks = _trackService.GetTracksByArtist(artist.Id);
var filesOnDiskKeys = new HashSet<string>(filesOnDisk, PathEqualityComparer.Instance); var filesOnDiskKeys = new HashSet<string>(filesOnDisk, PathEqualityComparer.Instance);
foreach (var seriesFile in seriesFiles) foreach (var artistFile in artistFiles)
{ {
var episodeFile = seriesFile; var trackFile = artistFile;
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath); var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
try try
{ {
if (!filesOnDiskKeys.Contains(episodeFilePath)) if (!filesOnDiskKeys.Contains(trackFilePath))
{ {
_logger.Debug("File [{0}] no longer exists on disk, removing from db", episodeFilePath); _logger.Debug("File [{0}] no longer exists on disk, removing from db", trackFilePath);
_mediaFileService.Delete(seriesFile, DeleteMediaFileReason.MissingFromDisk); _mediaFileService.Delete(artistFile, DeleteMediaFileReason.MissingFromDisk);
continue; continue;
} }
if (episodes.None(e => e.EpisodeFileId == episodeFile.Id)) if (tracks.None(e => e.TrackFileId == trackFile.Id))
{ {
_logger.Debug("File [{0}] is not assigned to any episodes, removing from db", episodeFilePath); _logger.Debug("File [{0}] is not assigned to any artist, removing from db", trackFilePath);
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.NoLinkedEpisodes); _mediaFileService.Delete(trackFile, DeleteMediaFileReason.NoLinkedEpisodes);
continue; continue;
} }
// var localEpsiode = _parsingService.GetLocalEpisode(episodeFile.Path, series);
//
// if (localEpsiode == null || episodes.Count != localEpsiode.Episodes.Count)
// {
// _logger.Debug("File [{0}] parsed episodes has changed, removing from db", episodeFile.Path);
// _mediaFileService.Delete(episodeFile);
// continue;
// }
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Error(ex, "Unable to cleanup EpisodeFile in DB: {0}", episodeFile.Id); _logger.Error(ex, "Unable to cleanup EpisodeFile in DB: {0}", trackFile.Id);
} }
} }
foreach (var e in episodes) foreach (var t in tracks)
{ {
var episode = e; var track = t;
if (episode.EpisodeFileId > 0 && seriesFiles.None(f => f.Id == episode.EpisodeFileId)) if (track.TrackFileId > 0 && artistFiles.None(f => f.Id == track.TrackFileId))
{ {
episode.EpisodeFileId = 0; track.TrackFileId = 0;
_episodeService.UpdateEpisode(episode); _trackService.UpdateTrack(track);
} }
} }
} }

View file

@ -7,10 +7,11 @@ using NzbDrone.Core.Tv;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.MediaFiles.MediaInfo namespace NzbDrone.Core.MediaFiles.MediaInfo
{ {
public class UpdateMediaInfoService : IHandle<SeriesScannedEvent> public class UpdateMediaInfoService : IHandle<ArtistScannedEvent>
{ {
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
@ -33,11 +34,11 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
_logger = logger; _logger = logger;
} }
private void UpdateMediaInfo(Series series, List<EpisodeFile> mediaFiles) private void UpdateMediaInfo(Artist artist, List<TrackFile> mediaFiles)
{ {
foreach (var mediaFile in mediaFiles) foreach (var mediaFile in mediaFiles)
{ {
var path = Path.Combine(series.Path, mediaFile.RelativePath); var path = Path.Combine(artist.Path, mediaFile.RelativePath);
if (!_diskProvider.FileExists(path)) if (!_diskProvider.FileExists(path))
{ {
@ -56,7 +57,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
} }
} }
public void Handle(SeriesScannedEvent message) public void Handle(ArtistScannedEvent message)
{ {
if (!_configService.EnableMediaInfo) if (!_configService.EnableMediaInfo)
{ {
@ -64,10 +65,10 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
return; return;
} }
var allMediaFiles = _mediaFileService.GetFilesBySeries(message.Series.Id); var allMediaFiles = _mediaFileService.GetFilesByArtist(message.Artist.Id);
var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < CURRENT_MEDIA_INFO_SCHEMA_REVISION).ToList(); var filteredMediaFiles = allMediaFiles.Where(c => c.MediaInfo == null || c.MediaInfo.SchemaRevision < CURRENT_MEDIA_INFO_SCHEMA_REVISION).ToList();
UpdateMediaInfo(message.Series, filteredMediaFiles); UpdateMediaInfo(message.Artist, filteredMediaFiles);
} }
} }
} }

View file

@ -55,24 +55,28 @@ namespace NzbDrone.Core.MediaFiles
public List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId) public List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId)
{ {
var series = _seriesService.GetSeries(seriesId); // TODO
var episodes = _episodeService.GetEpisodeBySeries(seriesId); throw new NotImplementedException();
var files = _mediaFileService.GetFilesBySeries(seriesId); //var series = _seriesService.GetSeries(seriesId);
//var episodes = _episodeService.GetEpisodeBySeries(seriesId);
//var files = _mediaFileService.GetFilesBySeries(seriesId);
return GetPreviews(series, episodes, files) //return GetPreviews(series, episodes, files)
.OrderByDescending(e => e.SeasonNumber) // .OrderByDescending(e => e.SeasonNumber)
.ThenByDescending(e => e.EpisodeNumbers.First()) // .ThenByDescending(e => e.EpisodeNumbers.First())
.ToList(); // .ToList();
} }
public List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId, int seasonNumber) public List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId, int seasonNumber)
{ {
var series = _seriesService.GetSeries(seriesId); // TODO
var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber); throw new NotImplementedException();
var files = _mediaFileService.GetFilesBySeason(seriesId, seasonNumber); //var series = _seriesService.GetSeries(seriesId);
//var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber);
//var files = _mediaFileService.GetFilesBySeason(seriesId, seasonNumber);
return GetPreviews(series, episodes, files) //return GetPreviews(series, episodes, files)
.OrderByDescending(e => e.EpisodeNumbers.First()).ToList(); // .OrderByDescending(e => e.EpisodeNumbers.First()).ToList();
} }
private IEnumerable<RenameEpisodeFilePreview> GetPreviews(Series series, List<Episode> episodes, List<EpisodeFile> files) private IEnumerable<RenameEpisodeFilePreview> GetPreviews(Series series, List<Episode> episodes, List<EpisodeFile> files)
@ -110,62 +114,68 @@ namespace NzbDrone.Core.MediaFiles
private void RenameFiles(List<EpisodeFile> episodeFiles, Series series) private void RenameFiles(List<EpisodeFile> episodeFiles, Series series)
{ {
var renamed = new List<EpisodeFile>(); // TODO
throw new NotImplementedException();
//var renamed = new List<EpisodeFile>();
foreach (var episodeFile in episodeFiles) //foreach (var episodeFile in episodeFiles)
{ //{
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath); // var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
try // try
{ // {
_logger.Debug("Renaming episode file: {0}", episodeFile); // _logger.Debug("Renaming episode file: {0}", episodeFile);
_episodeFileMover.MoveEpisodeFile(episodeFile, series); // _episodeFileMover.MoveEpisodeFile(episodeFile, series);
_mediaFileService.Update(episodeFile); // _mediaFileService.Update(episodeFile);
renamed.Add(episodeFile); // renamed.Add(episodeFile);
_logger.Debug("Renamed episode file: {0}", episodeFile); // _logger.Debug("Renamed episode file: {0}", episodeFile);
} // }
catch (SameFilenameException ex) // catch (SameFilenameException ex)
{ // {
_logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename); // _logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
} // }
catch (Exception ex) // catch (Exception ex)
{ // {
_logger.Error(ex, "Failed to rename file {0}", episodeFilePath); // _logger.Error(ex, "Failed to rename file {0}", episodeFilePath);
} // }
} //}
if (renamed.Any()) //if (renamed.Any())
{ //{
_diskProvider.RemoveEmptySubfolders(series.Path); // _diskProvider.RemoveEmptySubfolders(series.Path);
_eventAggregator.PublishEvent(new SeriesRenamedEvent(series)); // _eventAggregator.PublishEvent(new SeriesRenamedEvent(series));
} //}
} }
public void Execute(RenameFilesCommand message) public void Execute(RenameFilesCommand message)
{ {
var series = _seriesService.GetSeries(message.SeriesId); // TODO
var episodeFiles = _mediaFileService.Get(message.Files); throw new NotImplementedException();
//var series = _seriesService.GetSeries(message.SeriesId);
//var episodeFiles = _mediaFileService.Get(message.Files);
_logger.ProgressInfo("Renaming {0} files for {1}", episodeFiles.Count, series.Title); //_logger.ProgressInfo("Renaming {0} files for {1}", episodeFiles.Count, series.Title);
RenameFiles(episodeFiles, series); //RenameFiles(episodeFiles, series);
_logger.ProgressInfo("Selected episode files renamed for {0}", series.Title); //_logger.ProgressInfo("Selected episode files renamed for {0}", series.Title);
} }
public void Execute(RenameSeriesCommand message) public void Execute(RenameSeriesCommand message)
{ {
_logger.Debug("Renaming all files for selected series"); // TODO
var seriesToRename = _seriesService.GetSeries(message.SeriesIds); throw new NotImplementedException();
//_logger.Debug("Renaming all files for selected series");
//var seriesToRename = _seriesService.GetSeries(message.SeriesIds);
foreach (var series in seriesToRename) //foreach (var series in seriesToRename)
{ //{
var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id); // var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id);
_logger.ProgressInfo("Renaming all files in series: {0}", series.Title); // _logger.ProgressInfo("Renaming all files in series: {0}", series.Title);
RenameFiles(episodeFiles, series); // RenameFiles(episodeFiles, series);
_logger.ProgressInfo("All episode files renamed for {0}", series.Title); // _logger.ProgressInfo("All episode files renamed for {0}", series.Title);
} //}
} }
} }
} }

View file

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace NzbDrone.Core.MediaFiles
{
public class RenameTrackFilePreview
{
public int ArtistId { get; set; }
public int AlbumId { get; set; }
public List<int> TrackNumbers { get; set; }
public int TrackFileId { get; set; }
public string ExistingPath { get; set; }
public string NewPath { get; set; }
}
}

View file

@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.MediaFiles
{
public interface IRenameTrackFileService
{
List<RenameTrackFilePreview> GetRenamePreviews(int artistId);
List<RenameTrackFilePreview> GetRenamePreviews(int artistId, int albumId);
}
public class RenameTrackFileService : IRenameTrackFileService,
IExecute<RenameFilesCommand>,
IExecute<RenameArtistCommand>
{
private readonly IArtistService _artistService;
private readonly IAlbumService _albumService;
private readonly IMediaFileService _mediaFileService;
private readonly IMoveTrackFiles _trackFileMover;
private readonly IEventAggregator _eventAggregator;
private readonly ITrackService _trackService;
private readonly IBuildFileNames _filenameBuilder;
private readonly IDiskProvider _diskProvider;
private readonly Logger _logger;
public RenameTrackFileService(IArtistService artistService,
IAlbumService albumService,
IMediaFileService mediaFileService,
IMoveTrackFiles trackFileMover,
IEventAggregator eventAggregator,
ITrackService trackService,
IBuildFileNames filenameBuilder,
IDiskProvider diskProvider,
Logger logger)
{
_artistService = artistService;
_albumService = albumService;
_mediaFileService = mediaFileService;
_trackFileMover = trackFileMover;
_eventAggregator = eventAggregator;
_trackService = trackService;
_filenameBuilder = filenameBuilder;
_diskProvider = diskProvider;
_logger = logger;
}
public List<RenameTrackFilePreview> GetRenamePreviews(int artistId)
{
// TODO
throw new NotImplementedException();
//var artist = _artistService.GetArtist(artistId);
//var tracks = _trackService.GetTracksByArtist(artistId);
//var files = _mediaFileService.GetFilesByArtist(artistId);
//return GetPreviews(artist, tracks, files)
// .OrderByDescending(e => e.SeasonNumber)
// .ThenByDescending(e => e.TrackNumbers.First())
// .ToList();
}
public List<RenameTrackFilePreview> GetRenamePreviews(int artistId, int albumId)
{
// TODO
//throw new NotImplementedException();
var artist = _artistService.GetArtist(artistId);
var album = _albumService.GetAlbum(albumId);
var tracks = _trackService.GetTracksByAlbum(artistId, albumId);
var files = _mediaFileService.GetFilesByAlbum(artistId, albumId);
return GetPreviews(artist, album, tracks, files)
.OrderByDescending(e => e.TrackNumbers.First()).ToList();
}
private IEnumerable<RenameTrackFilePreview> GetPreviews(Artist artist, Album album, List<Track> tracks, List<TrackFile> files)
{
foreach (var f in files)
{
var file = f;
var tracksInFile = tracks.Where(e => e.TrackFileId == file.Id).ToList();
var trackFilePath = Path.Combine(artist.Path, file.RelativePath);
if (!tracksInFile.Any())
{
_logger.Warn("File ({0}) is not linked to any tracks", trackFilePath);
continue;
}
var albumId = tracksInFile.First().AlbumId;
var newName = _filenameBuilder.BuildTrackFileName(tracksInFile, artist, album, file);
var newPath = _filenameBuilder.BuildTrackFilePath(artist, album, newName, Path.GetExtension(trackFilePath));
if (!trackFilePath.PathEquals(newPath, StringComparison.Ordinal))
{
yield return new RenameTrackFilePreview
{
ArtistId = artist.Id,
AlbumId = albumId,
TrackNumbers = tracksInFile.Select(e => e.TrackNumber).ToList(),
TrackFileId = file.Id,
ExistingPath = file.RelativePath,
NewPath = artist.Path.GetRelativePath(newPath)
};
}
}
}
private void RenameFiles(List<TrackFile> trackFiles, Artist artist)
{
// TODO
throw new NotImplementedException();
//var renamed = new List<TrackFile>();
//foreach (var trackFile in trackFiles)
//{
// var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
// try
// {
// _logger.Debug("Renaming track file: {0}", trackFile);
// _trackFileMover.MoveTrackFile(trackFile, artist);
// _mediaFileService.Update(trackFile);
// renamed.Add(trackFile);
// _logger.Debug("Renamed track file: {0}", trackFile);
// }
// catch (SameFilenameException ex)
// {
// _logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
// }
// catch (Exception ex)
// {
// _logger.Error(ex, "Failed to rename file {0}", trackFilePath);
// }
//}
//if (renamed.Any())
//{
// _diskProvider.RemoveEmptySubfolders(artist.Path);
// _eventAggregator.PublishEvent(new ArtistRenamedEvent(artist));
//}
}
public void Execute(RenameFilesCommand message)
{
// TODO
throw new NotImplementedException();
//var artist = _artistService.GetArtist(message.ArtistId);
//var trackFiles = _mediaFileService.Get(message.Files);
//_logger.ProgressInfo("Renaming {0} files for {1}", trackFiles.Count, artist.Title);
//RenameFiles(trackFiles, artist);
//_logger.ProgressInfo("Selected track files renamed for {0}", artist.Title);
}
public void Execute(RenameArtistCommand message)
{
// TODO
throw new NotImplementedException();
//_logger.Debug("Renaming all files for selected artist");
//var artistToRename = _artistService.GetArtist(message.ArtistIds);
//foreach (var artist in artistToRename)
//{
// var trackFiles = _mediaFileService.GetFilesByArtist(artist.Id);
// _logger.ProgressInfo("Renaming all files in artist: {0}", artist.Title);
// RenameFiles(trackFiles, artist);
// _logger.ProgressInfo("All track files renamed for {0}", artist.Title);
//}
}
}
}

View file

@ -12,8 +12,10 @@ namespace NzbDrone.Core.MediaFiles
{ {
public class TrackFile : ModelBase public class TrackFile : ModelBase
{ {
public int ItunesTrackId { get; set; } //public string ForeignTrackId { get; set; }
//public string ForeignArtistId { get; set; }
public int AlbumId { get; set; } public int AlbumId { get; set; }
public int ArtistId { get; set; }
public string RelativePath { get; set; } public string RelativePath { get; set; }
public string Path { get; set; } public string Path { get; set; }
public long Size { get; set; } public long Size { get; set; }

View file

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles
{
public class TrackFileMoveResult
{
public TrackFileMoveResult()
{
OldFiles = new List<TrackFile>();
}
public TrackFile TrackFile { get; set; }
public List<TrackFile> OldFiles { get; set; }
}
}

View file

@ -0,0 +1,226 @@
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Music;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles
{
public interface IMoveTrackFiles
{
TrackFile MoveTrackFile(TrackFile trackFile, Artist artist);
TrackFile MoveTrackFile(TrackFile trackFile, LocalTrack localTrack);
TrackFile CopyTrackFile(TrackFile trackFile, LocalTrack localTrack);
}
public class TrackFileMovingService : IMoveTrackFiles
{
private readonly ITrackService _trackService;
//private readonly IUpdateTrackFileService _updateTrackFileService;
private readonly IBuildFileNames _buildFileNames;
private readonly IDiskTransferService _diskTransferService;
private readonly IDiskProvider _diskProvider;
private readonly IMediaFileAttributeService _mediaFileAttributeService;
private readonly IEventAggregator _eventAggregator;
private readonly IConfigService _configService;
private readonly Logger _logger;
public TrackFileMovingService(ITrackService episodeService,
//IUpdateEpisodeFileService updateEpisodeFileService,
IBuildFileNames buildFileNames,
IDiskTransferService diskTransferService,
IDiskProvider diskProvider,
IMediaFileAttributeService mediaFileAttributeService,
IEventAggregator eventAggregator,
IConfigService configService,
Logger logger)
{
_trackService = episodeService;
//_updateTrackFileService = updateEpisodeFileService;
_buildFileNames = buildFileNames;
_diskTransferService = diskTransferService;
_diskProvider = diskProvider;
_mediaFileAttributeService = mediaFileAttributeService;
_eventAggregator = eventAggregator;
_configService = configService;
_logger = logger;
}
public TrackFile MoveTrackFile(TrackFile trackFile, Artist artist)
{
throw new System.NotImplementedException();
// TODO
//var tracks = _trackService.GetTracksByFileId(trackFile.Id);
//var newFileName = _buildFileNames.BuildFileName(tracks, artist, trackFile);
//var filePath = _buildFileNames.BuildFilePath(artist, tracks.First(), trackFile.AlbumId, newFileName, Path.GetExtension(trackFile.RelativePath));
//EnsureAlbumFolder(trackFile, artist, tracks.Select(v => v.Album).First(), filePath);
//_logger.Debug("Renaming track file: {0} to {1}", trackFile, filePath);
//return TransferFile(trackFile, artist, tracks, filePath, TransferMode.Move);
}
public TrackFile MoveTrackFile(TrackFile trackFile, LocalTrack localTrack)
{
// TODO
throw new System.NotImplementedException();
//var newFileName = _buildFileNames.BuildFileName(localEpisode.Episodes, localEpisode.Series, episodeFile);
//var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(localEpisode.Path));
//EnsureEpisodeFolder(episodeFile, localEpisode, filePath);
//_logger.Debug("Moving episode file: {0} to {1}", episodeFile.Path, filePath);
//return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Move);
}
public TrackFile CopyTrackFile(TrackFile trackFile, LocalTrack localTrack)
{
// TODO
throw new System.NotImplementedException();
//var newFileName = _buildFileNames.BuildFileName(localEpisode.Episodes, localEpisode.Series, episodeFile);
//var filePath = _buildFileNames.BuildFilePath(localEpisode.Series, localEpisode.SeasonNumber, newFileName, Path.GetExtension(localEpisode.Path));
//EnsureEpisodeFolder(episodeFile, localEpisode, filePath);
//if (_configService.CopyUsingHardlinks)
//{
// _logger.Debug("Hardlinking episode file: {0} to {1}", episodeFile.Path, filePath);
// return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.HardLinkOrCopy);
//}
//_logger.Debug("Copying episode file: {0} to {1}", episodeFile.Path, filePath);
//return TransferFile(episodeFile, localEpisode.Series, localEpisode.Episodes, filePath, TransferMode.Copy);
}
private EpisodeFile TransferFile(EpisodeFile episodeFile, Series series, List<Episode> episodes, string destinationFilePath, TransferMode mode)
{
// TODO
throw new System.NotImplementedException();
//Ensure.That(episodeFile, () => episodeFile).IsNotNull();
//Ensure.That(series, () => series).IsNotNull();
//Ensure.That(destinationFilePath, () => destinationFilePath).IsValidPath();
//var episodeFilePath = episodeFile.Path ?? Path.Combine(series.Path, episodeFile.RelativePath);
//if (!_diskProvider.FileExists(episodeFilePath))
//{
// throw new FileNotFoundException("Episode file path does not exist", episodeFilePath);
//}
//if (episodeFilePath == destinationFilePath)
//{
// throw new SameFilenameException("File not moved, source and destination are the same", episodeFilePath);
//}
//_diskTransferService.TransferFile(episodeFilePath, destinationFilePath, mode);
//episodeFile.RelativePath = series.Path.GetRelativePath(destinationFilePath);
//_updateTrackFileService.ChangeFileDateForFile(episodeFile, series, episodes);
//try
//{
// _mediaFileAttributeService.SetFolderLastWriteTime(series.Path, episodeFile.DateAdded);
// if (series.SeasonFolder)
// {
// var seasonFolder = Path.GetDirectoryName(destinationFilePath);
// _mediaFileAttributeService.SetFolderLastWriteTime(seasonFolder, episodeFile.DateAdded);
// }
//}
//catch (Exception ex)
//{
// _logger.Warn(ex, "Unable to set last write time");
//}
//_mediaFileAttributeService.SetFilePermissions(destinationFilePath);
//return episodeFile;
}
private void EnsureEpisodeFolder(EpisodeFile episodeFile, LocalEpisode localEpisode, string filePath)
{
EnsureEpisodeFolder(episodeFile, localEpisode.Series, localEpisode.SeasonNumber, filePath);
}
private void EnsureEpisodeFolder(EpisodeFile episodeFile, Series series, int seasonNumber, string filePath)
{
var episodeFolder = Path.GetDirectoryName(filePath);
var seasonFolder = _buildFileNames.BuildSeasonPath(series, seasonNumber);
var seriesFolder = series.Path;
var rootFolder = new OsPath(seriesFolder).Directory.FullPath;
if (!_diskProvider.FolderExists(rootFolder))
{
throw new DirectoryNotFoundException(string.Format("Root folder '{0}' was not found.", rootFolder));
}
var changed = false;
var newEvent = new EpisodeFolderCreatedEvent(series, episodeFile);
if (!_diskProvider.FolderExists(seriesFolder))
{
CreateFolder(seriesFolder);
newEvent.SeriesFolder = seriesFolder;
changed = true;
}
if (seriesFolder != seasonFolder && !_diskProvider.FolderExists(seasonFolder))
{
CreateFolder(seasonFolder);
newEvent.SeasonFolder = seasonFolder;
changed = true;
}
if (seasonFolder != episodeFolder && !_diskProvider.FolderExists(episodeFolder))
{
CreateFolder(episodeFolder);
newEvent.EpisodeFolder = episodeFolder;
changed = true;
}
if (changed)
{
_eventAggregator.PublishEvent(newEvent);
}
}
private void CreateFolder(string directoryName)
{
Ensure.That(directoryName, () => directoryName).IsNotNullOrWhiteSpace();
var parentFolder = new OsPath(directoryName).Directory.FullPath;
if (!_diskProvider.FolderExists(parentFolder))
{
CreateFolder(parentFolder);
}
try
{
_diskProvider.CreateFolder(directoryName);
}
catch (IOException ex)
{
_logger.Error(ex, "Unable to create directory: {0}", directoryName);
}
_mediaFileAttributeService.SetFolderPermissions(directoryName);
}
}
}

View file

@ -0,0 +1,172 @@
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Download;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Qualities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.TrackImport
{
public interface IImportApprovedTracks
{
List<ImportResult> Import(List<ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto);
}
public class ImportApprovedTracks : IImportApprovedTracks
{
private readonly IUpgradeMediaFiles _trackFileUpgrader;
private readonly IMediaFileService _mediaFileService;
//private readonly IExtraService _extraService;
private readonly IDiskProvider _diskProvider;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public ImportApprovedTracks(IUpgradeMediaFiles episodeFileUpgrader,
IMediaFileService mediaFileService,
//IExtraService extraService,
IDiskProvider diskProvider,
IEventAggregator eventAggregator,
Logger logger)
{
_trackFileUpgrader = episodeFileUpgrader;
_mediaFileService = mediaFileService;
// _extraService = extraService;
_diskProvider = diskProvider;
_eventAggregator = eventAggregator;
_logger = logger;
}
public List<ImportResult> Import(List<ImportDecision> decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto)
{
var qualifiedImports = decisions.Where(c => c.Approved)
.GroupBy(c => c.LocalTrack.Artist.Id, (i, s) => s
.OrderByDescending(c => c.LocalTrack.Quality, new QualityModelComparer(s.First().LocalTrack.Artist.Profile))
.ThenByDescending(c => c.LocalTrack.Size))
.SelectMany(c => c)
.ToList();
var importResults = new List<ImportResult>();
foreach (var importDecision in qualifiedImports.OrderBy(e => e.LocalTrack.Tracks.Select(track => track.TrackNumber).MinOrDefault())
.ThenByDescending(e => e.LocalTrack.Size))
{
var localTrack = importDecision.LocalTrack;
var oldFiles = new List<TrackFile>();
try
{
//check if already imported
if (importResults.SelectMany(r => r.ImportDecision.LocalTrack.Tracks)
.Select(e => e.Id)
.Intersect(localTrack.Tracks.Select(e => e.Id))
.Any())
{
importResults.Add(new ImportResult(importDecision, "Track has already been imported"));
continue;
}
var trackFile = new TrackFile();
trackFile.DateAdded = DateTime.UtcNow;
trackFile.ArtistId = localTrack.Artist.Id;
trackFile.Path = localTrack.Path.CleanFilePath();
trackFile.Size = _diskProvider.GetFileSize(localTrack.Path);
trackFile.Quality = localTrack.Quality;
trackFile.MediaInfo = localTrack.MediaInfo;
trackFile.AlbumId = localTrack.Album.Id;
trackFile.Tracks = localTrack.Tracks;
trackFile.ReleaseGroup = localTrack.ParsedTrackInfo.ReleaseGroup;
bool copyOnly;
switch (importMode)
{
default:
case ImportMode.Auto:
copyOnly = downloadClientItem != null && downloadClientItem.IsReadOnly;
break;
case ImportMode.Move:
copyOnly = false;
break;
case ImportMode.Copy:
copyOnly = true;
break;
}
if (newDownload)
{
//trackFile.SceneName = GetSceneName(downloadClientItem, localTrack);
var moveResult = _trackFileUpgrader.UpgradeTrackFile(trackFile, localTrack, copyOnly);
oldFiles = moveResult.OldFiles;
}
else
{
trackFile.RelativePath = localTrack.Artist.Path.GetRelativePath(trackFile.Path);
}
_mediaFileService.Add(trackFile);
importResults.Add(new ImportResult(importDecision));
//if (newDownload)
//{
// _extraService.ImportExtraFiles(localTrack, trackFile, copyOnly); // TODO: Import Music Extras
//}
if (downloadClientItem != null)
{
_eventAggregator.PublishEvent(new TrackImportedEvent(localTrack, trackFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadId, downloadClientItem.IsReadOnly));
}
else
{
_eventAggregator.PublishEvent(new TrackImportedEvent(localTrack, trackFile, newDownload));
}
if (newDownload)
{
_eventAggregator.PublishEvent(new TrackDownloadedEvent(localTrack, trackFile, oldFiles));
}
}
catch (Exception e)
{
_logger.Warn(e, "Couldn't import track " + localTrack);
importResults.Add(new ImportResult(importDecision, "Failed to import episode"));
}
}
//Adding all the rejected decisions
importResults.AddRange(decisions.Where(c => !c.Approved)
.Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray())));
return importResults;
}
//private string GetSceneName(DownloadClientItem downloadClientItem, LocalEpisode localEpisode)
//{
// if (downloadClientItem != null)
// {
// var title = Parser.Parser.RemoveFileExtension(downloadClientItem.Title);
// var parsedTitle = Parser.Parser.ParseTitle(title);
// if (parsedTitle != null && !parsedTitle.FullSeason)
// {
// return title;
// }
// }
// var fileName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath());
// if (SceneChecker.IsSceneTitle(fileName))
// {
// return fileName;
// }
// return null;
//}
}
}

View file

@ -0,0 +1,26 @@
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.MediaFiles.TrackImport
{
public class ImportDecision
{
public LocalTrack LocalTrack { get; private set; }
public IEnumerable<Rejection> Rejections { get; private set; }
public bool Approved => Rejections.Empty();
public object LocalEpisode { get; internal set; }
public ImportDecision(LocalTrack localTrack, params Rejection[] rejections)
{
LocalTrack = localTrack;
Rejections = rejections.ToList();
}
}
}

View file

@ -1,4 +1,4 @@
namespace NzbDrone.Core.MediaFiles.EpisodeImport namespace NzbDrone.Core.MediaFiles.TrackImport
{ {
public enum ImportMode public enum ImportMode
{ {

View file

@ -1,8 +1,10 @@
using System.Collections.Generic; using NzbDrone.Common.EnsureThat;
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Common.EnsureThat; using System.Text;
namespace NzbDrone.Core.MediaFiles.EpisodeImport namespace NzbDrone.Core.MediaFiles.TrackImport
{ {
public class ImportResult public class ImportResult
{ {

View file

@ -1,4 +1,4 @@
namespace NzbDrone.Core.MediaFiles.EpisodeImport namespace NzbDrone.Core.MediaFiles.TrackImport
{ {
public enum ImportResultType public enum ImportResultType
{ {

View file

@ -9,7 +9,8 @@ namespace NzbDrone.Core.MediaFiles
{ {
public interface IUpgradeMediaFiles public interface IUpgradeMediaFiles
{ {
EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false); //EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false);
TrackFileMoveResult UpgradeTrackFile(TrackFile trackFile, LocalTrack localTrack, bool copyOnly = false);
} }
public class UpgradeMediaFileService : IUpgradeMediaFiles public class UpgradeMediaFileService : IUpgradeMediaFiles
@ -17,35 +18,36 @@ namespace NzbDrone.Core.MediaFiles
private readonly IRecycleBinProvider _recycleBinProvider; private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IMediaFileService _mediaFileService; private readonly IMediaFileService _mediaFileService;
private readonly IMoveEpisodeFiles _episodeFileMover; private readonly IMoveEpisodeFiles _episodeFileMover;
private readonly IMoveTrackFiles _trackFileMover;
private readonly IDiskProvider _diskProvider; private readonly IDiskProvider _diskProvider;
private readonly Logger _logger; private readonly Logger _logger;
public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider, public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider,
IMediaFileService mediaFileService, IMediaFileService mediaFileService,
IMoveEpisodeFiles episodeFileMover, IMoveTrackFiles trackFileMover,
IDiskProvider diskProvider, IDiskProvider diskProvider,
Logger logger) Logger logger)
{ {
_recycleBinProvider = recycleBinProvider; _recycleBinProvider = recycleBinProvider;
_mediaFileService = mediaFileService; _mediaFileService = mediaFileService;
_episodeFileMover = episodeFileMover; _trackFileMover = trackFileMover;
_diskProvider = diskProvider; _diskProvider = diskProvider;
_logger = logger; _logger = logger;
} }
public EpisodeFileMoveResult UpgradeEpisodeFile(EpisodeFile episodeFile, LocalEpisode localEpisode, bool copyOnly = false) public TrackFileMoveResult UpgradeTrackFile(TrackFile trackFile, LocalTrack localTrack, bool copyOnly = false)
{ {
var moveFileResult = new EpisodeFileMoveResult(); var moveFileResult = new TrackFileMoveResult();
var existingFiles = localEpisode.Episodes var existingFiles = localTrack.Tracks
.Where(e => e.EpisodeFileId > 0) .Where(e => e.TrackFileId > 0)
.Select(e => e.EpisodeFile.Value) .Select(e => e.TrackFile.Value)
.GroupBy(e => e.Id); .GroupBy(e => e.Id);
foreach (var existingFile in existingFiles) foreach (var existingFile in existingFiles)
{ {
var file = existingFile.First(); var file = existingFile.First();
var episodeFilePath = Path.Combine(localEpisode.Series.Path, file.RelativePath); var episodeFilePath = Path.Combine(localTrack.Artist.Path, file.RelativePath);
var subfolder = _diskProvider.GetParentFolder(localEpisode.Series.Path).GetRelativePath(_diskProvider.GetParentFolder(episodeFilePath)); var subfolder = _diskProvider.GetParentFolder(localTrack.Artist.Path).GetRelativePath(_diskProvider.GetParentFolder(episodeFilePath));
if (_diskProvider.FileExists(episodeFilePath)) if (_diskProvider.FileExists(episodeFilePath))
{ {
@ -59,11 +61,11 @@ namespace NzbDrone.Core.MediaFiles
if (copyOnly) if (copyOnly)
{ {
moveFileResult.EpisodeFile = _episodeFileMover.CopyEpisodeFile(episodeFile, localEpisode); moveFileResult.TrackFile = _trackFileMover.CopyTrackFile(trackFile, localTrack);
} }
else else
{ {
moveFileResult.EpisodeFile = _episodeFileMover.MoveEpisodeFile(episodeFile, localEpisode); moveFileResult.TrackFile = _trackFileMover.MoveTrackFile(trackFile, localTrack);
} }
return moveFileResult; return moveFileResult;

View file

@ -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);
} }
} }

View file

@ -5,22 +5,24 @@ using System.Text;
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{ {
public class AlbumInfoResource public class AlbumResource
{ {
public AlbumInfoResource() public AlbumResource()
{ {
Tracks = new List<TrackResource>();
} }
//public string AlbumType { get; set; } // Might need to make this a separate class //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 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 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 string Id { get; set; } // This is a unique Album ID. Needed for all future API calls
public int Year { get; set; } public DateTime ReleaseDate { get; set; }
public List<ImageResource> Images { get; set; } public List<ImageResource> Images { get; set; }
public string AlbumName { get; set; } // In case of a takedown, this may be empty public string Title { get; set; } // In case of a takedown, this may be empty
public string Overview { get; set; } public string Overview { get; set; }
public List<string> Genres { get; set; } public List<string> Genres { get; set; }
public string Label { get; set; } public string Label { get; set; }
public string Type { get; set; }
public List<TrackResource> Tracks { get; set; }
} }

View file

@ -5,47 +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 int Count { get; set; }
}
public class AlbumResultResource
{
public AlbumResultResource()
{
}
public List<AlbumInfoResource> Items { get; set; }
public int Count { get; set; }
}
public class TrackResultResource
{
public TrackResultResource()
{
}
public List<TrackInfoResource> Items { get; set; }
public int Count { 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; }
} }
} }

View file

@ -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()
{ {
} }
@ -19,7 +19,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
public string TrackName { 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; }
} }
} }

View file

@ -74,14 +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 LidarrAPI.MetadataID of {0}", foreignArtistId);
// 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", "artist/" + foreignArtistId)
//.SetSegment("route", "search") //.SetSegment("route", "search")
//.AddQueryParam("type", "artist,album") //.AddQueryParam("type", "artist,album")
//.AddQueryParam("q", spotifyId.ToString()) //.AddQueryParam("q", spotifyId.ToString())
@ -99,7 +99,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
{ {
if (httpResponse.StatusCode == HttpStatusCode.NotFound) if (httpResponse.StatusCode == HttpStatusCode.NotFound)
{ {
throw new ArtistNotFoundException(spotifyId); throw new ArtistNotFoundException(foreignArtistId);
} }
else else
{ {
@ -108,92 +108,95 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
} }
// It is safe to assume an id will only return one Artist back // It is safe to assume an id will only return one Artist back
Artist artist = new Artist(); var albums = httpResponse.Resource.Albums.Select(MapAlbum);
artist.ArtistName = httpResponse.Resource.Artists.Items[0].ArtistName; var artist = MapArtist(httpResponse.Resource);
artist.SpotifyId = httpResponse.Resource.Artists.Items[0].Id;
artist.Genres = httpResponse.Resource.Artists.Items[0].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.AlbumName;
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.TrackName;
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)
@ -203,7 +206,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
var lowerTitle = title.ToLowerInvariant(); var lowerTitle = title.ToLowerInvariant();
Console.WriteLine("Searching for " + lowerTitle); Console.WriteLine("Searching for " + lowerTitle);
if (lowerTitle.StartsWith("itunes:") || lowerTitle.StartsWith("itunesid:")) if (lowerTitle.StartsWith("spotify:") || lowerTitle.StartsWith("spotifyid:"))
{ {
var slug = lowerTitle.Split(':')[1].Trim(); var slug = lowerTitle.Split(':')[1].Trim();
@ -224,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") // TODO: LidarrAPI.Metadata is getting , encoded. Needs to be raw , .AddQueryParam("type", "artist")
.AddQueryParam("query", 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)
{ {
@ -248,27 +265,50 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
} }
} }
private List<Artist> MapArtists(ArtistResource resource) private static Album MapAlbum(AlbumResource resource)
{ {
Album album = new Album();
album.Title = resource.Title;
album.ForeignAlbumId = resource.Id;
album.ReleaseDate = resource.ReleaseDate;
album.CleanTitle = Parser.Parser.CleanArtistTitle(album.Title);
album.AlbumType = resource.Type;
var tracks = resource.Tracks.Select(MapTrack);
album.Tracks = tracks.ToList();
List<Artist> artists = new List<Artist>(); return album;
foreach(var artistResource in resource.Artists.Items)
{
Artist artist = new Artist();
artist.ArtistName = artistResource.ArtistName;
artist.SpotifyId = artistResource.Id; // TODO: Rename spotifyId to LidarrId
artist.Genres = artistResource.Genres;
artist.ArtistSlug = Parser.Parser.CleanArtistTitle(artist.ArtistName);
//artist.Images = artistResource.Images;
artists.Add(artist);
} }
private static Track MapTrack(TrackResource resource)
{
Track track = new Track();
track.Title = resource.TrackName;
track.ForeignTrackId = resource.Id;
track.TrackNumber = resource.TrackNumber;
return track;
}
private static Artist MapArtist(ArtistResource resource)
{
Artist artist = new Artist();
artist.Name = resource.ArtistName;
artist.ForeignArtistId = resource.Id;
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)

View file

@ -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 (ArtistNotFoundException) 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)
}); });
} }

View file

@ -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
} }
} }
} }

View file

@ -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());
}
} }
} }

View 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();
}
}
}

View 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;
}
}
}

View file

@ -22,21 +22,23 @@ namespace NzbDrone.Core.Music
} }
public string SpotifyId { get; set; } public string ForeignArtistId { get; set; }
public string ArtistName { get; set; } public string MBId { get; set; }
public string ArtistSlug { get; set; } public int TADBId { get; set; }
public string CleanTitle { get; set; } public int DiscogsId { get; set; }
public string AMId { get; set; }
public string Name { get; set; }
public string NameSlug { 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 +49,20 @@ 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; MBId = otherArtist.MBId;
ArtistSlug = otherArtist.ArtistSlug; TADBId = otherArtist.TADBId;
CleanTitle = otherArtist.CleanTitle; DiscogsId = otherArtist.DiscogsId;
AMId = otherArtist.AMId;
Name = otherArtist.Name;
NameSlug = otherArtist.NameSlug;
CleanName = otherArtist.CleanName;
Monitored = otherArtist.Monitored; Monitored = otherArtist.Monitored;
AlbumFolder = otherArtist.AlbumFolder; AlbumFolder = otherArtist.AlbumFolder;
LastInfoSync = otherArtist.LastInfoSync; LastInfoSync = otherArtist.LastInfoSync;
@ -69,7 +75,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;

View file

@ -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();
} }
} }

View file

@ -89,11 +89,12 @@ namespace NzbDrone.Core.Music
return _artistRepository.All().ToList(); return _artistRepository.All().ToList();
} }
public Artist GetArtist(int artistId) public Artist GetArtist(int artistDBId)
{ {
return _artistRepository.Get(artistId); return _artistRepository.Get(artistDBId);
} }
public List<Artist> GetArtists(IEnumerable<int> artistIds) public List<Artist> GetArtists(IEnumerable<int> artistIds)
{ {
return _artistRepository.Get(artistIds).ToList(); return _artistRepository.Get(artistIds).ToList();
@ -110,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.Id, album.Id, album.Monitored);
} }
} }
@ -129,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);
} }
} }

View file

@ -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);
} }
} }
} }

View 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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View file

@ -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);
} }

View 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);
}
}
}

View file

@ -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);
//TODO: _diskScanService.Scan(artist); _diskScanService.Scan(artist);
} }
catch (Exception e) catch (Exception e)
{ {

View file

@ -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,23 @@ 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); // TODO: JOE: I believe this should be string, string
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 +53,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 +74,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 +91,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 +111,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);
} }

View file

@ -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;
} }

View file

@ -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 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());
} }
} }
} }

View file

@ -13,15 +13,15 @@ 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);
void SetMonitoredByAlbum(string artistId, string albumId, bool monitored); void SetMonitoredByAlbum(int artistId, int albumId, bool monitored);
void SetFileId(int trackId, int fileId); void SetFileId(int trackId, int fileId);
} }
@ -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.Join<Track, Artist>(JoinType.Inner, s => s.Artist, (track, artist) => track.ArtistId == artist.Id).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)
@ -85,7 +85,7 @@ namespace NzbDrone.Core.Music
SetFields(track, p => p.Monitored); SetFields(track, p => p.Monitored);
} }
public void SetMonitoredByAlbum(string artistId, string albumId, bool monitored) public void SetMonitoredByAlbum(int artistId, int albumId, bool monitored)
{ {
var mapper = _database.GetDataMapper(); var mapper = _database.GetDataMapper();
@ -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)

View file

@ -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);
@ -29,7 +29,7 @@ namespace NzbDrone.Core.Music
void InsertMany(List<Track> tracks); void InsertMany(List<Track> tracks);
void UpdateMany(List<Track> tracks); void UpdateMany(List<Track> tracks);
void DeleteMany(List<Track> tracks); void DeleteMany(List<Track> tracks);
void SetTrackMonitoredByAlbum(string artistId, string albumId, bool monitored); void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored);
} }
public class TrackService : ITrackService public class TrackService : ITrackService
@ -55,22 +55,22 @@ namespace NzbDrone.Core.Music
return _trackRepository.Get(ids).ToList(); return _trackRepository.Get(ids).ToList();
} }
public Track FindTrack(string artistId, string albumId, int episodeNumber) public Track FindTrack(int artistId, int albumId, int trackNumber)
{ {
return _trackRepository.Find(artistId, albumId, episodeNumber); return _trackRepository.Find(artistId, albumId, trackNumber);
} }
public List<Track> GetTracksByArtist(string artistId) public List<Track> GetTracksByArtist(int 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(".", " ");
@ -96,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);
} }
@ -127,12 +127,12 @@ namespace NzbDrone.Core.Music
_logger.Debug("Monitored flag for Track:{0} was set to {1}", trackId, monitored); _logger.Debug("Monitored flag for Track:{0} was set to {1}", trackId, monitored);
} }
public void SetTrackMonitoredByAlbum(string artistId, string albumId, bool monitored) public void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored)
{ {
_trackRepository.SetMonitoredByAlbum(artistId, albumId, monitored); _trackRepository.SetMonitoredByAlbum(artistId, albumId, monitored);
} }
public void UpdateEpisodes(List<Track> tracks) public void UpdateTracks(List<Track> tracks)
{ {
_trackRepository.UpdateMany(tracks); _trackRepository.UpdateMany(tracks);
} }
@ -154,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);
} }
@ -182,10 +182,5 @@ namespace NzbDrone.Core.Music
_logger.Debug("Linking [{0}] > [{1}]", message.TrackFile.RelativePath, track); _logger.Debug("Linking [{0}] > [{1}]", message.TrackFile.RelativePath, track);
} }
} }
public void UpdateTracks(List<Track> tracks)
{
_trackRepository.UpdateMany(tracks);
}
} }
} }

View file

@ -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" />
@ -725,7 +724,13 @@
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" /> <Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" /> <Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" /> <Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportMode.cs" /> <Compile Include="MediaFiles\Commands\RenameArtistCommand.cs" />
<Compile Include="MediaFiles\Events\TrackDownloadedEvent.cs" />
<Compile Include="MediaFiles\RenameTrackFilePreview.cs" />
<Compile Include="MediaFiles\RenameTrackFileService.cs" />
<Compile Include="MediaFiles\TrackFileMovingService.cs" />
<Compile Include="MediaFiles\TrackFileMoveResult.cs" />
<Compile Include="MediaFiles\TrackImport\ImportMode.cs" />
<Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" /> <Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameSeriesCommand.cs" /> <Compile Include="MediaFiles\Commands\RenameSeriesCommand.cs" />
<Compile Include="MediaFiles\Commands\RescanSeriesCommand.cs" /> <Compile Include="MediaFiles\Commands\RescanSeriesCommand.cs" />
@ -740,12 +745,10 @@
<Compile Include="MediaFiles\EpisodeFile.cs" /> <Compile Include="MediaFiles\EpisodeFile.cs" />
<Compile Include="MediaFiles\EpisodeFileMoveResult.cs" /> <Compile Include="MediaFiles\EpisodeFileMoveResult.cs" />
<Compile Include="MediaFiles\EpisodeFileMovingService.cs" /> <Compile Include="MediaFiles\EpisodeFileMovingService.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportResult.cs" />
<Compile Include="MediaFiles\EpisodeImport\IImportDecisionEngineSpecification.cs" /> <Compile Include="MediaFiles\EpisodeImport\IImportDecisionEngineSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportApprovedEpisodes.cs" /> <Compile Include="MediaFiles\EpisodeImport\ImportApprovedEpisodes.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportDecision.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportDecisionMaker.cs" /> <Compile Include="MediaFiles\EpisodeImport\ImportDecisionMaker.cs" />
<Compile Include="MediaFiles\EpisodeImport\ImportResultType.cs" /> <Compile Include="MediaFiles\TrackImport\ImportResultType.cs" />
<Compile Include="MediaFiles\EpisodeImport\Manual\ManualImportFile.cs" /> <Compile Include="MediaFiles\EpisodeImport\Manual\ManualImportFile.cs" />
<Compile Include="MediaFiles\EpisodeImport\Manual\ManualImportCommand.cs" /> <Compile Include="MediaFiles\EpisodeImport\Manual\ManualImportCommand.cs" />
<Compile Include="MediaFiles\EpisodeImport\Manual\ManualImportItem.cs" /> <Compile Include="MediaFiles\EpisodeImport\Manual\ManualImportItem.cs" />
@ -761,11 +764,14 @@
<Compile Include="MediaFiles\EpisodeImport\Specifications\UnverifiedSceneNumberingSpecification.cs" /> <Compile Include="MediaFiles\EpisodeImport\Specifications\UnverifiedSceneNumberingSpecification.cs" />
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecification.cs" /> <Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecification.cs" />
<Compile Include="MediaFiles\Events\ArtistRenamedEvent.cs" /> <Compile Include="MediaFiles\Events\ArtistRenamedEvent.cs" />
<Compile Include="MediaFiles\Events\ArtistScannedEvent.cs" />
<Compile Include="MediaFiles\Events\ArtistScanSkippedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeDownloadedEvent.cs" /> <Compile Include="MediaFiles\Events\EpisodeDownloadedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeFileAddedEvent.cs" /> <Compile Include="MediaFiles\Events\EpisodeFileAddedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeFileDeletedEvent.cs" /> <Compile Include="MediaFiles\Events\EpisodeFileDeletedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeFolderCreatedEvent.cs" /> <Compile Include="MediaFiles\Events\EpisodeFolderCreatedEvent.cs" />
<Compile Include="MediaFiles\Events\EpisodeImportedEvent.cs" /> <Compile Include="MediaFiles\Events\EpisodeImportedEvent.cs" />
<Compile Include="MediaFiles\Commands\RescanArtistCommand.cs" />
<Compile Include="MediaFiles\Events\SeriesRenamedEvent.cs" /> <Compile Include="MediaFiles\Events\SeriesRenamedEvent.cs" />
<Compile Include="MediaFiles\Events\SeriesScanSkippedEvent.cs" /> <Compile Include="MediaFiles\Events\SeriesScanSkippedEvent.cs" />
<Compile Include="MediaFiles\Events\SeriesScannedEvent.cs" /> <Compile Include="MediaFiles\Events\SeriesScannedEvent.cs" />
@ -789,6 +795,9 @@
<Compile Include="MediaFiles\RenameEpisodeFileService.cs" /> <Compile Include="MediaFiles\RenameEpisodeFileService.cs" />
<Compile Include="MediaFiles\SameFilenameException.cs" /> <Compile Include="MediaFiles\SameFilenameException.cs" />
<Compile Include="MediaFiles\TrackFile.cs" /> <Compile Include="MediaFiles\TrackFile.cs" />
<Compile Include="MediaFiles\TrackImport\ImportApprovedTracks.cs" />
<Compile Include="MediaFiles\TrackImport\ImportDecision.cs" />
<Compile Include="MediaFiles\TrackImport\ImportResult.cs" />
<Compile Include="MediaFiles\UpdateEpisodeFileService.cs" /> <Compile Include="MediaFiles\UpdateEpisodeFileService.cs" />
<Compile Include="MediaFiles\UpgradeMediaFileService.cs" /> <Compile Include="MediaFiles\UpgradeMediaFileService.cs" />
<Compile Include="Messaging\Commands\BackendCommandAttribute.cs" /> <Compile Include="Messaging\Commands\BackendCommandAttribute.cs" />
@ -816,7 +825,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" />
@ -825,7 +834,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" />
@ -857,17 +866,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" />
@ -928,6 +944,7 @@
<Compile Include="Parser\IsoLanguage.cs" /> <Compile Include="Parser\IsoLanguage.cs" />
<Compile Include="Parser\IsoLanguages.cs" /> <Compile Include="Parser\IsoLanguages.cs" />
<Compile Include="Parser\LanguageParser.cs" /> <Compile Include="Parser\LanguageParser.cs" />
<Compile Include="Parser\Model\ArtistTitleInfo.cs" />
<Compile Include="Parser\Model\LocalTrack.cs" /> <Compile Include="Parser\Model\LocalTrack.cs" />
<Compile Include="Parser\Model\ParsedTrackInfo.cs" /> <Compile Include="Parser\Model\ParsedTrackInfo.cs" />
<Compile Include="Profiles\Delay\DelayProfile.cs" /> <Compile Include="Profiles\Delay\DelayProfile.cs" />

View file

@ -18,10 +18,15 @@ 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 BuildTrackFilePath(Artist artist, Album album, string fileName, string extension);
string BuildSeasonPath(Series series, int seasonNumber); string BuildSeasonPath(Series series, int seasonNumber);
string BuildAlbumPath(Artist artist, Album album);
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 +47,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 +67,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 +154,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();
@ -149,6 +204,15 @@ namespace NzbDrone.Core.Organizer
return Path.Combine(path, fileName + extension); return Path.Combine(path, fileName + extension);
} }
public string BuildTrackFilePath(Artist artist, Album album, string fileName, string extension)
{
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
var path = BuildAlbumPath(artist, album);
return Path.Combine(path, fileName + extension);
}
public string BuildSeasonPath(Series series, int seasonNumber) public string BuildSeasonPath(Series series, int seasonNumber)
{ {
var path = series.Path; var path = series.Path;
@ -172,6 +236,24 @@ namespace NzbDrone.Core.Organizer
return path; return path;
} }
public string BuildAlbumPath(Artist artist, Album album)
{
var path = artist.Path;
if (artist.AlbumFolder)
{
var albumFolder = GetAlbumFolder(artist, album);
albumFolder = CleanFileName(albumFolder);
path = Path.Combine(path, albumFolder);
}
return path;
}
public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec)
{ {
var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault(); var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault();
@ -232,6 +314,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 +343,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 +395,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 +550,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 +563,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 +582,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 +789,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 +885,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 +950,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 +970,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

View file

@ -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;
}
}
} }
} }

View file

@ -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()

View file

@ -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);

View file

@ -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; }

View file

@ -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; }
} }
} }

View file

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.Parser.Model
{
public class ArtistTitleInfo
{
public string Title { get; set; }
public string TitleWithoutYear { get; set; }
public int Year { get; set; }
}
}

View file

@ -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()
{ {

View file

@ -9,88 +9,35 @@ namespace NzbDrone.Core.Parser.Model
{ {
public class ParsedTrackInfo public class ParsedTrackInfo
{ {
// [TODO]: Properly fill this out
public string ArtistTitle { get; set; } public string ArtistTitle { get; set; }
public string AlbumTitle { get; set; } public string AlbumTitle { get; set; }
public SeriesTitleInfo SeriesTitleInfo { get; set; } public ArtistTitleInfo ArtistTitleInfo { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public int SeasonNumber { get; set; } public string AlbumId { get; set; } // maybe
public int[] EpisodeNumbers { get; set; } public int[] TrackNumbers { get; set; }
public int[] AbsoluteEpisodeNumbers { get; set; } //public Language Language { get; set; }
public string AirDate { get; set; }
public Language Language { get; set; }
public bool FullSeason { get; set; }
public bool Special { get; set; }
public string ReleaseGroup { get; set; } public string ReleaseGroup { get; set; }
public string ReleaseHash { get; set; } public string ReleaseHash { get; set; }
public ParsedTrackInfo() public ParsedTrackInfo()
{ {
EpisodeNumbers = new int[0]; TrackNumbers = new int[0];
AbsoluteEpisodeNumbers = new int[0];
} }
public bool IsDaily
{
get
{
return !string.IsNullOrWhiteSpace(AirDate);
}
//This prevents manually downloading a release from blowing up in mono
//TODO: Is there a better way?
private set { }
}
public bool IsAbsoluteNumbering
{
get
{
return AbsoluteEpisodeNumbers.Any();
}
//This prevents manually downloading a release from blowing up in mono
//TODO: Is there a better way?
private set { }
}
public bool IsPossibleSpecialEpisode
{
get
{
// if we don't have eny episode numbers we are likely a special episode and need to do a search by episode title
return (AirDate.IsNullOrWhiteSpace() &&
ArtistTitle.IsNullOrWhiteSpace() &&
(EpisodeNumbers.Length == 0 || SeasonNumber == 0) ||
!ArtistTitle.IsNullOrWhiteSpace() && Special);
}
//This prevents manually downloading a release from blowing up in mono
//TODO: Is there a better way?
private set { }
}
public override string ToString() public override string ToString()
{ {
string episodeString = "[Unknown Episode]"; string episodeString = "[Unknown Track]";
if (IsDaily && EpisodeNumbers.Empty())
if (TrackNumbers != null && TrackNumbers.Any())
{ {
episodeString = string.Format("{0}", AirDate); episodeString = string.Format("T{1}", string.Join("-", TrackNumbers.Select(c => c.ToString("00"))));
}
else if (FullSeason)
{
episodeString = string.Format("Season {0:00}", SeasonNumber);
}
else if (EpisodeNumbers != null && EpisodeNumbers.Any())
{
episodeString = string.Format("S{0:00}E{1}", SeasonNumber, string.Join("-", EpisodeNumbers.Select(c => c.ToString("00"))));
}
else if (AbsoluteEpisodeNumbers != null && AbsoluteEpisodeNumbers.Any())
{
episodeString = string.Format("{0}", string.Join("-", AbsoluteEpisodeNumbers.Select(c => c.ToString("000"))));
} }
return string.Format("{0} - {1} {2}", ArtistTitle, episodeString, Quality); return string.Format("{0} - {1} {2}", ArtistTitle, episodeString, Quality);
} }
} }

View 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;
}
}
}

Some files were not shown because too many files have changed in this diff Show more