mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-20 21:43:33 -07:00
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:
commit
2c7398ac66
132 changed files with 3582 additions and 1343 deletions
|
@ -35,10 +35,13 @@ namespace NzbDrone.Api.Config
|
|||
|
||||
SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 5);
|
||||
SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat();
|
||||
SharedValidator.RuleFor(c => c.StandardTrackFormat).ValidTrackFormat();
|
||||
SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat();
|
||||
SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat();
|
||||
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
|
||||
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
|
||||
SharedValidator.RuleFor(c => c.ArtistFolderFormat).ValidArtistFolderFormat();
|
||||
SharedValidator.RuleFor(c => c.AlbumFolderFormat).ValidAlbumFolderFormat();
|
||||
}
|
||||
|
||||
private void UpdateNamingConfig(NamingConfigResource resource)
|
||||
|
@ -74,6 +77,7 @@ namespace NzbDrone.Api.Config
|
|||
var sampleResource = new NamingSampleResource();
|
||||
|
||||
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
|
||||
var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec);
|
||||
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
|
||||
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
|
||||
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
|
||||
|
@ -83,6 +87,10 @@ namespace NzbDrone.Api.Config
|
|||
? "Invalid format"
|
||||
: singleEpisodeSampleResult.FileName;
|
||||
|
||||
sampleResource.SingleTrackExample = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult) != null
|
||||
? "Invalid format"
|
||||
: singleTrackSampleResult.FileName;
|
||||
|
||||
sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null
|
||||
? "Invalid format"
|
||||
: multiEpisodeSampleResult.FileName;
|
||||
|
@ -107,18 +115,28 @@ namespace NzbDrone.Api.Config
|
|||
? "Invalid format"
|
||||
: _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();
|
||||
}
|
||||
|
||||
private void ValidateFormatResult(NamingConfig nameSpec)
|
||||
{
|
||||
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
|
||||
var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec);
|
||||
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
|
||||
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
|
||||
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
|
||||
var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec);
|
||||
|
||||
var singleEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult);
|
||||
var singleTrackValidationResult = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult);
|
||||
var multiEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult);
|
||||
var dailyEpisodeValidationResult = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult);
|
||||
var animeEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult);
|
||||
|
@ -127,6 +145,7 @@ namespace NzbDrone.Api.Config
|
|||
var validationFailures = new List<ValidationFailure>();
|
||||
|
||||
validationFailures.AddIfNotNull(singleEpisodeValidationResult);
|
||||
validationFailures.AddIfNotNull(singleTrackValidationResult);
|
||||
validationFailures.AddIfNotNull(multiEpisodeValidationResult);
|
||||
validationFailures.AddIfNotNull(dailyEpisodeValidationResult);
|
||||
validationFailures.AddIfNotNull(animeEpisodeValidationResult);
|
||||
|
|
|
@ -6,13 +6,17 @@ namespace NzbDrone.Api.Config
|
|||
public class NamingConfigResource : RestResource
|
||||
{
|
||||
public bool RenameEpisodes { get; set; }
|
||||
public bool RenameTracks { get; set; }
|
||||
public bool ReplaceIllegalCharacters { get; set; }
|
||||
public int MultiEpisodeStyle { get; set; }
|
||||
public string StandardEpisodeFormat { get; set; }
|
||||
public string StandardTrackFormat { get; set; }
|
||||
public string DailyEpisodeFormat { get; set; }
|
||||
public string AnimeEpisodeFormat { get; set; }
|
||||
public string SeriesFolderFormat { get; set; }
|
||||
public string SeasonFolderFormat { get; set; }
|
||||
public string ArtistFolderFormat { get; set; }
|
||||
public string AlbumFolderFormat { get; set; }
|
||||
public bool IncludeSeriesTitle { get; set; }
|
||||
public bool IncludeEpisodeTitle { get; set; }
|
||||
public bool IncludeQuality { get; set; }
|
||||
|
@ -30,13 +34,17 @@ namespace NzbDrone.Api.Config
|
|||
Id = model.Id,
|
||||
|
||||
RenameEpisodes = model.RenameEpisodes,
|
||||
RenameTracks = model.RenameTracks,
|
||||
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
|
||||
MultiEpisodeStyle = model.MultiEpisodeStyle,
|
||||
StandardEpisodeFormat = model.StandardEpisodeFormat,
|
||||
StandardTrackFormat = model.StandardTrackFormat,
|
||||
DailyEpisodeFormat = model.DailyEpisodeFormat,
|
||||
AnimeEpisodeFormat = model.AnimeEpisodeFormat,
|
||||
SeriesFolderFormat = model.SeriesFolderFormat,
|
||||
SeasonFolderFormat = model.SeasonFolderFormat
|
||||
SeasonFolderFormat = model.SeasonFolderFormat,
|
||||
ArtistFolderFormat = model.ArtistFolderFormat,
|
||||
AlbumFolderFormat = model.AlbumFolderFormat
|
||||
//IncludeSeriesTitle
|
||||
//IncludeEpisodeTitle
|
||||
//IncludeQuality
|
||||
|
@ -63,13 +71,17 @@ namespace NzbDrone.Api.Config
|
|||
Id = resource.Id,
|
||||
|
||||
RenameEpisodes = resource.RenameEpisodes,
|
||||
RenameTracks = resource.RenameTracks,
|
||||
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
|
||||
MultiEpisodeStyle = resource.MultiEpisodeStyle,
|
||||
StandardEpisodeFormat = resource.StandardEpisodeFormat,
|
||||
StandardTrackFormat = resource.StandardTrackFormat,
|
||||
DailyEpisodeFormat = resource.DailyEpisodeFormat,
|
||||
AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
|
||||
SeriesFolderFormat = resource.SeriesFolderFormat,
|
||||
SeasonFolderFormat = resource.SeasonFolderFormat
|
||||
SeasonFolderFormat = resource.SeasonFolderFormat,
|
||||
ArtistFolderFormat = resource.ArtistFolderFormat,
|
||||
AlbumFolderFormat = resource.AlbumFolderFormat
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
public class NamingSampleResource
|
||||
{
|
||||
public string SingleEpisodeExample { get; set; }
|
||||
public string SingleTrackExample { get; set; }
|
||||
public string MultiEpisodeExample { get; set; }
|
||||
public string DailyEpisodeExample { get; set; }
|
||||
public string AnimeEpisodeExample { get; set; }
|
||||
public string AnimeMultiEpisodeExample { get; set; }
|
||||
public string SeriesFolderExample { get; set; }
|
||||
public string SeasonFolderExample { get; set; }
|
||||
public string ArtistFolderExample { get; set; }
|
||||
public string AlbumFolderExample { get; set; }
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ using NzbDrone.Core.Messaging.Events;
|
|||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.SignalR;
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Api.EpisodeFiles
|
||||
{
|
||||
|
@ -47,24 +48,26 @@ namespace NzbDrone.Api.EpisodeFiles
|
|||
|
||||
private EpisodeFileResource GetEpisodeFile(int id)
|
||||
{
|
||||
var episodeFile = _mediaFileService.Get(id);
|
||||
var series = _seriesService.GetSeries(episodeFile.SeriesId);
|
||||
throw new NotImplementedException();
|
||||
//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()
|
||||
{
|
||||
if (!Request.Query.SeriesId.HasValue)
|
||||
{
|
||||
throw new BadRequestException("seriesId is missing");
|
||||
}
|
||||
throw new NotImplementedException();
|
||||
//if (!Request.Query.SeriesId.HasValue)
|
||||
//{
|
||||
// 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)
|
||||
|
@ -76,14 +79,15 @@ namespace NzbDrone.Api.EpisodeFiles
|
|||
|
||||
private void DeleteEpisodeFile(int id)
|
||||
{
|
||||
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));
|
||||
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);
|
||||
//_logger.Info("Deleting episode file: {0}", fullPath);
|
||||
//_recycleBinProvider.DeleteFile(fullPath, subfolder);
|
||||
//_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual);
|
||||
}
|
||||
|
||||
public void Handle(EpisodeFileAddedEvent message)
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace NzbDrone.Api.Music
|
|||
public string AlbumId { get; set; }
|
||||
public string AlbumName { get; set; }
|
||||
public bool Monitored { get; set; }
|
||||
public int Year { get; set; }
|
||||
public DateTime ReleaseDate { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public string ArtworkUrl { get; set; }
|
||||
|
||||
|
@ -25,12 +25,12 @@ namespace NzbDrone.Api.Music
|
|||
|
||||
return new AlbumResource
|
||||
{
|
||||
AlbumId = model.AlbumId,
|
||||
AlbumId = model.ForeignAlbumId,
|
||||
Monitored = model.Monitored,
|
||||
Year = model.Year,
|
||||
ReleaseDate = model.ReleaseDate,
|
||||
AlbumName = model.Title,
|
||||
Genres = model.Genres,
|
||||
ArtworkUrl = model.ArtworkUrl
|
||||
//ArtworkUrl = model.ArtworkUrl
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -40,12 +40,12 @@ namespace NzbDrone.Api.Music
|
|||
|
||||
return new Album
|
||||
{
|
||||
AlbumId = resource.AlbumId,
|
||||
ForeignAlbumId = resource.AlbumId,
|
||||
Monitored = resource.Monitored,
|
||||
Year = resource.Year,
|
||||
ReleaseDate = resource.ReleaseDate,
|
||||
Title = resource.AlbumName,
|
||||
Genres = resource.Genres,
|
||||
ArtworkUrl = resource.ArtworkUrl
|
||||
//ArtworkUrl = resource.ArtworkUrl
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ namespace NzbDrone.Api.Music
|
|||
|
||||
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
|
||||
PostValidator.RuleFor(s => s.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();
|
||||
}
|
||||
|
@ -144,14 +144,14 @@ namespace NzbDrone.Api.Music
|
|||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
|
|
@ -18,8 +18,12 @@ namespace NzbDrone.Api.Music
|
|||
|
||||
|
||||
//View Only
|
||||
public string ArtistName { get; set; }
|
||||
public string SpotifyId { get; set; }
|
||||
public string Name { 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 int AlbumCount
|
||||
|
@ -53,13 +57,13 @@ namespace NzbDrone.Api.Music
|
|||
public bool Monitored { get; set; }
|
||||
|
||||
public string RootFolderPath { get; set; }
|
||||
public string Certification { get; set; }
|
||||
//public string Certification { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public HashSet<int> Tags { get; set; }
|
||||
public DateTime Added { get; set; }
|
||||
public AddSeriesOptions AddOptions { get; set; }
|
||||
public Ratings Ratings { get; set; }
|
||||
public string ArtistSlug { get; internal set; }
|
||||
public string NameSlug { get; set; }
|
||||
}
|
||||
|
||||
public static class ArtistResourceMapper
|
||||
|
@ -71,8 +75,11 @@ namespace NzbDrone.Api.Music
|
|||
return new ArtistResource
|
||||
{
|
||||
Id = model.Id,
|
||||
|
||||
ArtistName = model.ArtistName,
|
||||
MBId = model.MBId,
|
||||
TADBId = model.TADBId,
|
||||
DiscogsId = model.DiscogsId,
|
||||
AMId = model.AMId,
|
||||
Name = model.Name,
|
||||
//AlternateTitles
|
||||
//SortTitle = resource.SortTitle,
|
||||
|
||||
|
@ -94,7 +101,6 @@ namespace NzbDrone.Api.Music
|
|||
Path = model.Path,
|
||||
ProfileId = model.ProfileId,
|
||||
|
||||
ArtistFolder = model.ArtistFolder,
|
||||
Monitored = model.Monitored,
|
||||
|
||||
//UseSceneNumbering = resource.UseSceneNumbering,
|
||||
|
@ -105,8 +111,8 @@ namespace NzbDrone.Api.Music
|
|||
//FirstAired = resource.FirstAired,
|
||||
//LastInfoSync = resource.LastInfoSync,
|
||||
//SeriesType = resource.SeriesType,
|
||||
SpotifyId = model.SpotifyId,
|
||||
ArtistSlug = model.ArtistSlug,
|
||||
ForeignArtistId = model.ForeignArtistId,
|
||||
NameSlug = model.NameSlug,
|
||||
|
||||
RootFolderPath = model.RootFolderPath,
|
||||
Genres = model.Genres,
|
||||
|
@ -125,10 +131,13 @@ namespace NzbDrone.Api.Music
|
|||
{
|
||||
Id = resource.Id,
|
||||
|
||||
ArtistName = resource.ArtistName,
|
||||
Name = resource.Name,
|
||||
//AlternateTitles
|
||||
//SortTitle = resource.SortTitle,
|
||||
|
||||
MBId = resource.MBId,
|
||||
TADBId = resource.TADBId,
|
||||
DiscogsId = resource.DiscogsId,
|
||||
AMId = resource.AMId,
|
||||
//TotalEpisodeCount
|
||||
//EpisodeCount
|
||||
//EpisodeFileCount
|
||||
|
@ -147,11 +156,10 @@ namespace NzbDrone.Api.Music
|
|||
Path = resource.Path,
|
||||
ProfileId = resource.ProfileId,
|
||||
|
||||
ArtistFolder = resource.ArtistFolder,
|
||||
Monitored = resource.Monitored,
|
||||
//LastInfoSync = resource.LastInfoSync,
|
||||
SpotifyId = resource.SpotifyId,
|
||||
ArtistSlug = resource.ArtistSlug,
|
||||
ForeignArtistId = resource.ForeignArtistId,
|
||||
NameSlug = resource.NameSlug,
|
||||
|
||||
RootFolderPath = resource.RootFolderPath,
|
||||
Genres = resource.Genres,
|
||||
|
|
|
@ -104,6 +104,13 @@
|
|||
<Compile Include="ClientSchema\SelectOption.cs" />
|
||||
<Compile Include="Commands\CommandModule.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\Pipelines\CorsPipeline.cs" />
|
||||
<Compile Include="Extensions\Pipelines\RequestLoggingPipeline.cs" />
|
||||
|
|
101
src/NzbDrone.Api/TrackFiles/TrackFileModule.cs
Normal file
101
src/NzbDrone.Api/TrackFiles/TrackFileModule.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
64
src/NzbDrone.Api/TrackFiles/TrackFileResource.cs
Normal file
64
src/NzbDrone.Api/TrackFiles/TrackFileResource.cs
Normal 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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
37
src/NzbDrone.Api/Tracks/RenameTrackModule.cs
Normal file
37
src/NzbDrone.Api/Tracks/RenameTrackModule.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
39
src/NzbDrone.Api/Tracks/RenameTrackResource.cs
Normal file
39
src/NzbDrone.Api/Tracks/RenameTrackResource.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
40
src/NzbDrone.Api/Tracks/TrackModule.cs
Normal file
40
src/NzbDrone.Api/Tracks/TrackModule.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
126
src/NzbDrone.Api/Tracks/TrackModuleWithSignalR.cs
Normal file
126
src/NzbDrone.Api/Tracks/TrackModuleWithSignalR.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
78
src/NzbDrone.Api/Tracks/TrackResource.cs
Normal file
78
src/NzbDrone.Api/Tracks/TrackResource.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,14 +17,9 @@ namespace NzbDrone.Common.Cloud
|
|||
Services = new HttpRequestBuilder("http://services.lidarr.tv/v1/")
|
||||
.CreateFactory();
|
||||
|
||||
//Search = new HttpRequestBuilder("https://api.spotify.com/{version}/{route}/") // TODO: maybe use {version}
|
||||
// .SetSegment("version", "v1")
|
||||
// .CreateFactory();
|
||||
Search = new HttpRequestBuilder("http://localhost:5000/{route}/") // TODO: maybe use {version}
|
||||
Search = new HttpRequestBuilder("http://localhost:3000/{route}/") // TODO: Add {version} once LidarrAPI.Metadata is released.
|
||||
.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}/")
|
||||
.SetSegment("language", "en")
|
||||
|
|
|
@ -12,64 +12,94 @@ namespace NzbDrone.Core.Datastore.Migration
|
|||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Create.TableForModel("Artist")
|
||||
.WithColumn("SpotifyId").AsString().Nullable().Unique()
|
||||
.WithColumn("ArtistName").AsString().Unique()
|
||||
.WithColumn("ArtistSlug").AsString().Nullable() //.Unique()
|
||||
.WithColumn("CleanTitle").AsString().Nullable() // Do we need this?
|
||||
.WithColumn("Monitored").AsBoolean()
|
||||
Create.TableForModel("Artists")
|
||||
.WithColumn("ForeignArtistId").AsString().Unique()
|
||||
.WithColumn("MBId").AsString().Nullable()
|
||||
.WithColumn("AMId").AsString().Nullable()
|
||||
.WithColumn("TADBId").AsInt32().Nullable()
|
||||
.WithColumn("DiscogsId").AsInt32().Nullable()
|
||||
.WithColumn("Name").AsString()
|
||||
.WithColumn("NameSlug").AsString().Nullable().Unique()
|
||||
.WithColumn("CleanName").AsString().Indexed()
|
||||
.WithColumn("Status").AsInt32()
|
||||
.WithColumn("Overview").AsString().Nullable()
|
||||
.WithColumn("AlbumFolder").AsBoolean().Nullable()
|
||||
.WithColumn("ArtistFolder").AsBoolean().Nullable()
|
||||
.WithColumn("Images").AsString()
|
||||
.WithColumn("Path").AsString().Indexed()
|
||||
.WithColumn("Monitored").AsBoolean()
|
||||
.WithColumn("AlbumFolder").AsBoolean()
|
||||
.WithColumn("LastInfoSync").AsDateTime().Nullable()
|
||||
.WithColumn("LastDiskSync").AsDateTime().Nullable()
|
||||
.WithColumn("Status").AsInt32().Nullable()
|
||||
.WithColumn("Path").AsString()
|
||||
.WithColumn("Images").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("DateFormed").AsDateTime().Nullable()
|
||||
.WithColumn("Members").AsString().Nullable()
|
||||
.WithColumn("Ratings").AsString().Nullable()
|
||||
.WithColumn("Genres").AsString().Nullable()
|
||||
.WithColumn("Albums").AsString().Nullable()
|
||||
.WithColumn("SortName").AsString().Nullable()
|
||||
.WithColumn("ProfileId").AsInt32().Nullable()
|
||||
.WithColumn("Tags").AsString().Nullable()
|
||||
.WithColumn("AddOptions").AsString().Nullable()
|
||||
;
|
||||
.WithColumn("Added").AsDateTime().Nullable()
|
||||
.WithColumn("AddOptions").AsString().Nullable();
|
||||
|
||||
Create.TableForModel("Albums")
|
||||
.WithColumn("AlbumId").AsString().Unique()
|
||||
.WithColumn("ArtistId").AsInt32() // Should this be artistId (string)
|
||||
.WithColumn("ForeignAlbumId").AsString().Unique()
|
||||
.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("Year").AsInt32()
|
||||
.WithColumn("Image").AsInt32()
|
||||
.WithColumn("TrackCount").AsInt32()
|
||||
.WithColumn("DiscCount").AsInt32()
|
||||
.WithColumn("TitleSlug").AsString().Nullable().Unique()
|
||||
.WithColumn("CleanTitle").AsString().Indexed()
|
||||
.WithColumn("Overview").AsString().Nullable()
|
||||
.WithColumn("Images").AsString()
|
||||
.WithColumn("Path").AsString().Indexed()
|
||||
.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")
|
||||
.WithColumn("SpotifyTrackId").AsString().Nullable() // This shouldn't be nullable, but TrackRepository won't behave. Someone please fix this.
|
||||
.WithColumn("AlbumId").AsString()
|
||||
.WithColumn("ArtistId").AsString() // This may be a list of Ids in future for compilations
|
||||
.WithColumn("ArtistSpotifyId").AsString()
|
||||
.WithColumn("Compilation").AsBoolean()
|
||||
.WithColumn("ForeignTrackId").AsString().Unique()
|
||||
.WithColumn("ArtistId").AsInt32().Indexed()
|
||||
.WithColumn("AlbumId").AsInt32()
|
||||
.WithColumn("MBId").AsString().Nullable().Indexed()
|
||||
.WithColumn("TrackNumber").AsInt32()
|
||||
.WithColumn("Title").AsString().Nullable()
|
||||
.WithColumn("Ignored").AsBoolean().Nullable()
|
||||
.WithColumn("Explict").AsBoolean()
|
||||
.WithColumn("Explicit").AsBoolean()
|
||||
.WithColumn("Compilation").AsBoolean()
|
||||
.WithColumn("DiscNumber").AsInt32().Nullable()
|
||||
.WithColumn("TrackFileId").AsInt32().Nullable().Indexed()
|
||||
.WithColumn("Monitored").AsBoolean()
|
||||
.WithColumn("TrackFileId").AsInt32().Nullable()
|
||||
.WithColumn("ReleaseDate").AsDateTime().Nullable();
|
||||
.WithColumn("Ratings").AsString().Nullable();
|
||||
|
||||
Create.Index().OnTable("Tracks").OnColumn("ArtistId").Ascending()
|
||||
.OnColumn("AlbumId").Ascending()
|
||||
.OnColumn("TrackNumber").Ascending();
|
||||
|
||||
Create.TableForModel("TrackFiles")
|
||||
.WithColumn("ArtistId").AsInt32()
|
||||
.WithColumn("Path").AsString().Unique()
|
||||
.WithColumn("ArtistId").AsInt32().Indexed()
|
||||
.WithColumn("AlbumId").AsInt32().Indexed()
|
||||
.WithColumn("Quality").AsString()
|
||||
.WithColumn("Size").AsInt64()
|
||||
.WithColumn("DateAdded").AsDateTime()
|
||||
.WithColumn("AlbumId").AsInt32(); // How does this impact stand alone tracks?
|
||||
.WithColumn("SceneName").AsString().Nullable()
|
||||
.WithColumn("ReleaseGroup").AsString().Nullable()
|
||||
.WithColumn("MediaInfo").AsString().Nullable()
|
||||
.WithColumn("RelativePath").AsString().Nullable();
|
||||
|
||||
Alter.Table("NamingConfig")
|
||||
.AddColumn("ArtistFolderFormat").AsString().Nullable()
|
||||
.AddColumn("RenameTracks").AsBoolean().Nullable()
|
||||
.AddColumn("StandardTrackFormat").AsString().Nullable()
|
||||
.AddColumn("AlbumFolderFormat").AsString().Nullable();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(112)]
|
||||
public class add_music_fields_to_namingconfig : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Alter.Table("NamingConfig").AddColumn("ArtistFolderFormat").AsAnsiString().Nullable();
|
||||
Alter.Table("NamingConfig").AddColumn("AlbumFolderFormat").AsAnsiString().Nullable();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -92,11 +92,13 @@ namespace NzbDrone.Core.Datastore
|
|||
.Relationship()
|
||||
.HasOne(episode => episode.EpisodeFile, episode => episode.EpisodeFileId);
|
||||
|
||||
Mapper.Entity<Artist>().RegisterModel("Artist")
|
||||
Mapper.Entity<Artist>().RegisterModel("Artists")
|
||||
.Ignore(s => s.RootFolderPath)
|
||||
.Relationship()
|
||||
.HasOne(a => a.Profile, a => a.ProfileId);
|
||||
|
||||
Mapper.Entity<Album>().RegisterModel("Albums");
|
||||
|
||||
Mapper.Entity<TrackFile>().RegisterModel("TrackFiles")
|
||||
.Ignore(f => f.Path)
|
||||
.Relationships.AutoMapICollectionOrComplexProperties()
|
||||
|
|
17
src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs
Normal file
17
src/NzbDrone.Core/Download/AlbumGrabbedEvent.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
public class AlbumGrabbedEvent : IEvent
|
||||
{
|
||||
public RemoteAlbum Album { get; private set; }
|
||||
public string DownloadClient { get; set; }
|
||||
public string DownloadId { get; set; }
|
||||
|
||||
public AlbumGrabbedEvent(RemoteAlbum album)
|
||||
{
|
||||
Album = album;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@ using NzbDrone.Core.MediaFiles.EpisodeImport;
|
|||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
|
||||
namespace NzbDrone.Core.Download
|
||||
{
|
||||
|
@ -130,7 +131,7 @@ namespace NzbDrone.Core.Download
|
|||
{
|
||||
var statusMessages = importResults
|
||||
.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();
|
||||
|
||||
trackedDownload.Warn(statusMessages);
|
||||
|
|
|
@ -16,6 +16,7 @@ using NzbDrone.Core.Tv;
|
|||
|
||||
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
|
||||
{
|
||||
void ImportExtraFiles(LocalEpisode localEpisode, EpisodeFile episodeFile, bool isReadOnly);
|
||||
|
@ -136,16 +137,17 @@ namespace NzbDrone.Core.Extras
|
|||
|
||||
private List<EpisodeFile> GetEpisodeFiles(int seriesId)
|
||||
{
|
||||
var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
|
||||
var episodes = _episodeService.GetEpisodeBySeries(seriesId);
|
||||
//var episodeFiles = _mediaFileService.GetFilesBySeries(seriesId);
|
||||
//var episodes = _episodeService.GetEpisodeBySeries(seriesId);
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
var localEpisodeFile = episodeFile;
|
||||
episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
|
||||
}
|
||||
//foreach (var episodeFile in episodeFiles)
|
||||
//{
|
||||
// var localEpisodeFile = episodeFile;
|
||||
// episodeFile.Episodes = new LazyList<Episode>(episodes.Where(e => e.EpisodeFileId == localEpisodeFile.Id));
|
||||
//}
|
||||
|
||||
return episodeFiles;
|
||||
//return episodeFiles;
|
||||
return new List<EpisodeFile>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Text.RegularExpressions;
|
|||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
{
|
||||
|
@ -19,6 +20,9 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
|||
public virtual bool MonitoredEpisodesOnly { 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 static string GetQueryTitle(string title)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Commands
|
||||
|
|
16
src/NzbDrone.Core/MediaFiles/Commands/RenameArtistCommand.cs
Normal file
16
src/NzbDrone.Core/MediaFiles/Commands/RenameArtistCommand.cs
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
26
src/NzbDrone.Core/MediaFiles/Commands/RescanArtistCommand.cs
Normal file
26
src/NzbDrone.Core/MediaFiles/Commands/RescanArtistCommand.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,12 +16,15 @@ using NzbDrone.Core.Messaging.Commands;
|
|||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Tv.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Music.Events;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
public interface IDiskScanService
|
||||
{
|
||||
void Scan(Series series);
|
||||
void Scan(Artist artist);
|
||||
string[] GetVideoFiles(string path, bool allDirectories = true);
|
||||
string[] GetNonVideoFiles(string path, bool allDirectories = true);
|
||||
List<string> FilterFiles(Series series, IEnumerable<string> files);
|
||||
|
@ -29,32 +32,35 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
public class DiskScanService :
|
||||
IDiskScanService,
|
||||
IHandle<SeriesUpdatedEvent>,
|
||||
IExecute<RescanSeriesCommand>
|
||||
IHandle<ArtistUpdatedEvent>,
|
||||
IExecute<RescanArtistCommand>
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
||||
private readonly IImportApprovedTracks _importApprovedTracks;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public DiskScanService(IDiskProvider diskProvider,
|
||||
IMakeImportDecision importDecisionMaker,
|
||||
IImportApprovedEpisodes importApprovedEpisodes,
|
||||
IImportApprovedTracks importApprovedTracks,
|
||||
IConfigService configService,
|
||||
ISeriesService seriesService,
|
||||
IArtistService artistService,
|
||||
IMediaFileTableCleanupService mediaFileTableCleanupService,
|
||||
IEventAggregator eventAggregator,
|
||||
Logger logger)
|
||||
{
|
||||
_diskProvider = diskProvider;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_importApprovedEpisodes = importApprovedEpisodes;
|
||||
_importApprovedTracks = importApprovedTracks;
|
||||
_configService = configService;
|
||||
_seriesService = seriesService;
|
||||
_artistService = artistService;
|
||||
_mediaFileTableCleanupService = mediaFileTableCleanupService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_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 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))
|
||||
{
|
||||
_logger.Warn("Series' root folder ({0}) doesn't exist.", rootFolder);
|
||||
_eventAggregator.PublishEvent(new SeriesScanSkippedEvent(series, SeriesScanSkippedReason.RootFolderDoesNotExist));
|
||||
_logger.Warn("Artist' root folder ({0}) doesn't exist.", rootFolder);
|
||||
_eventAggregator.PublishEvent(new ArtistScanSkippedEvent(artist, ArtistScanSkippedReason.RootFolderDoesNotExist));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_diskProvider.GetDirectories(rootFolder).Empty())
|
||||
{
|
||||
_logger.Warn("Series' root folder ({0}) is empty.", rootFolder);
|
||||
_eventAggregator.PublishEvent(new SeriesScanSkippedEvent(series, SeriesScanSkippedReason.RootFolderIsEmpty));
|
||||
_logger.Warn("Artist' root folder ({0}) is empty.", rootFolder);
|
||||
_eventAggregator.PublishEvent(new ArtistScanSkippedEvent(artist, ArtistScanSkippedReason.RootFolderIsEmpty));
|
||||
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)
|
||||
{
|
||||
_logger.Debug("Creating missing series folder: {0}", series.Path);
|
||||
_diskProvider.CreateFolder(series.Path);
|
||||
SetPermissions(series.Path);
|
||||
_logger.Debug("Creating missing artist folder: {0}", artist.Path);
|
||||
_diskProvider.CreateFolder(artist.Path);
|
||||
SetPermissions(artist.Path);
|
||||
}
|
||||
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>());
|
||||
CompletedScanning(series);
|
||||
CleanMediaFiles(artist, new List<string>());
|
||||
CompletedScanning(artist);
|
||||
return;
|
||||
}
|
||||
|
||||
var videoFilesStopwatch = Stopwatch.StartNew();
|
||||
var mediaFileList = FilterFiles(series, GetVideoFiles(series.Path)).ToList();
|
||||
videoFilesStopwatch.Stop();
|
||||
_logger.Trace("Finished getting episode files for: {0} [{1}]", series, videoFilesStopwatch.Elapsed);
|
||||
var musicFilesStopwatch = Stopwatch.StartNew();
|
||||
var mediaFileList = FilterFiles(artist, GetMusicFiles(artist.Path)).ToList();
|
||||
musicFilesStopwatch.Stop();
|
||||
_logger.Trace("Finished getting track files for: {0} [{1}]", artist, musicFilesStopwatch.Elapsed);
|
||||
|
||||
CleanMediaFiles(series, mediaFileList);
|
||||
CleanMediaFiles(artist, mediaFileList);
|
||||
|
||||
var decisionsStopwatch = Stopwatch.StartNew();
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, series);
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(mediaFileList, artist);
|
||||
decisionsStopwatch.Stop();
|
||||
_logger.Trace("Import decisions complete for: {0} [{1}]", series, decisionsStopwatch.Elapsed);
|
||||
_importApprovedEpisodes.Import(decisions, false);
|
||||
_logger.Trace("Import decisions complete for: {0} [{1}]", artist, decisionsStopwatch.Elapsed);
|
||||
_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);
|
||||
_mediaFileTableCleanupService.Clean(series, mediaFileList);
|
||||
_logger.Debug("{0} Cleaning up media files in DB", artist);
|
||||
_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);
|
||||
_eventAggregator.PublishEvent(new SeriesScannedEvent(series));
|
||||
_logger.Info("Completed scanning disk for {0}", artist.Name);
|
||||
_eventAggregator.PublishEvent(new ArtistScannedEvent(artist));
|
||||
}
|
||||
|
||||
public string[] GetVideoFiles(string path, bool allDirectories = true)
|
||||
|
@ -143,9 +155,24 @@ namespace NzbDrone.Core.MediaFiles
|
|||
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)
|
||||
{
|
||||
_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 filesOnDisk = _diskProvider.GetFiles(path, searchOption).ToList();
|
||||
|
@ -154,7 +181,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
.ToList();
|
||||
|
||||
_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();
|
||||
}
|
||||
|
||||
|
@ -165,6 +192,13 @@ namespace NzbDrone.Core.MediaFiles
|
|||
.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)
|
||||
{
|
||||
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);
|
||||
Scan(series);
|
||||
var artist = _artistService.FindById(message.ArtistId);
|
||||
Scan(artist);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var allSeries = _seriesService.GetAllSeries();
|
||||
var allArtists = _artistService.GetAllArtists();
|
||||
|
||||
foreach (var series in allSeries)
|
||||
foreach (var artist in allArtists)
|
||||
{
|
||||
Scan(series);
|
||||
Scan(artist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ using NzbDrone.Core.Download.TrackedDownloads;
|
|||
using NzbDrone.Core.MediaFiles.Commands;
|
||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@ using NzbDrone.Core.Parser;
|
|||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
|
@ -59,7 +60,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
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);
|
||||
results.AddRange(fileResults);
|
||||
|
@ -100,7 +101,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
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");
|
||||
|
||||
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)
|
||||
{
|
||||
if (_seriesService.SeriesPathExists(directoryInfo.FullName))
|
||||
{
|
||||
_logger.Warn("Unable to process folder that is mapped to an existing show");
|
||||
return new List<ImportResult>();
|
||||
}
|
||||
throw new System.NotImplementedException("Will be removed");
|
||||
|
||||
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||
var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
|
||||
//if (_seriesService.SeriesPathExists(directoryInfo.FullName))
|
||||
//{
|
||||
// _logger.Warn("Unable to process folder that is mapped to an existing show");
|
||||
// return new List<ImportResult>();
|
||||
//}
|
||||
|
||||
if (folderInfo != null)
|
||||
{
|
||||
_logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
|
||||
}
|
||||
//var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
|
||||
//var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
|
||||
|
||||
var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||
//if (folderInfo != null)
|
||||
//{
|
||||
// _logger.Debug("{0} folder quality: {1}", cleanedUpName, folderInfo.Quality);
|
||||
//}
|
||||
|
||||
if (downloadClientItem == null)
|
||||
{
|
||||
foreach (var videoFile in videoFiles)
|
||||
{
|
||||
if (_diskProvider.IsFileLocked(videoFile))
|
||||
{
|
||||
return new List<ImportResult>
|
||||
{
|
||||
FileIsLockedResult(videoFile)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
//var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName);
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, folderInfo, true);
|
||||
var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode);
|
||||
//if (downloadClientItem == null)
|
||||
//{
|
||||
// foreach (var videoFile in videoFiles)
|
||||
// {
|
||||
// if (_diskProvider.IsFileLocked(videoFile))
|
||||
// {
|
||||
// return new List<ImportResult>
|
||||
// {
|
||||
// FileIsLockedResult(videoFile)
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
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);
|
||||
}
|
||||
//var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, folderInfo, true);
|
||||
//var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode);
|
||||
|
||||
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)
|
||||
|
@ -215,30 +218,31 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
private List<ImportResult> ProcessFile(FileInfo fileInfo, ImportMode importMode, Series series, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
|
||||
{
|
||||
_logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);
|
||||
throw new System.NotImplementedException("Will be removed");
|
||||
//if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._"))
|
||||
//{
|
||||
// _logger.Debug("[{0}] starts with '._', skipping", fileInfo.FullName);
|
||||
|
||||
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 '._'")
|
||||
};
|
||||
}
|
||||
// return new List<ImportResult>
|
||||
// {
|
||||
// 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 (_diskProvider.IsFileLocked(fileInfo.FullName))
|
||||
{
|
||||
return new List<ImportResult>
|
||||
{
|
||||
FileIsLockedResult(fileInfo.FullName)
|
||||
};
|
||||
}
|
||||
}
|
||||
//if (downloadClientItem == null)
|
||||
//{
|
||||
// if (_diskProvider.IsFileLocked(fileInfo.FullName))
|
||||
// {
|
||||
// return new List<ImportResult>
|
||||
// {
|
||||
// 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)
|
||||
|
@ -251,15 +255,17 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
private ImportResult FileIsLockedResult(string videoFile)
|
||||
{
|
||||
_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");
|
||||
throw new System.NotImplementedException("Will be removed");
|
||||
//_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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||
public interface IImportDecisionEngineSpecification
|
||||
{
|
||||
Decision IsSatisfiedBy(LocalEpisode localEpisode);
|
||||
Decision IsSatisfiedBy(LocalTrack localTrack);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ using NzbDrone.Core.Parser.Model;
|
|||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Extras;
|
||||
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
|
||||
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)
|
||||
{
|
||||
var qualifiedImports = decisions.Where(c => c.Approved)
|
||||
.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;
|
||||
throw new NotImplementedException("This will be removed");
|
||||
}
|
||||
|
||||
private string GetSceneName(DownloadClientItem downloadClientItem, LocalEpisode localEpisode)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,14 +11,17 @@ using NzbDrone.Core.Parser.Model;
|
|||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
{
|
||||
public interface IMakeImportDecision
|
||||
{
|
||||
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> videoFiles, Series series);
|
||||
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
|
||||
|
@ -48,84 +51,124 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||
_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>();
|
||||
|
||||
foreach (var file in newFiles)
|
||||
{
|
||||
decisions.AddIfNotNull(GetDecision(file, series, folderInfo, sceneSource, shouldUseFolderName));
|
||||
decisions.AddIfNotNull(GetDecision(file, artist, folderInfo, shouldUseFolderName));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
localEpisode.Size = _diskProvider.GetFileSize(file);
|
||||
localTrack.Quality = GetQuality(folderInfo, localTrack.Quality, artist);
|
||||
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
|
||||
if (sceneSource)
|
||||
//TODO: make it so media info doesn't ruin the import process of a new artist
|
||||
|
||||
if (localTrack.Tracks.Empty())
|
||||
{
|
||||
localEpisode.MediaInfo = _videoFileInfoReader.GetMediaInfo(file);
|
||||
}
|
||||
|
||||
if (localEpisode.Episodes.Empty())
|
||||
{
|
||||
decision = new ImportDecision(localEpisode, new Rejection("Invalid season or episode"));
|
||||
decision = new ImportDecision(localTrack, new Rejection("Invalid album or track"));
|
||||
}
|
||||
else
|
||||
{
|
||||
decision = GetDecision(localEpisode);
|
||||
decision = GetDecision(localTrack);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
localEpisode = new LocalEpisode();
|
||||
localEpisode.Path = file;
|
||||
localTrack = new LocalTrack();
|
||||
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)
|
||||
{
|
||||
_logger.Error(e, "Couldn't import file. {0}", file);
|
||||
|
||||
var localEpisode = new LocalEpisode { Path = file };
|
||||
decision = new ImportDecision(localEpisode, new Rejection("Unexpected error processing file"));
|
||||
var localTrack = new LocalTrack { Path = file };
|
||||
decision = new ImportDecision(localTrack, new Rejection("Unexpected error processing file"));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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)
|
||||
|
@ -150,28 +193,28 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||
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)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (folderInfo.FullSeason)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
//if (folderInfo.FullSeason)
|
||||
//{
|
||||
// return false;
|
||||
//}
|
||||
|
||||
return videoFiles.Count(file =>
|
||||
return musicFiles.Count(file =>
|
||||
{
|
||||
var size = _diskProvider.GetFileSize(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)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
//if (sample)
|
||||
//{
|
||||
// return false;
|
||||
//}
|
||||
|
||||
if (SceneChecker.IsSceneTitle(Path.GetFileName(file)))
|
||||
{
|
||||
|
@ -182,9 +225,9 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||
}) == 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);
|
||||
return folderInfo.Quality;
|
||||
|
@ -193,7 +236,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||
return fileQuality;
|
||||
}
|
||||
|
||||
private bool UseFolderQuality(ParsedEpisodeInfo folderInfo, QualityModel fileQuality, Series series)
|
||||
private bool UseFolderQuality(ParsedTrackInfo folderInfo, QualityModel fileQuality, Artist artist)
|
||||
{
|
||||
if (folderInfo == null)
|
||||
{
|
||||
|
@ -210,7 +253,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
|||
return true;
|
||||
}
|
||||
|
||||
if (new QualityModelComparer(series.Profile).Compare(folderInfo.Quality, fileQuality) > 0)
|
||||
if (new QualityModelComparer(artist.Profile).Compare(folderInfo.Quality, fileQuality) > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
{
|
||||
|
|
|
@ -15,6 +15,7 @@ using NzbDrone.Core.Messaging.Events;
|
|||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
{
|
||||
|
@ -94,65 +95,67 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||
|
||||
private List<ManualImportItem> ProcessFolder(string folder, string downloadId)
|
||||
{
|
||||
var directoryInfo = new DirectoryInfo(folder);
|
||||
var series = _parsingService.GetSeries(directoryInfo.Name);
|
||||
throw new System.NotImplementedException("TODO: This will be rewritten for Music");
|
||||
//var directoryInfo = new DirectoryInfo(folder);
|
||||
//var series = _parsingService.GetSeries(directoryInfo.Name);
|
||||
|
||||
if (series == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
series = trackedDownload.RemoteEpisode.Series;
|
||||
}
|
||||
//if (series == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
//{
|
||||
// var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
// series = trackedDownload.RemoteEpisode.Series;
|
||||
//}
|
||||
|
||||
if (series == null)
|
||||
{
|
||||
var files = _diskScanService.GetVideoFiles(folder);
|
||||
//if (series == null)
|
||||
//{
|
||||
// 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 seriesFiles = _diskScanService.GetVideoFiles(folder).ToList();
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(seriesFiles, series, folderInfo, SceneSource(series, folder));
|
||||
//var folderInfo = Parser.Parser.ParseTitle(directoryInfo.Name);
|
||||
//var seriesFiles = _diskScanService.GetVideoFiles(folder).ToList();
|
||||
//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)
|
||||
{
|
||||
if (folder.IsNullOrWhiteSpace())
|
||||
{
|
||||
folder = new FileInfo(file).Directory.FullName;
|
||||
}
|
||||
throw new System.NotImplementedException("TODO: This will be rewritten for Music");
|
||||
//if (folder.IsNullOrWhiteSpace())
|
||||
//{
|
||||
// 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)
|
||||
{
|
||||
series = _parsingService.GetSeries(relativeFile);
|
||||
}
|
||||
//if (series == null)
|
||||
//{
|
||||
// series = _parsingService.GetSeries(relativeFile);
|
||||
//}
|
||||
|
||||
if (series == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
series = trackedDownload.RemoteEpisode.Series;
|
||||
}
|
||||
//if (series == null && downloadId.IsNotNullOrWhiteSpace())
|
||||
//{
|
||||
// var trackedDownload = _trackedDownloadService.Find(downloadId);
|
||||
// series = trackedDownload.RemoteEpisode.Series;
|
||||
//}
|
||||
|
||||
if (series == null)
|
||||
{
|
||||
var localEpisode = new LocalEpisode();
|
||||
localEpisode.Path = file;
|
||||
localEpisode.Quality = QualityParser.ParseQuality(file);
|
||||
localEpisode.Size = _diskProvider.GetFileSize(file);
|
||||
//if (series == null)
|
||||
//{
|
||||
// var localEpisode = new LocalEpisode();
|
||||
// localEpisode.Path = file;
|
||||
// localEpisode.Quality = QualityParser.ParseQuality(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},
|
||||
series, null, SceneSource(series, folder));
|
||||
//var importDecisions = _importDecisionMaker.GetImportDecisions(new List<string> {file},
|
||||
// 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)
|
||||
|
@ -162,107 +165,109 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
|||
|
||||
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.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
|
||||
item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
|
||||
item.DownloadId = downloadId;
|
||||
//item.Path = decision.LocalEpisode.Path;
|
||||
//item.RelativePath = folder.GetRelativePath(decision.LocalEpisode.Path);
|
||||
//item.Name = Path.GetFileNameWithoutExtension(decision.LocalEpisode.Path);
|
||||
//item.DownloadId = downloadId;
|
||||
|
||||
if (decision.LocalEpisode.Series != null)
|
||||
{
|
||||
item.Series = decision.LocalEpisode.Series;
|
||||
}
|
||||
//if (decision.LocalEpisode.Series != null)
|
||||
//{
|
||||
// item.Series = decision.LocalEpisode.Series;
|
||||
//}
|
||||
|
||||
if (decision.LocalEpisode.Episodes.Any())
|
||||
{
|
||||
item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
|
||||
item.Episodes = decision.LocalEpisode.Episodes;
|
||||
}
|
||||
//if (decision.LocalEpisode.Episodes.Any())
|
||||
//{
|
||||
// item.SeasonNumber = decision.LocalEpisode.SeasonNumber;
|
||||
// item.Episodes = decision.LocalEpisode.Episodes;
|
||||
//}
|
||||
|
||||
item.Quality = decision.LocalEpisode.Quality;
|
||||
item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
|
||||
item.Rejections = decision.Rejections;
|
||||
//item.Quality = decision.LocalEpisode.Quality;
|
||||
//item.Size = _diskProvider.GetFileSize(decision.LocalEpisode.Path);
|
||||
//item.Rejections = decision.Rejections;
|
||||
|
||||
return item;
|
||||
//return item;
|
||||
}
|
||||
|
||||
public void Execute(ManualImportCommand message)
|
||||
{
|
||||
_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 importedTrackedDownload = new List<ManuallyImportedFile>();
|
||||
//var imported = new List<ImportResult>();
|
||||
//var importedTrackedDownload = new List<ManuallyImportedFile>();
|
||||
|
||||
for (int i = 0; i < message.Files.Count; i++)
|
||||
{
|
||||
_logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
|
||||
//for (int i = 0; i < message.Files.Count; i++)
|
||||
//{
|
||||
// _logger.ProgressTrace("Processing file {0} of {1}", i + 1, message.Files.Count);
|
||||
|
||||
var file = message.Files[i];
|
||||
var series = _seriesService.GetSeries(file.SeriesId);
|
||||
var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
|
||||
var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
|
||||
var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
|
||||
var existingFile = series.Path.IsParentPath(file.Path);
|
||||
// var file = message.Files[i];
|
||||
// var series = _seriesService.GetSeries(file.SeriesId);
|
||||
// var episodes = _episodeService.GetEpisodes(file.EpisodeIds);
|
||||
// var parsedEpisodeInfo = Parser.Parser.ParsePath(file.Path) ?? new ParsedEpisodeInfo();
|
||||
// var mediaInfo = _videoFileInfoReader.GetMediaInfo(file.Path);
|
||||
// var existingFile = series.Path.IsParentPath(file.Path);
|
||||
|
||||
var localEpisode = new LocalEpisode
|
||||
{
|
||||
ExistingFile = false,
|
||||
Episodes = episodes,
|
||||
MediaInfo = mediaInfo,
|
||||
ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||
Path = file.Path,
|
||||
Quality = file.Quality,
|
||||
Series = series,
|
||||
Size = 0
|
||||
};
|
||||
// var localEpisode = new LocalEpisode
|
||||
// {
|
||||
// ExistingFile = false,
|
||||
// Episodes = episodes,
|
||||
// MediaInfo = mediaInfo,
|
||||
// ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||
// Path = file.Path,
|
||||
// Quality = file.Quality,
|
||||
// Series = series,
|
||||
// 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())
|
||||
{
|
||||
imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
|
||||
}
|
||||
// if (file.DownloadId.IsNullOrWhiteSpace())
|
||||
// {
|
||||
// imported.AddRange(_importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, !existingFile, null, message.ImportMode));
|
||||
// }
|
||||
|
||||
else
|
||||
{
|
||||
var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
|
||||
var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
|
||||
// else
|
||||
// {
|
||||
// var trackedDownload = _trackedDownloadService.Find(file.DownloadId);
|
||||
// var importResult = _importApprovedEpisodes.Import(new List<ImportDecision> { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First();
|
||||
|
||||
imported.Add(importResult);
|
||||
// imported.Add(importResult);
|
||||
|
||||
importedTrackedDownload.Add(new ManuallyImportedFile
|
||||
{
|
||||
TrackedDownload = trackedDownload,
|
||||
ImportResult = importResult
|
||||
});
|
||||
}
|
||||
}
|
||||
// importedTrackedDownload.Add(new ManuallyImportedFile
|
||||
// {
|
||||
// TrackedDownload = trackedDownload,
|
||||
// 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())
|
||||
{
|
||||
var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
|
||||
//foreach (var groupedTrackedDownload in importedTrackedDownload.GroupBy(i => i.TrackedDownload.DownloadItem.DownloadId).ToList())
|
||||
//{
|
||||
// var trackedDownload = groupedTrackedDownload.First().TrackedDownload;
|
||||
|
||||
if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
|
||||
{
|
||||
if (_downloadedEpisodesImportService.ShouldDeleteFolder(
|
||||
new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
|
||||
trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
|
||||
{
|
||||
_diskProvider.DeleteFolder(trackedDownload.DownloadItem.OutputPath.FullPath, true);
|
||||
}
|
||||
}
|
||||
// if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath.FullPath))
|
||||
// {
|
||||
// if (_downloadedEpisodesImportService.ShouldDeleteFolder(
|
||||
// new DirectoryInfo(trackedDownload.DownloadItem.OutputPath.FullPath),
|
||||
// trackedDownload.RemoteEpisode.Series) && !trackedDownload.DownloadItem.IsReadOnly)
|
||||
// {
|
||||
// _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))
|
||||
{
|
||||
trackedDownload.State = TrackedDownloadStage.Imported;
|
||||
_eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||
}
|
||||
}
|
||||
// if (groupedTrackedDownload.Select(c => c.ImportResult).Count(c => c.Result == ImportResultType.Imported) >= Math.Max(1, trackedDownload.RemoteEpisode.Episodes.Count))
|
||||
// {
|
||||
// trackedDownload.State = TrackedDownloadStage.Imported;
|
||||
// _eventAggregator.PublishEvent(new DownloadCompletedEvent(trackedDownload));
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using NzbDrone.Core.Download.TrackedDownloads;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual
|
||||
{
|
||||
|
|
|
@ -63,5 +63,48 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
|||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using NLog;
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
|
@ -13,6 +14,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public Decision IsSatisfiedBy(LocalTrack localTrack)
|
||||
{
|
||||
throw new NotImplementedException("Interface will be removed");
|
||||
}
|
||||
|
||||
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
||||
{
|
||||
if (localEpisode.ParsedEpisodeInfo.FullSeason)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
|
@ -14,6 +15,53 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
|||
{
|
||||
_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)
|
||||
{
|
||||
if (localEpisode.ExistingFile)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using NLog;
|
||||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
|
@ -16,6 +17,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public Decision IsSatisfiedBy(LocalTrack localTrack)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
||||
{
|
||||
if (localEpisode.ExistingFile)
|
||||
|
|
|
@ -56,5 +56,10 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
|||
|
||||
return Decision.Accept();
|
||||
}
|
||||
|
||||
public Decision IsSatisfiedBy(LocalTrack localTrack)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
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");
|
||||
return Decision.Reject("Episode file on disk contains more episodes than this file contains");
|
||||
}
|
||||
|
||||
public Decision IsSatisfiedBy(LocalTrack localTrack)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
@ -13,6 +14,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public Decision IsSatisfiedBy(LocalTrack localTrack)
|
||||
{
|
||||
throw new NotImplementedException("This is not needed, Interface will be removed");
|
||||
}
|
||||
|
||||
public Decision IsSatisfiedBy(LocalEpisode localEpisode)
|
||||
{
|
||||
if (localEpisode.ExistingFile)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
@ -15,6 +16,18 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
|
|||
_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)
|
||||
{
|
||||
var qualityComparer = new QualityModelComparer(localEpisode.Series.Profile);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
19
src/NzbDrone.Core/MediaFiles/Events/ArtistScannedEvent.cs
Normal file
19
src/NzbDrone.Core/MediaFiles/Events/ArtistScannedEvent.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using NzbDrone.Common.Messaging;
|
||||
using NzbDrone.Core.Music;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.Events
|
||||
{
|
||||
public class ArtistScannedEvent : IEvent
|
||||
{
|
||||
public Artist Artist { get; private set; }
|
||||
|
||||
public ArtistScannedEvent(Artist artist)
|
||||
{
|
||||
Artist = artist;
|
||||
}
|
||||
}
|
||||
}
|
23
src/NzbDrone.Core/MediaFiles/Events/TrackDownloadedEvent.cs
Normal file
23
src/NzbDrone.Core/MediaFiles/Events/TrackDownloadedEvent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
@ -5,36 +6,28 @@ using NzbDrone.Core.Messaging.Events;
|
|||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
public interface IMediaFileRepository : IBasicRepository<EpisodeFile>
|
||||
public interface IMediaFileRepository : IBasicRepository<TrackFile>
|
||||
{
|
||||
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
||||
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
||||
List<EpisodeFile> GetFilesWithoutMediaInfo();
|
||||
List<TrackFile> GetFilesByArtist(int artistId);
|
||||
List<TrackFile> GetFilesWithoutMediaInfo();
|
||||
}
|
||||
|
||||
|
||||
public class MediaFileRepository : BasicRepository<EpisodeFile>, IMediaFileRepository
|
||||
public class MediaFileRepository : BasicRepository<TrackFile>, IMediaFileRepository
|
||||
{
|
||||
public MediaFileRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
}
|
||||
|
||||
public List<EpisodeFile> GetFilesBySeries(int seriesId)
|
||||
{
|
||||
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()
|
||||
public List<TrackFile> GetFilesWithoutMediaInfo()
|
||||
{
|
||||
return Query.Where(c => c.MediaInfo == null).ToList();
|
||||
}
|
||||
|
||||
public List<TrackFile> GetFilesByArtist(int artistId)
|
||||
{
|
||||
return Query.Where(c => c.ArtistId == artistId).ToList();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
|
@ -7,24 +7,27 @@ using NzbDrone.Core.Messaging.Events;
|
|||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Tv.Events;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.Music;
|
||||
using System;
|
||||
using NzbDrone.Core.Music.Events;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
public interface IMediaFileService
|
||||
{
|
||||
EpisodeFile Add(EpisodeFile episodeFile);
|
||||
void Update(EpisodeFile episodeFile);
|
||||
void Delete(EpisodeFile episodeFile, DeleteMediaFileReason reason);
|
||||
List<EpisodeFile> GetFilesBySeries(int seriesId);
|
||||
List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber);
|
||||
List<EpisodeFile> GetFilesWithoutMediaInfo();
|
||||
List<string> FilterExistingFiles(List<string> files, Series series);
|
||||
EpisodeFile Get(int id);
|
||||
List<EpisodeFile> Get(IEnumerable<int> ids);
|
||||
TrackFile Add(TrackFile trackFile);
|
||||
void Update(TrackFile trackFile);
|
||||
void Delete(TrackFile trackFile, DeleteMediaFileReason reason);
|
||||
List<TrackFile> GetFilesByArtist(int artistId);
|
||||
List<TrackFile> GetFilesByAlbum(int artistId, int albumId);
|
||||
List<TrackFile> GetFilesWithoutMediaInfo();
|
||||
List<string> FilterExistingFiles(List<string> files, Artist artist);
|
||||
TrackFile Get(int id);
|
||||
List<TrackFile> Get(IEnumerable<int> ids);
|
||||
|
||||
}
|
||||
|
||||
public class MediaFileService : IMediaFileService, IHandleAsync<SeriesDeletedEvent>
|
||||
public class MediaFileService : IMediaFileService, IHandleAsync<ArtistDeletedEvent>
|
||||
{
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IMediaFileRepository _mediaFileRepository;
|
||||
|
@ -37,66 +40,69 @@ namespace NzbDrone.Core.MediaFiles
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public EpisodeFile Add(EpisodeFile episodeFile)
|
||||
public TrackFile Add(TrackFile trackFile)
|
||||
{
|
||||
var addedFile = _mediaFileRepository.Insert(episodeFile);
|
||||
_eventAggregator.PublishEvent(new EpisodeFileAddedEvent(addedFile));
|
||||
var addedFile = _mediaFileRepository.Insert(trackFile);
|
||||
_eventAggregator.PublishEvent(new TrackFileAddedEvent(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
|
||||
episodeFile.Episodes.LazyLoad();
|
||||
episodeFile.Path = Path.Combine(episodeFile.Series.Value.Path, episodeFile.RelativePath);
|
||||
//Little hack so we have the tracks and artist attached for the event consumers
|
||||
trackFile.Episodes.LazyLoad();
|
||||
trackFile.Path = Path.Combine(trackFile.Artist.Value.Path, trackFile.RelativePath);
|
||||
|
||||
_mediaFileRepository.Delete(episodeFile);
|
||||
_eventAggregator.PublishEvent(new EpisodeFileDeletedEvent(episodeFile, reason));
|
||||
_mediaFileRepository.Delete(trackFile);
|
||||
_eventAggregator.PublishEvent(new TrackFileDeletedEvent(trackFile, reason));
|
||||
}
|
||||
|
||||
public List<EpisodeFile> GetFilesBySeries(int seriesId)
|
||||
{
|
||||
return _mediaFileRepository.GetFilesBySeries(seriesId);
|
||||
}
|
||||
|
||||
public List<EpisodeFile> GetFilesBySeason(int seriesId, int seasonNumber)
|
||||
{
|
||||
return _mediaFileRepository.GetFilesBySeason(seriesId, seasonNumber);
|
||||
}
|
||||
|
||||
public List<EpisodeFile> GetFilesWithoutMediaInfo()
|
||||
public List<TrackFile> 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);
|
||||
}
|
||||
|
||||
public List<EpisodeFile> Get(IEnumerable<int> ids)
|
||||
public List<TrackFile> Get(IEnumerable<int> ids)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
public List<TrackFile> GetFilesByArtist(int artistId)
|
||||
{
|
||||
return _mediaFileRepository.GetFilesByArtist(artistId);
|
||||
}
|
||||
|
||||
public List<TrackFile> GetFilesByAlbum(int artistId, int albumId)
|
||||
{
|
||||
return _mediaFileRepository.GetFilesByArtist(artistId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,85 +1,78 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
public interface IMediaFileTableCleanupService
|
||||
{
|
||||
void Clean(Series series, List<string> filesOnDisk);
|
||||
void Clean(Artist artist, List<string> filesOnDisk);
|
||||
}
|
||||
|
||||
public class MediaFileTableCleanupService : IMediaFileTableCleanupService
|
||||
{
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly ITrackService _trackService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public MediaFileTableCleanupService(IMediaFileService mediaFileService,
|
||||
IEpisodeService episodeService,
|
||||
ITrackService trackService,
|
||||
Logger logger)
|
||||
{
|
||||
_mediaFileService = mediaFileService;
|
||||
_episodeService = episodeService;
|
||||
_trackService = trackService;
|
||||
_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 episodes = _episodeService.GetEpisodeBySeries(series.Id);
|
||||
var artistFiles = _mediaFileService.GetFilesByArtist(artist.Id);
|
||||
var tracks = _trackService.GetTracksByArtist(artist.Id);
|
||||
|
||||
|
||||
var filesOnDiskKeys = new HashSet<string>(filesOnDisk, PathEqualityComparer.Instance);
|
||||
|
||||
foreach (var seriesFile in seriesFiles)
|
||||
foreach (var artistFile in artistFiles)
|
||||
{
|
||||
var episodeFile = seriesFile;
|
||||
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||
var trackFile = artistFile;
|
||||
var trackFilePath = Path.Combine(artist.Path, trackFile.RelativePath);
|
||||
|
||||
try
|
||||
{
|
||||
if (!filesOnDiskKeys.Contains(episodeFilePath))
|
||||
if (!filesOnDiskKeys.Contains(trackFilePath))
|
||||
{
|
||||
_logger.Debug("File [{0}] no longer exists on disk, removing from db", episodeFilePath);
|
||||
_mediaFileService.Delete(seriesFile, DeleteMediaFileReason.MissingFromDisk);
|
||||
_logger.Debug("File [{0}] no longer exists on disk, removing from db", trackFilePath);
|
||||
_mediaFileService.Delete(artistFile, DeleteMediaFileReason.MissingFromDisk);
|
||||
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);
|
||||
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.NoLinkedEpisodes);
|
||||
_logger.Debug("File [{0}] is not assigned to any artist, removing from db", trackFilePath);
|
||||
_mediaFileService.Delete(trackFile, DeleteMediaFileReason.NoLinkedEpisodes);
|
||||
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)
|
||||
{
|
||||
_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;
|
||||
_episodeService.UpdateEpisode(episode);
|
||||
track.TrackFileId = 0;
|
||||
_trackService.UpdateTrack(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,11 @@ using NzbDrone.Core.Tv;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
{
|
||||
public class UpdateMediaInfoService : IHandle<SeriesScannedEvent>
|
||||
public class UpdateMediaInfoService : IHandle<ArtistScannedEvent>
|
||||
{
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
|
@ -33,11 +34,11 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
private void UpdateMediaInfo(Series series, List<EpisodeFile> mediaFiles)
|
||||
private void UpdateMediaInfo(Artist artist, List<TrackFile> 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))
|
||||
{
|
||||
|
@ -56,7 +57,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
|||
}
|
||||
}
|
||||
|
||||
public void Handle(SeriesScannedEvent message)
|
||||
public void Handle(ArtistScannedEvent message)
|
||||
{
|
||||
if (!_configService.EnableMediaInfo)
|
||||
{
|
||||
|
@ -64,10 +65,10 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
|||
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();
|
||||
|
||||
UpdateMediaInfo(message.Series, filteredMediaFiles);
|
||||
UpdateMediaInfo(message.Artist, filteredMediaFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,24 +55,28 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
public List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId)
|
||||
{
|
||||
var series = _seriesService.GetSeries(seriesId);
|
||||
var episodes = _episodeService.GetEpisodeBySeries(seriesId);
|
||||
var files = _mediaFileService.GetFilesBySeries(seriesId);
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
//var series = _seriesService.GetSeries(seriesId);
|
||||
//var episodes = _episodeService.GetEpisodeBySeries(seriesId);
|
||||
//var files = _mediaFileService.GetFilesBySeries(seriesId);
|
||||
|
||||
return GetPreviews(series, episodes, files)
|
||||
.OrderByDescending(e => e.SeasonNumber)
|
||||
.ThenByDescending(e => e.EpisodeNumbers.First())
|
||||
.ToList();
|
||||
//return GetPreviews(series, episodes, files)
|
||||
// .OrderByDescending(e => e.SeasonNumber)
|
||||
// .ThenByDescending(e => e.EpisodeNumbers.First())
|
||||
// .ToList();
|
||||
}
|
||||
|
||||
public List<RenameEpisodeFilePreview> GetRenamePreviews(int seriesId, int seasonNumber)
|
||||
{
|
||||
var series = _seriesService.GetSeries(seriesId);
|
||||
var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber);
|
||||
var files = _mediaFileService.GetFilesBySeason(seriesId, seasonNumber);
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
//var series = _seriesService.GetSeries(seriesId);
|
||||
//var episodes = _episodeService.GetEpisodesBySeason(seriesId, seasonNumber);
|
||||
//var files = _mediaFileService.GetFilesBySeason(seriesId, seasonNumber);
|
||||
|
||||
return GetPreviews(series, episodes, files)
|
||||
.OrderByDescending(e => e.EpisodeNumbers.First()).ToList();
|
||||
//return GetPreviews(series, episodes, files)
|
||||
// .OrderByDescending(e => e.EpisodeNumbers.First()).ToList();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var renamed = new List<EpisodeFile>();
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
//var renamed = new List<EpisodeFile>();
|
||||
|
||||
foreach (var episodeFile in episodeFiles)
|
||||
{
|
||||
var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||
//foreach (var episodeFile in episodeFiles)
|
||||
//{
|
||||
// var episodeFilePath = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("Renaming episode file: {0}", episodeFile);
|
||||
_episodeFileMover.MoveEpisodeFile(episodeFile, series);
|
||||
// try
|
||||
// {
|
||||
// _logger.Debug("Renaming episode file: {0}", episodeFile);
|
||||
// _episodeFileMover.MoveEpisodeFile(episodeFile, series);
|
||||
|
||||
_mediaFileService.Update(episodeFile);
|
||||
renamed.Add(episodeFile);
|
||||
// _mediaFileService.Update(episodeFile);
|
||||
// renamed.Add(episodeFile);
|
||||
|
||||
_logger.Debug("Renamed episode file: {0}", episodeFile);
|
||||
}
|
||||
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}", episodeFilePath);
|
||||
}
|
||||
}
|
||||
// _logger.Debug("Renamed episode file: {0}", episodeFile);
|
||||
// }
|
||||
// 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}", episodeFilePath);
|
||||
// }
|
||||
//}
|
||||
|
||||
if (renamed.Any())
|
||||
{
|
||||
_diskProvider.RemoveEmptySubfolders(series.Path);
|
||||
//if (renamed.Any())
|
||||
//{
|
||||
// _diskProvider.RemoveEmptySubfolders(series.Path);
|
||||
|
||||
_eventAggregator.PublishEvent(new SeriesRenamedEvent(series));
|
||||
}
|
||||
// _eventAggregator.PublishEvent(new SeriesRenamedEvent(series));
|
||||
//}
|
||||
}
|
||||
|
||||
public void Execute(RenameFilesCommand message)
|
||||
{
|
||||
var series = _seriesService.GetSeries(message.SeriesId);
|
||||
var episodeFiles = _mediaFileService.Get(message.Files);
|
||||
// TODO
|
||||
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);
|
||||
RenameFiles(episodeFiles, series);
|
||||
_logger.ProgressInfo("Selected episode files renamed for {0}", series.Title);
|
||||
//_logger.ProgressInfo("Renaming {0} files for {1}", episodeFiles.Count, series.Title);
|
||||
//RenameFiles(episodeFiles, series);
|
||||
//_logger.ProgressInfo("Selected episode files renamed for {0}", series.Title);
|
||||
}
|
||||
|
||||
public void Execute(RenameSeriesCommand message)
|
||||
{
|
||||
_logger.Debug("Renaming all files for selected series");
|
||||
var seriesToRename = _seriesService.GetSeries(message.SeriesIds);
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
//_logger.Debug("Renaming all files for selected series");
|
||||
//var seriesToRename = _seriesService.GetSeries(message.SeriesIds);
|
||||
|
||||
foreach (var series in seriesToRename)
|
||||
{
|
||||
var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id);
|
||||
_logger.ProgressInfo("Renaming all files in series: {0}", series.Title);
|
||||
RenameFiles(episodeFiles, series);
|
||||
_logger.ProgressInfo("All episode files renamed for {0}", series.Title);
|
||||
}
|
||||
//foreach (var series in seriesToRename)
|
||||
//{
|
||||
// var episodeFiles = _mediaFileService.GetFilesBySeries(series.Id);
|
||||
// _logger.ProgressInfo("Renaming all files in series: {0}", series.Title);
|
||||
// RenameFiles(episodeFiles, series);
|
||||
// _logger.ProgressInfo("All episode files renamed for {0}", series.Title);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
14
src/NzbDrone.Core/MediaFiles/RenameTrackFilePreview.cs
Normal file
14
src/NzbDrone.Core/MediaFiles/RenameTrackFilePreview.cs
Normal 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; }
|
||||
}
|
||||
}
|
185
src/NzbDrone.Core/MediaFiles/RenameTrackFileService.cs
Normal file
185
src/NzbDrone.Core/MediaFiles/RenameTrackFileService.cs
Normal 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);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,8 +12,10 @@ namespace NzbDrone.Core.MediaFiles
|
|||
{
|
||||
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 ArtistId { get; set; }
|
||||
public string RelativePath { get; set; }
|
||||
public string Path { get; set; }
|
||||
public long Size { get; set; }
|
||||
|
|
18
src/NzbDrone.Core/MediaFiles/TrackFileMoveResult.cs
Normal file
18
src/NzbDrone.Core/MediaFiles/TrackFileMoveResult.cs
Normal 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; }
|
||||
}
|
||||
}
|
226
src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs
Normal file
226
src/NzbDrone.Core/MediaFiles/TrackFileMovingService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
172
src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs
Normal file
172
src/NzbDrone.Core/MediaFiles/TrackImport/ImportApprovedTracks.cs
Normal 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;
|
||||
//}
|
||||
}
|
||||
}
|
26
src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecision.cs
Normal file
26
src/NzbDrone.Core/MediaFiles/TrackImport/ImportDecision.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport
|
||||
{
|
||||
public enum ImportMode
|
||||
{
|
|
@ -1,8 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport
|
||||
{
|
||||
public class ImportResult
|
||||
{
|
|
@ -1,4 +1,4 @@
|
|||
namespace NzbDrone.Core.MediaFiles.EpisodeImport
|
||||
namespace NzbDrone.Core.MediaFiles.TrackImport
|
||||
{
|
||||
public enum ImportResultType
|
||||
{
|
|
@ -9,7 +9,8 @@ namespace NzbDrone.Core.MediaFiles
|
|||
{
|
||||
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
|
||||
|
@ -17,35 +18,36 @@ namespace NzbDrone.Core.MediaFiles
|
|||
private readonly IRecycleBinProvider _recycleBinProvider;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IMoveEpisodeFiles _episodeFileMover;
|
||||
private readonly IMoveTrackFiles _trackFileMover;
|
||||
private readonly IDiskProvider _diskProvider;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider,
|
||||
IMediaFileService mediaFileService,
|
||||
IMoveEpisodeFiles episodeFileMover,
|
||||
IMoveTrackFiles trackFileMover,
|
||||
IDiskProvider diskProvider,
|
||||
Logger logger)
|
||||
{
|
||||
_recycleBinProvider = recycleBinProvider;
|
||||
_mediaFileService = mediaFileService;
|
||||
_episodeFileMover = episodeFileMover;
|
||||
_trackFileMover = trackFileMover;
|
||||
_diskProvider = diskProvider;
|
||||
_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 existingFiles = localEpisode.Episodes
|
||||
.Where(e => e.EpisodeFileId > 0)
|
||||
.Select(e => e.EpisodeFile.Value)
|
||||
var moveFileResult = new TrackFileMoveResult();
|
||||
var existingFiles = localTrack.Tracks
|
||||
.Where(e => e.TrackFileId > 0)
|
||||
.Select(e => e.TrackFile.Value)
|
||||
.GroupBy(e => e.Id);
|
||||
|
||||
foreach (var existingFile in existingFiles)
|
||||
{
|
||||
var file = existingFile.First();
|
||||
var episodeFilePath = Path.Combine(localEpisode.Series.Path, file.RelativePath);
|
||||
var subfolder = _diskProvider.GetParentFolder(localEpisode.Series.Path).GetRelativePath(_diskProvider.GetParentFolder(episodeFilePath));
|
||||
var episodeFilePath = Path.Combine(localTrack.Artist.Path, file.RelativePath);
|
||||
var subfolder = _diskProvider.GetParentFolder(localTrack.Artist.Path).GetRelativePath(_diskProvider.GetParentFolder(episodeFilePath));
|
||||
|
||||
if (_diskProvider.FileExists(episodeFilePath))
|
||||
{
|
||||
|
@ -59,11 +61,11 @@ namespace NzbDrone.Core.MediaFiles
|
|||
|
||||
if (copyOnly)
|
||||
{
|
||||
moveFileResult.EpisodeFile = _episodeFileMover.CopyEpisodeFile(episodeFile, localEpisode);
|
||||
moveFileResult.TrackFile = _trackFileMover.CopyTrackFile(trackFile, localTrack);
|
||||
}
|
||||
else
|
||||
{
|
||||
moveFileResult.EpisodeFile = _episodeFileMover.MoveEpisodeFile(episodeFile, localEpisode);
|
||||
moveFileResult.TrackFile = _trackFileMover.MoveTrackFile(trackFile, localTrack);
|
||||
}
|
||||
|
||||
return moveFileResult;
|
||||
|
|
|
@ -6,6 +6,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
{
|
||||
public interface IProvideArtistInfo
|
||||
{
|
||||
Tuple<Artist, List<Track>> GetArtistInfo(string spotifyId);
|
||||
Tuple<Artist, List<Album>> GetArtistInfo(string lidarrId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,22 +5,24 @@ using System.Text;
|
|||
|
||||
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 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 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 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 List<string> Genres { get; set; }
|
||||
public string Label { get; set; }
|
||||
public string Type { get; set; }
|
||||
public List<TrackResource> Tracks { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -5,47 +5,19 @@ using System.Text;
|
|||
|
||||
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 ArtistResource()
|
||||
{
|
||||
|
||||
public ArtistResource() {
|
||||
Albums = new List<AlbumResource>();
|
||||
}
|
||||
|
||||
public AristResultResource Artists { get; set; }
|
||||
public AristResultResource Albums { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public string AristUrl { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public string Id { get; set; }
|
||||
public List<ImageResource> Images { get; set; }
|
||||
public string ArtistName { get; set; }
|
||||
public List<AlbumResource> Albums { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ using System.Text;
|
|||
|
||||
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
|
||||
{
|
||||
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 int TrackNumber { get; set; }
|
||||
public bool Explicit { get; set; }
|
||||
public List<ArtistInfoResource> Artists { get; set; }
|
||||
public List<ArtistResource> Artists { get; set; }
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
var httpRequest = _requestBuilder.Create()
|
||||
.SetSegment("route", "artists/" + spotifyId)
|
||||
.SetSegment("route", "artist/" + foreignArtistId)
|
||||
//.SetSegment("route", "search")
|
||||
//.AddQueryParam("type", "artist,album")
|
||||
//.AddQueryParam("q", spotifyId.ToString())
|
||||
|
@ -99,7 +99,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
{
|
||||
if (httpResponse.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
throw new ArtistNotFoundException(spotifyId);
|
||||
throw new ArtistNotFoundException(foreignArtistId);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -108,92 +108,95 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
}
|
||||
|
||||
// It is safe to assume an id will only return one Artist back
|
||||
Artist artist = new Artist();
|
||||
artist.ArtistName = httpResponse.Resource.Artists.Items[0].ArtistName;
|
||||
artist.SpotifyId = httpResponse.Resource.Artists.Items[0].Id;
|
||||
artist.Genres = httpResponse.Resource.Artists.Items[0].Genres;
|
||||
var albums = httpResponse.Resource.Albums.Select(MapAlbum);
|
||||
var artist = MapArtist(httpResponse.Resource);
|
||||
|
||||
var albumRet = MapAlbums(artist);
|
||||
artist = albumRet.Item1;
|
||||
//artist.Name = httpResponse.Resource.Artists.Items[0].ArtistName;
|
||||
|
||||
//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)
|
||||
{
|
||||
throw new HttpException(httpRequest, httpResponse);
|
||||
}
|
||||
// // Find all albums for the artist and all tracks for said album
|
||||
// ///v1/artists/{id}/albums
|
||||
// var httpRequest = _requestBuilder.Create()
|
||||
// .SetSegment("route", "artists/" + artist.ForeignArtistId + "/albums")
|
||||
// .Build();
|
||||
// httpRequest.AllowAutoRedirect = true;
|
||||
// httpRequest.SuppressHttpError = true;
|
||||
|
||||
List<Track> masterTracks = new List<Track>();
|
||||
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);
|
||||
}
|
||||
// var httpResponse = _httpClient.Get<AlbumResultResource>(httpRequest);
|
||||
|
||||
// TODO: We now need to get all tracks for each album
|
||||
// if (httpResponse.HasHttpError)
|
||||
// {
|
||||
// throw new HttpException(httpRequest, httpResponse);
|
||||
// }
|
||||
|
||||
artist.Albums = albums;
|
||||
return new Tuple<Artist, List<Track>>(artist, masterTracks);
|
||||
}
|
||||
// List<Track> masterTracks = new List<Track>();
|
||||
// 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)
|
||||
{
|
||||
var httpRequest = _requestBuilder.Create()
|
||||
.SetSegment("route", "albums/" + album.AlbumId + "/tracks")
|
||||
.Build();
|
||||
// // TODO: We now need to get all tracks for each album
|
||||
|
||||
httpRequest.AllowAutoRedirect = true;
|
||||
httpRequest.SuppressHttpError = true;
|
||||
// artist.Albums = albums;
|
||||
// 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)
|
||||
{
|
||||
throw new HttpException(httpRequest, httpResponse);
|
||||
}
|
||||
// httpRequest.AllowAutoRedirect = true;
|
||||
// httpRequest.SuppressHttpError = true;
|
||||
|
||||
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);
|
||||
}
|
||||
// var httpResponse = _httpClient.Get<TrackResultResource>(httpRequest);
|
||||
|
||||
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)
|
||||
|
@ -203,7 +206,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
var lowerTitle = title.ToLowerInvariant();
|
||||
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();
|
||||
|
||||
|
@ -224,18 +227,32 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
|
||||
var httpRequest = _requestBuilder.Create()
|
||||
.SetSegment("route", "search")
|
||||
.AddQueryParam("type", "artist") // TODO: LidarrAPI.Metadata is getting , encoded. Needs to be raw ,
|
||||
.AddQueryParam("type", "artist")
|
||||
.AddQueryParam("query", title.ToLower().Trim())
|
||||
.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)
|
||||
{
|
||||
|
@ -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>();
|
||||
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);
|
||||
return album;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
||||
return artists;
|
||||
return artist;
|
||||
}
|
||||
|
||||
//private Album MapAlbum(AlbumResource albumQuery)
|
||||
|
|
|
@ -48,11 +48,11 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
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.CleanTitle = newArtist.ArtistName.CleanSeriesTitle();
|
||||
newArtist.CleanName = newArtist.Name.CleanArtistTitle();
|
||||
//newArtist.SortTitle = ArtistNameNormalizer.Normalize(newArtist.ArtistName, newArtist.ItunesId); // There is no Sort Title
|
||||
newArtist.Added = DateTime.UtcNow;
|
||||
|
||||
|
@ -71,19 +71,19 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
private Artist AddSkyhookData(Artist newArtist)
|
||||
{
|
||||
Tuple<Artist, List<Track>> tuple;
|
||||
Tuple<Artist, List<Album>> tuple;
|
||||
|
||||
try
|
||||
{
|
||||
tuple = _artistInfo.GetArtistInfo(newArtist.SpotifyId);
|
||||
tuple = _artistInfo.GetArtistInfo(newArtist.ForeignArtistId);
|
||||
}
|
||||
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>
|
||||
{
|
||||
new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.SpotifyId)
|
||||
new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.ForeignArtistId)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace NzbDrone.Core.Music
|
|||
.SetValidator(droneFactoryValidator)
|
||||
.SetValidator(seriesAncestorValidator);
|
||||
|
||||
RuleFor(c => c.ArtistSlug).SetValidator(artistTitleSlugValidator);// TODO: Check if we are going to use a slug or artistName
|
||||
RuleFor(c => c.NameSlug).SetValidator(artistTitleSlugValidator);// TODO: Check if we are going to use a slug or artistName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Tv;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -7,24 +8,34 @@ using System.Text;
|
|||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public class Album : IEmbeddedDocument
|
||||
public class Album : ModelBase
|
||||
{
|
||||
public Album()
|
||||
{
|
||||
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 int Year { get; set; }
|
||||
public int TrackCount { get; set; }
|
||||
public string CleanTitle { get; set; }
|
||||
public DateTime ReleaseDate { get; set; }
|
||||
//public int TrackCount { get; set; }
|
||||
public string Path { get; set; }
|
||||
public List<Track> Tracks { get; set; }
|
||||
public int DiscCount { get; set; }
|
||||
//public int DiscCount { get; set; }
|
||||
public bool Monitored { 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 string ArtworkUrl { get; set; }
|
||||
public string Explicitness { get; set; }
|
||||
public String AlbumType { get; set; } //Turn this into a type similar to Series Type in TV
|
||||
//public string ArtworkUrl { get; set; }
|
||||
//public string Explicitness { get; set; }
|
||||
public AddSeriesOptions AddOptions { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0}][{1}]", ForeignAlbumId, Title.NullSafe());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
46
src/NzbDrone.Core/Music/AlbumRepository.cs
Normal file
46
src/NzbDrone.Core/Music/AlbumRepository.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System.Linq;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public interface IAlbumRepository : IBasicRepository<Album>
|
||||
{
|
||||
bool AlbumPathExists(string path);
|
||||
List<Album> GetAlbums(int artistId);
|
||||
Album FindByName(string cleanTitle);
|
||||
Album FindById(string spotifyId);
|
||||
}
|
||||
|
||||
public class AlbumRepository : BasicRepository<Album>, IAlbumRepository
|
||||
{
|
||||
public AlbumRepository(IMainDatabase database, IEventAggregator eventAggregator)
|
||||
: base(database, eventAggregator)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public bool AlbumPathExists(string path)
|
||||
{
|
||||
return Query.Where(c => c.Path == path).Any();
|
||||
}
|
||||
public List<Album> GetAlbums(int artistId)
|
||||
{
|
||||
return Query.Where(s => s.ArtistId == artistId).ToList();
|
||||
}
|
||||
|
||||
public Album FindById(string foreignAlbumId)
|
||||
{
|
||||
return Query.Where(s => s.ForeignAlbumId == foreignAlbumId).SingleOrDefault();
|
||||
}
|
||||
|
||||
public Album FindByName(string cleanTitle)
|
||||
{
|
||||
cleanTitle = cleanTitle.ToLowerInvariant();
|
||||
|
||||
return Query.Where(s => s.CleanTitle == cleanTitle)
|
||||
.SingleOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
147
src/NzbDrone.Core/Music/AlbumService.cs
Normal file
147
src/NzbDrone.Core/Music/AlbumService.cs
Normal file
|
@ -0,0 +1,147 @@
|
|||
using NLog;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music.Events;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Parser;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public interface IAlbumService
|
||||
{
|
||||
Album GetAlbum(int albumid);
|
||||
List<Album> GetAlbums(IEnumerable<int> albumIds);
|
||||
List<Album> GetAlbumsByArtist(int artistId);
|
||||
Album AddAlbum(Album newAlbum);
|
||||
Album FindById(string spotifyId);
|
||||
Album FindByTitleInexact(string title);
|
||||
void DeleteAlbum(int albumId, bool deleteFiles);
|
||||
List<Album> GetAllAlbums();
|
||||
Album UpdateAlbum(Album album);
|
||||
List<Album> UpdateAlbums(List<Album> album);
|
||||
void InsertMany(List<Album> albums);
|
||||
void UpdateMany(List<Album> albums);
|
||||
void DeleteMany(List<Album> albums);
|
||||
bool AlbumPathExists(string folder);
|
||||
void RemoveAddOptions(Album album);
|
||||
}
|
||||
|
||||
public class AlbumService : IAlbumService
|
||||
{
|
||||
private readonly IAlbumRepository _albumRepository;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly ITrackService _trackService;
|
||||
private readonly IBuildFileNames _fileNameBuilder;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public AlbumService(IAlbumRepository albumRepository,
|
||||
IEventAggregator eventAggregator,
|
||||
ITrackService trackService,
|
||||
IBuildFileNames fileNameBuilder,
|
||||
Logger logger)
|
||||
{
|
||||
_albumRepository = albumRepository;
|
||||
_eventAggregator = eventAggregator;
|
||||
_trackService = trackService;
|
||||
_fileNameBuilder = fileNameBuilder;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Album AddAlbum(Album newAlbum)
|
||||
{
|
||||
_albumRepository.Insert(newAlbum);
|
||||
_eventAggregator.PublishEvent(new AlbumAddedEvent(GetAlbum(newAlbum.Id)));
|
||||
|
||||
return newAlbum;
|
||||
}
|
||||
|
||||
public bool AlbumPathExists(string folder)
|
||||
{
|
||||
return _albumRepository.AlbumPathExists(folder);
|
||||
}
|
||||
|
||||
public void DeleteAlbum(int albumId, bool deleteFiles)
|
||||
{
|
||||
var album = _albumRepository.Get(albumId);
|
||||
_albumRepository.Delete(albumId);
|
||||
_eventAggregator.PublishEvent(new AlbumDeletedEvent(album, deleteFiles));
|
||||
}
|
||||
|
||||
public Album FindById(string spotifyId)
|
||||
{
|
||||
return _albumRepository.FindById(spotifyId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public Album FindByTitleInexact(string title)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public List<Album> GetAllAlbums()
|
||||
{
|
||||
return _albumRepository.All().ToList();
|
||||
}
|
||||
|
||||
public Album GetAlbum(int albumId)
|
||||
{
|
||||
return _albumRepository.Get(albumId);
|
||||
}
|
||||
|
||||
public List<Album> GetAlbums(IEnumerable<int> albumIds)
|
||||
{
|
||||
return _albumRepository.Get(albumIds).ToList();
|
||||
}
|
||||
|
||||
public List<Album> GetAlbumsByArtist(int artistId)
|
||||
{
|
||||
return _albumRepository.GetAlbums(artistId).ToList();
|
||||
}
|
||||
|
||||
public void RemoveAddOptions(Album album)
|
||||
{
|
||||
_albumRepository.SetFields(album, s => s.AddOptions);
|
||||
}
|
||||
|
||||
public void InsertMany(List<Album> albums)
|
||||
{
|
||||
_albumRepository.InsertMany(albums);
|
||||
}
|
||||
|
||||
public void UpdateMany(List<Album> albums)
|
||||
{
|
||||
_albumRepository.UpdateMany(albums);
|
||||
}
|
||||
|
||||
public void DeleteMany(List<Album> albums)
|
||||
{
|
||||
_albumRepository.DeleteMany(albums);
|
||||
}
|
||||
|
||||
public Album UpdateAlbum(Album album)
|
||||
{
|
||||
var storedAlbum = GetAlbum(album.Id); // Is it Id or iTunesId?
|
||||
|
||||
var updatedAlbum = _albumRepository.Update(album);
|
||||
_eventAggregator.PublishEvent(new AlbumEditedEvent(updatedAlbum, storedAlbum));
|
||||
|
||||
return updatedAlbum;
|
||||
}
|
||||
|
||||
public List<Album> UpdateAlbums(List<Album> album)
|
||||
{
|
||||
_logger.Debug("Updating {0} album", album.Count);
|
||||
|
||||
_albumRepository.UpdateMany(album);
|
||||
_logger.Debug("{0} albums updated", album.Count);
|
||||
|
||||
return album;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,21 +22,23 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
}
|
||||
|
||||
public string SpotifyId { get; set; }
|
||||
public string ArtistName { get; set; }
|
||||
public string ArtistSlug { get; set; }
|
||||
public string CleanTitle { 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 Name { get; set; }
|
||||
public string NameSlug { get; set; }
|
||||
public string CleanName { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public bool Monitored { get; set; }
|
||||
public bool AlbumFolder { get; set; }
|
||||
public bool ArtistFolder { get; set; }
|
||||
public DateTime? LastInfoSync { get; set; }
|
||||
public DateTime? LastDiskSync { get; set; }
|
||||
public int Status { get; set; } // TODO: Figure out what this is, do we need it?
|
||||
public string Path { get; set; }
|
||||
public List<MediaCover.MediaCover> Images { get; set; }
|
||||
public List<string> Genres { get; set; }
|
||||
public int QualityProfileId { get; set; }
|
||||
public string RootFolderPath { get; set; }
|
||||
public DateTime Added { get; set; }
|
||||
public LazyLoaded<Profile> Profile { get; set; }
|
||||
|
@ -47,16 +49,20 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
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)
|
||||
{
|
||||
|
||||
SpotifyId = otherArtist.SpotifyId;
|
||||
ArtistName = otherArtist.ArtistName;
|
||||
ArtistSlug = otherArtist.ArtistSlug;
|
||||
CleanTitle = otherArtist.CleanTitle;
|
||||
ForeignArtistId = otherArtist.ForeignArtistId;
|
||||
MBId = otherArtist.MBId;
|
||||
TADBId = otherArtist.TADBId;
|
||||
DiscogsId = otherArtist.DiscogsId;
|
||||
AMId = otherArtist.AMId;
|
||||
Name = otherArtist.Name;
|
||||
NameSlug = otherArtist.NameSlug;
|
||||
CleanName = otherArtist.CleanName;
|
||||
Monitored = otherArtist.Monitored;
|
||||
AlbumFolder = otherArtist.AlbumFolder;
|
||||
LastInfoSync = otherArtist.LastInfoSync;
|
||||
|
@ -69,7 +75,6 @@ namespace NzbDrone.Core.Music
|
|||
ProfileId = otherArtist.ProfileId;
|
||||
Albums = otherArtist.Albums;
|
||||
Tags = otherArtist.Tags;
|
||||
ArtistFolder = otherArtist.ArtistFolder;
|
||||
AddOptions = otherArtist.AddOptions;
|
||||
|
||||
Albums = otherArtist.Albums;
|
||||
|
|
|
@ -24,16 +24,16 @@ namespace NzbDrone.Core.Music
|
|||
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)
|
||||
{
|
||||
cleanName = cleanName.ToLowerInvariant();
|
||||
|
||||
return Query.Where(s => s.CleanTitle == cleanName)
|
||||
return Query.Where(s => s.CleanName == cleanName)
|
||||
.SingleOrDefault();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,11 +89,12 @@ namespace NzbDrone.Core.Music
|
|||
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)
|
||||
{
|
||||
return _artistRepository.Get(artistIds).ToList();
|
||||
|
@ -110,11 +111,11 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
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)
|
||||
{
|
||||
_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);
|
||||
foreach (var s in artist)
|
||||
{
|
||||
_logger.Trace("Updating: {0}", s.ArtistName);
|
||||
_logger.Trace("Updating: {0}", s.Name);
|
||||
if (!s.RootFolderPath.IsNullOrWhiteSpace())
|
||||
{
|
||||
var folderName = new DirectoryInfo(s.Path).Name;
|
||||
s.Path = Path.Combine(s.RootFolderPath, folderName);
|
||||
_logger.Trace("Changing path for {0} to {1}", s.ArtistName, s.Path);
|
||||
_logger.Trace("Changing path for {0} to {1}", s.Name, s.Path);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
_logger.Trace("Not changing path for: {0}", s.ArtistName);
|
||||
_logger.Trace("Not changing path for: {0}", s.Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace NzbDrone.Core.Music
|
|||
dynamic instance = context.ParentContext.InstanceToValidate;
|
||||
var instanceId = (int)instance.Id;
|
||||
|
||||
return !_artistService.GetAllArtists().Exists(s => s.ArtistSlug.Equals(context.PropertyValue.ToString()) && s.Id != instanceId);
|
||||
return !_artistService.GetAllArtists().Exists(s => s.NameSlug.Equals(context.PropertyValue.ToString()) && s.Id != instanceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
18
src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs
Normal file
18
src/NzbDrone.Core/Music/Events/AlbumAddedEvent.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using NzbDrone.Common.Messaging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.Music.Events
|
||||
{
|
||||
public class AlbumAddedEvent : IEvent
|
||||
{
|
||||
public Album Album { get; private set; }
|
||||
|
||||
public AlbumAddedEvent(Album album)
|
||||
{
|
||||
Album = album;
|
||||
}
|
||||
}
|
||||
}
|
20
src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs
Normal file
20
src/NzbDrone.Core/Music/Events/AlbumDeletedEvent.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using NzbDrone.Common.Messaging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.Music.Events
|
||||
{
|
||||
public class AlbumDeletedEvent : IEvent
|
||||
{
|
||||
public Album Album { get; private set; }
|
||||
public bool DeleteFiles { get; private set; }
|
||||
|
||||
public AlbumDeletedEvent(Album album, bool deleteFiles)
|
||||
{
|
||||
Album = album;
|
||||
DeleteFiles = deleteFiles;
|
||||
}
|
||||
}
|
||||
}
|
20
src/NzbDrone.Core/Music/Events/AlbumEditedEvent.cs
Normal file
20
src/NzbDrone.Core/Music/Events/AlbumEditedEvent.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using NzbDrone.Common.Messaging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.Music.Events
|
||||
{
|
||||
public class AlbumEditedEvent : IEvent
|
||||
{
|
||||
public Album Album { get; private set; }
|
||||
public Album OldAlbum { get; private set; }
|
||||
|
||||
public AlbumEditedEvent(Album album, Album oldAlbum)
|
||||
{
|
||||
Album = album;
|
||||
OldAlbum = oldAlbum;
|
||||
}
|
||||
}
|
||||
}
|
23
src/NzbDrone.Core/Music/Events/AlbumInfoRefreshedEvent.cs
Normal file
23
src/NzbDrone.Core/Music/Events/AlbumInfoRefreshedEvent.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using NzbDrone.Common.Messaging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.Music.Events
|
||||
{
|
||||
public class AlbumInfoRefreshedEvent : IEvent
|
||||
{
|
||||
public Artist Artist { get; set; }
|
||||
public ReadOnlyCollection<Album> Added { get; private set; }
|
||||
public ReadOnlyCollection<Album> Updated { get; private set; }
|
||||
|
||||
public AlbumInfoRefreshedEvent(Artist artist, IList<Album> added, IList<Album> updated)
|
||||
{
|
||||
Artist = artist;
|
||||
Added = new ReadOnlyCollection<Album>(added);
|
||||
Updated = new ReadOnlyCollection<Album>(updated);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,13 +9,13 @@ namespace NzbDrone.Core.Music.Events
|
|||
{
|
||||
public class TrackInfoRefreshedEvent : IEvent
|
||||
{
|
||||
public Artist Artist { get; set; }
|
||||
public Album Album { get; set; }
|
||||
public ReadOnlyCollection<Track> Added { 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);
|
||||
Updated = new ReadOnlyCollection<Track>(updated);
|
||||
}
|
||||
|
|
133
src/NzbDrone.Core/Music/RefreshAlbumService.cs
Normal file
133
src/NzbDrone.Core/Music/RefreshAlbumService.cs
Normal file
|
@ -0,0 +1,133 @@
|
|||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music.Events;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.Music
|
||||
{
|
||||
public interface IRefreshAlbumService
|
||||
{
|
||||
void RefreshAlbumInfo(Artist artist, IEnumerable<Album> remoteAlbums);
|
||||
}
|
||||
|
||||
public class RefreshAlbumService : IRefreshAlbumService
|
||||
{
|
||||
private readonly IAlbumService _albumService;
|
||||
private readonly IRefreshTrackService _refreshTrackService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public RefreshAlbumService(IAlbumService albumService, IRefreshTrackService refreshTrackService, IEventAggregator eventAggregator, Logger logger)
|
||||
{
|
||||
_albumService = albumService;
|
||||
_refreshTrackService = refreshTrackService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void RefreshAlbumInfo(Artist artist, IEnumerable<Album> remoteAlbums)
|
||||
{
|
||||
_logger.Info("Starting album info refresh for: {0}", artist);
|
||||
var successCount = 0;
|
||||
var failCount = 0;
|
||||
|
||||
var existingAlbums = _albumService.GetAlbumsByArtist(artist.Id);
|
||||
var albums = artist.Albums;
|
||||
|
||||
var updateList = new List<Album>();
|
||||
var newList = new List<Album>();
|
||||
var dupeFreeRemoteAlbums = remoteAlbums.DistinctBy(m => new { m.ForeignAlbumId, m.ReleaseDate }).ToList();
|
||||
|
||||
foreach (var album in OrderAlbums(artist, dupeFreeRemoteAlbums))
|
||||
{
|
||||
try
|
||||
{
|
||||
var albumToUpdate = GetAlbumToUpdate(artist, album, existingAlbums);
|
||||
|
||||
if (albumToUpdate != null)
|
||||
{
|
||||
existingAlbums.Remove(albumToUpdate);
|
||||
updateList.Add(albumToUpdate);
|
||||
}
|
||||
else
|
||||
{
|
||||
albumToUpdate = new Album();
|
||||
albumToUpdate.Monitored = artist.Monitored;
|
||||
newList.Add(albumToUpdate);
|
||||
//var folderName = _fileNameBuilder.GetAlbumFolder(albumToUpdate); //This likely does not belong here, need to create AddAlbumService
|
||||
//albumToUpdate.Path = Path.Combine(newArtist.RootFolderPath, folderName);
|
||||
}
|
||||
|
||||
albumToUpdate.ForeignAlbumId = album.ForeignAlbumId;
|
||||
albumToUpdate.CleanTitle = album.CleanTitle;
|
||||
//albumToUpdate.TrackNumber = album.TrackNumber;
|
||||
albumToUpdate.Title = album.Title ?? "Unknown";
|
||||
//albumToUpdate.AlbumId = album.AlbumId;
|
||||
//albumToUpdate.Album = album.Album;
|
||||
//albumToUpdate.Explicit = album.Explicit;
|
||||
albumToUpdate.ArtistId = artist.Id;
|
||||
albumToUpdate.Path = artist.Path + album.Title;
|
||||
albumToUpdate.AlbumType = album.AlbumType;
|
||||
//albumToUpdate.Compilation = album.Compilation;
|
||||
|
||||
_refreshTrackService.RefreshTrackInfo(album, album.Tracks);
|
||||
|
||||
|
||||
successCount++;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Fatal(e, "An error has occurred while updating track info for artist {0}. {1}", artist, album);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
var allAlbums = new List<Album>();
|
||||
allAlbums.AddRange(newList);
|
||||
allAlbums.AddRange(updateList);
|
||||
|
||||
// TODO: See if anything needs to be done here
|
||||
//AdjustMultiEpisodeAirTime(artist, allTracks);
|
||||
//AdjustDirectToDvdAirDate(artist, allTracks);
|
||||
|
||||
_albumService.DeleteMany(existingAlbums);
|
||||
_albumService.UpdateMany(updateList);
|
||||
_albumService.InsertMany(newList);
|
||||
|
||||
_eventAggregator.PublishEvent(new AlbumInfoRefreshedEvent(artist, newList, updateList));
|
||||
|
||||
if (failCount != 0)
|
||||
{
|
||||
_logger.Info("Finished album refresh for artist: {0}. Successful: {1} - Failed: {2} ",
|
||||
artist.Name, successCount, failCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("Finished album refresh for artist: {0}.", artist);
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetMonitoredStatus(Album album, IEnumerable<Artist> artists)
|
||||
{
|
||||
var artist = artists.SingleOrDefault(c => c.Id == album.ArtistId);
|
||||
return album == null || album.Monitored;
|
||||
}
|
||||
|
||||
|
||||
private Album GetAlbumToUpdate(Artist artist, Album album, List<Album> existingAlbums)
|
||||
{
|
||||
return existingAlbums.FirstOrDefault(e => e.ForeignAlbumId == album.ForeignAlbumId && e.ReleaseDate == album.ReleaseDate);
|
||||
}
|
||||
|
||||
private IEnumerable<Album> OrderAlbums(Artist artist, List<Album> albums)
|
||||
{
|
||||
return albums.OrderBy(e => e.ForeignAlbumId).ThenBy(e => e.ReleaseDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ namespace NzbDrone.Core.Music
|
|||
{
|
||||
private readonly IProvideArtistInfo _artistInfo;
|
||||
private readonly IArtistService _artistService;
|
||||
private readonly IRefreshTrackService _refreshTrackService;
|
||||
private readonly IRefreshAlbumService _refreshAlbumService;
|
||||
private readonly IEventAggregator _eventAggregator;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly ICheckIfArtistShouldBeRefreshed _checkIfArtistShouldBeRefreshed;
|
||||
|
@ -28,7 +28,7 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
public RefreshArtistService(IProvideArtistInfo artistInfo,
|
||||
IArtistService artistService,
|
||||
IRefreshTrackService refreshTrackService,
|
||||
IRefreshAlbumService refreshAlbumService,
|
||||
IEventAggregator eventAggregator,
|
||||
IDiskScanService diskScanService,
|
||||
ICheckIfArtistShouldBeRefreshed checkIfArtistShouldBeRefreshed,
|
||||
|
@ -36,7 +36,7 @@ namespace NzbDrone.Core.Music
|
|||
{
|
||||
_artistInfo = artistInfo;
|
||||
_artistService = artistService;
|
||||
_refreshTrackService = refreshTrackService;
|
||||
_refreshAlbumService = refreshAlbumService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_diskScanService = diskScanService;
|
||||
_checkIfArtistShouldBeRefreshed = checkIfArtistShouldBeRefreshed;
|
||||
|
@ -45,33 +45,33 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
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
|
||||
{
|
||||
tuple = _artistInfo.GetArtistInfo(artist.SpotifyId);
|
||||
tuple = _artistInfo.GetArtistInfo(artist.ForeignArtistId);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
artist.SpotifyId = 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.ForeignArtistId = artistInfo.ForeignArtistId;
|
||||
}
|
||||
|
||||
artist.ArtistName = artistInfo.ArtistName;
|
||||
artist.ArtistSlug = artistInfo.ArtistSlug;
|
||||
artist.Name = artistInfo.Name;
|
||||
artist.NameSlug = artistInfo.NameSlug;
|
||||
artist.Overview = artistInfo.Overview;
|
||||
artist.Status = artistInfo.Status;
|
||||
artist.CleanTitle = artistInfo.CleanTitle;
|
||||
artist.CleanName = artistInfo.CleanName;
|
||||
artist.LastInfoSync = DateTime.UtcNow;
|
||||
artist.Images = artistInfo.Images;
|
||||
artist.Genres = artistInfo.Genres;
|
||||
|
@ -89,19 +89,20 @@ namespace NzbDrone.Core.Music
|
|||
artist.Albums = UpdateAlbums(artist, artistInfo);
|
||||
|
||||
_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));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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?
|
||||
if (existingAlbum == null)
|
||||
|
@ -112,7 +113,7 @@ namespace NzbDrone.Core.Music
|
|||
// 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;
|
||||
}
|
||||
|
||||
|
@ -136,7 +137,7 @@ namespace NzbDrone.Core.Music
|
|||
}
|
||||
else
|
||||
{
|
||||
var allArtists = _artistService.GetAllArtists().OrderBy(c => c.ArtistName).ToList();
|
||||
var allArtists = _artistService.GetAllArtists().OrderBy(c => c.Name).ToList();
|
||||
|
||||
foreach (var artist in allArtists)
|
||||
{
|
||||
|
@ -156,8 +157,8 @@ namespace NzbDrone.Core.Music
|
|||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("Skipping refresh of artist: {0}", artist.ArtistName);
|
||||
//TODO: _diskScanService.Scan(artist);
|
||||
_logger.Info("Skipping refresh of artist: {0}", artist.Name);
|
||||
_diskScanService.Scan(artist);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace NzbDrone.Core.Music
|
|||
{
|
||||
public interface IRefreshTrackService
|
||||
{
|
||||
void RefreshTrackInfo(Artist artist, IEnumerable<Track> remoteTracks);
|
||||
void RefreshTrackInfo(Album album, IEnumerable<Track> remoteTracks);
|
||||
}
|
||||
|
||||
public class RefreshTrackService : IRefreshTrackService
|
||||
|
@ -27,24 +27,23 @@ namespace NzbDrone.Core.Music
|
|||
_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 failCount = 0;
|
||||
|
||||
var existingTracks = _trackService.GetTracksByArtist(artist.SpotifyId);
|
||||
var albums = artist.Albums;
|
||||
var existingTracks = _trackService.GetTracksByAlbum(album.ArtistId, album.Id); // TODO: JOE: I believe this should be string, string
|
||||
|
||||
var updateList = new List<Track>();
|
||||
var newList = new List<Track>();
|
||||
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
|
||||
{
|
||||
var trackToUpdate = GetTrackToUpdate(artist, track, existingTracks);
|
||||
var trackToUpdate = GetTrackToUpdate(album, track, existingTracks);
|
||||
|
||||
if (trackToUpdate != null)
|
||||
{
|
||||
|
@ -54,24 +53,17 @@ namespace NzbDrone.Core.Music
|
|||
else
|
||||
{
|
||||
trackToUpdate = new Track();
|
||||
trackToUpdate.Monitored = GetMonitoredStatus(track, albums);
|
||||
trackToUpdate.Monitored = album.Monitored;
|
||||
newList.Add(trackToUpdate);
|
||||
}
|
||||
|
||||
trackToUpdate.SpotifyTrackId = track.SpotifyTrackId;
|
||||
trackToUpdate.ForeignTrackId = track.ForeignTrackId;
|
||||
trackToUpdate.TrackNumber = track.TrackNumber;
|
||||
trackToUpdate.Title = track.Title ?? "Unknown";
|
||||
trackToUpdate.AlbumId = track.AlbumId;
|
||||
trackToUpdate.AlbumId = album.Id;
|
||||
trackToUpdate.Album = track.Album;
|
||||
trackToUpdate.Explict = track.Explict;
|
||||
if (track.ArtistSpotifyId.IsNullOrWhiteSpace())
|
||||
{
|
||||
trackToUpdate.ArtistSpotifyId = artist.SpotifyId;
|
||||
} else
|
||||
{
|
||||
trackToUpdate.ArtistSpotifyId = track.ArtistSpotifyId;
|
||||
}
|
||||
trackToUpdate.ArtistId = track.ArtistId;
|
||||
trackToUpdate.Explicit = track.Explicit;
|
||||
trackToUpdate.ArtistId = album.ArtistId;
|
||||
trackToUpdate.Compilation = track.Compilation;
|
||||
|
||||
// TODO: Implement rest of [RefreshTrackService] fields
|
||||
|
@ -82,7 +74,7 @@ namespace NzbDrone.Core.Music
|
|||
}
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
@ -99,16 +91,16 @@ namespace NzbDrone.Core.Music
|
|||
_trackService.UpdateMany(updateList);
|
||||
_trackService.InsertMany(newList);
|
||||
|
||||
_eventAggregator.PublishEvent(new TrackInfoRefreshedEvent(artist, newList, updateList));
|
||||
_eventAggregator.PublishEvent(new TrackInfoRefreshedEvent(album, newList, updateList));
|
||||
|
||||
if (failCount != 0)
|
||||
{
|
||||
_logger.Info("Finished track refresh for artist: {0}. Successful: {1} - Failed: {2} ",
|
||||
artist.ArtistName, successCount, failCount);
|
||||
_logger.Info("Finished track refresh for album: {0}. Successful: {1} - Failed: {2} ",
|
||||
album.Title, successCount, failCount);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
var album = albums.SingleOrDefault(c => c.AlbumId == track.AlbumId);
|
||||
var album = albums.SingleOrDefault(c => c.Id == track.AlbumId);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -26,13 +26,13 @@ namespace NzbDrone.Core.Music
|
|||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,20 +17,20 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd";
|
||||
|
||||
public string SpotifyTrackId { get; set; }
|
||||
public string AlbumId { get; set; }
|
||||
public LazyLoaded<Artist> Artist { get; set; }
|
||||
public string ArtistSpotifyId { get; set; }
|
||||
public long ArtistId { get; set; } // This is the DB Id of the Artist, not the SpotifyId
|
||||
public string ForeignTrackId { get; set; }
|
||||
public int AlbumId { get; set; }
|
||||
public Artist Artist { get; set; }
|
||||
|
||||
public int ArtistId { get; set; } // This is the DB Id of the Artist, not the SpotifyId
|
||||
//public int CompilationId { get; set; }
|
||||
public bool Compilation { get; set; }
|
||||
public int TrackNumber { get; set; }
|
||||
public string Title { get; set; }
|
||||
public bool Ignored { get; set; }
|
||||
public bool Explict { get; set; }
|
||||
//public bool Ignored { get; set; }
|
||||
public bool Explicit { get; set; }
|
||||
public bool Monitored { get; set; }
|
||||
public int TrackFileId { get; set; }
|
||||
public DateTime? ReleaseDate { get; set; }
|
||||
//public DateTime? ReleaseDate { get; set; }
|
||||
|
||||
public LazyLoaded<TrackFile> TrackFile { get; set; }
|
||||
|
||||
|
@ -40,7 +40,7 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0}]{1}", SpotifyTrackId, Title.NullSafe());
|
||||
return string.Format("[{0}]{1}", ForeignTrackId, Title.NullSafe());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,15 +13,15 @@ namespace NzbDrone.Core.Music
|
|||
{
|
||||
public interface ITrackRepository : IBasicRepository<Track>
|
||||
{
|
||||
Track Find(string artistId, string albumId, int trackNumber);
|
||||
List<Track> GetTracks(string artistId);
|
||||
List<Track> GetTracks(string artistId, string albumId);
|
||||
Track Find(int artistId, int albumId, int trackNumber);
|
||||
List<Track> GetTracks(int artistId);
|
||||
List<Track> GetTracks(int artistId, int albumId);
|
||||
List<Track> GetTracksByFileId(int fileId);
|
||||
List<Track> TracksWithFiles(string artistId);
|
||||
List<Track> TracksWithFiles(int artistId);
|
||||
PagingSpec<Track> TracksWithoutFiles(PagingSpec<Track> pagingSpec);
|
||||
PagingSpec<Track> TracksWhereCutoffUnmet(PagingSpec<Track> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -37,23 +37,23 @@ namespace NzbDrone.Core.Music
|
|||
_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.TrackNumber == trackNumber)
|
||||
.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)
|
||||
.ToList();
|
||||
}
|
||||
|
@ -63,10 +63,10 @@ namespace NzbDrone.Core.Music
|
|||
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)
|
||||
.Where(e => e.ArtistSpotifyId == artistId);
|
||||
.Where(e => e.ArtistId == artistId);
|
||||
}
|
||||
|
||||
public PagingSpec<Track> TracksWhereCutoffUnmet(PagingSpec<Track> pagingSpec, List<QualitiesBelowCutoff> qualitiesBelowCutoff)
|
||||
|
@ -85,7 +85,7 @@ namespace NzbDrone.Core.Music
|
|||
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();
|
||||
|
||||
|
@ -118,7 +118,7 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
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)
|
||||
.AndWhere(e => e.TrackFileId == 0)
|
||||
.AndWhere(BuildAirDateUtcCutoffWhereClause(currentTime))
|
||||
|
@ -130,7 +130,7 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
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)
|
||||
.Where(pagingSpec.FilterExpression)
|
||||
.AndWhere(e => e.TrackFileId != 0)
|
||||
|
|
|
@ -15,12 +15,12 @@ namespace NzbDrone.Core.Music
|
|||
{
|
||||
Track GetTrack(int id);
|
||||
List<Track> GetTracks(IEnumerable<int> ids);
|
||||
Track FindTrack(string artistId, string albumId, int trackNumber);
|
||||
Track FindTrackByTitle(string artistId, string albumId, string releaseTitle);
|
||||
List<Track> GetTracksByArtist(string artistId);
|
||||
//List<Track> GetTracksByAlbum(string artistId, string albumId);
|
||||
Track FindTrack(int artistId, int albumId, int trackNumber);
|
||||
Track FindTrackByTitle(int artistId, int albumId, string releaseTitle);
|
||||
List<Track> GetTracksByArtist(int artistId);
|
||||
List<Track> GetTracksByAlbum(int artistId, int albumId);
|
||||
//List<Track> GetTracksByAlbumTitle(string artistId, string albumTitle);
|
||||
List<Track> TracksWithFiles(string artistId);
|
||||
List<Track> TracksWithFiles(int artistId);
|
||||
//PagingSpec<Track> TracksWithoutFiles(PagingSpec<Track> pagingSpec);
|
||||
List<Track> GetTracksByFileId(int trackFileId);
|
||||
void UpdateTrack(Track track);
|
||||
|
@ -29,7 +29,7 @@ namespace NzbDrone.Core.Music
|
|||
void InsertMany(List<Track> tracks);
|
||||
void UpdateMany(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
|
||||
|
@ -55,22 +55,22 @@ namespace NzbDrone.Core.Music
|
|||
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();
|
||||
}
|
||||
|
||||
public List<Track> GetTracksByAlbum(string artistId, string albumId)
|
||||
public List<Track> GetTracksByAlbum(int artistId, int 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
|
||||
var normalizedReleaseTitle = Parser.Parser.NormalizeEpisodeTitle(releaseTitle).Replace(".", " ");
|
||||
|
@ -96,7 +96,7 @@ namespace NzbDrone.Core.Music
|
|||
return null;
|
||||
}
|
||||
|
||||
public List<Track> TracksWithFiles(string artistId)
|
||||
public List<Track> TracksWithFiles(int 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);
|
||||
}
|
||||
|
||||
public void SetTrackMonitoredByAlbum(string artistId, string albumId, bool monitored)
|
||||
public void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored)
|
||||
{
|
||||
_trackRepository.SetMonitoredByAlbum(artistId, albumId, monitored);
|
||||
}
|
||||
|
||||
public void UpdateEpisodes(List<Track> tracks)
|
||||
public void UpdateTracks(List<Track> tracks)
|
||||
{
|
||||
_trackRepository.UpdateMany(tracks);
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ namespace NzbDrone.Core.Music
|
|||
|
||||
public void HandleAsync(ArtistDeletedEvent message)
|
||||
{
|
||||
var tracks = GetTracksByArtist(message.Artist.SpotifyId);
|
||||
var tracks = GetTracksByArtist(message.Artist.Id);
|
||||
_trackRepository.DeleteMany(tracks);
|
||||
}
|
||||
|
||||
|
@ -182,10 +182,5 @@ namespace NzbDrone.Core.Music
|
|||
_logger.Debug("Linking [{0}] > [{1}]", message.TrackFile.RelativePath, track);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateTracks(List<Track> tracks)
|
||||
{
|
||||
_trackRepository.UpdateMany(tracks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -288,7 +288,6 @@
|
|||
</Compile>
|
||||
<Compile Include="Datastore\Migration\105_rename_torrent_downloadstation.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\MigrationController.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" />
|
||||
|
@ -725,7 +724,13 @@
|
|||
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
|
||||
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.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\RenameSeriesCommand.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RescanSeriesCommand.cs" />
|
||||
|
@ -740,12 +745,10 @@
|
|||
<Compile Include="MediaFiles\EpisodeFile.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeFileMoveResult.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeFileMovingService.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\ImportResult.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\IImportDecisionEngineSpecification.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\ImportApprovedEpisodes.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\ImportDecision.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\ManualImportCommand.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\Manual\ManualImportItem.cs" />
|
||||
|
@ -761,11 +764,14 @@
|
|||
<Compile Include="MediaFiles\EpisodeImport\Specifications\UnverifiedSceneNumberingSpecification.cs" />
|
||||
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecification.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\EpisodeFileAddedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\EpisodeFileDeletedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\EpisodeFolderCreatedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\EpisodeImportedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Commands\RescanArtistCommand.cs" />
|
||||
<Compile Include="MediaFiles\Events\SeriesRenamedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\SeriesScanSkippedEvent.cs" />
|
||||
<Compile Include="MediaFiles\Events\SeriesScannedEvent.cs" />
|
||||
|
@ -789,6 +795,9 @@
|
|||
<Compile Include="MediaFiles\RenameEpisodeFileService.cs" />
|
||||
<Compile Include="MediaFiles\SameFilenameException.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\UpgradeMediaFileService.cs" />
|
||||
<Compile Include="Messaging\Commands\BackendCommandAttribute.cs" />
|
||||
|
@ -816,7 +825,7 @@
|
|||
<Compile Include="Messaging\IProcessMessage.cs" />
|
||||
<Compile Include="MetadataSource\IProvideArtistInfo.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\ArtistResource.cs" />
|
||||
<Compile Include="MetadataSource\SkyHook\Resource\EpisodeResource.cs" />
|
||||
|
@ -825,7 +834,7 @@
|
|||
<Compile Include="MetadataSource\SkyHook\Resource\SeasonResource.cs" />
|
||||
<Compile Include="MetadataSource\SkyHook\Resource\ShowResource.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\SearchSeriesComparer.cs" />
|
||||
<Compile Include="MetadataSource\SkyHook\SkyHookException.cs" />
|
||||
|
@ -857,17 +866,24 @@
|
|||
<Compile Include="Music\Artist.cs" />
|
||||
<Compile Include="Music\ArtistAddedHandler.cs" />
|
||||
<Compile Include="Music\ArtistNameNormalizer.cs" />
|
||||
<Compile Include="Music\AlbumService.cs" />
|
||||
<Compile Include="Music\AlbumRepository.cs" />
|
||||
<Compile Include="Music\ArtistSlugValidator.cs" />
|
||||
<Compile Include="Music\ArtistRepository.cs" />
|
||||
<Compile Include="Music\ArtistService.cs" />
|
||||
<Compile Include="Music\Commands\RefreshArtistCommand.cs" />
|
||||
<Compile Include="Music\Events\AlbumAddedEvent.cs" />
|
||||
<Compile Include="Music\Events\ArtistAddedEvent.cs" />
|
||||
<Compile Include="Music\Events\AlbumDeletedEvent.cs" />
|
||||
<Compile Include="Music\Events\ArtistDeletedEvent.cs" />
|
||||
<Compile Include="Music\Events\AlbumEditedEvent.cs" />
|
||||
<Compile Include="Music\Events\ArtistEditedEvent.cs" />
|
||||
<Compile Include="Music\Events\ArtistRefreshStartingEvent.cs" />
|
||||
<Compile Include="Music\Events\ArtistUpdatedEvent.cs" />
|
||||
<Compile Include="Music\Events\AlbumInfoRefreshedEvent.cs" />
|
||||
<Compile Include="Music\Events\TrackInfoRefreshedEvent.cs" />
|
||||
<Compile Include="Music\RefreshArtistService.cs" />
|
||||
<Compile Include="Music\RefreshAlbumService.cs" />
|
||||
<Compile Include="Music\RefreshTrackService.cs" />
|
||||
<Compile Include="Music\ShouldRefreshArtist.cs" />
|
||||
<Compile Include="Music\Track.cs" />
|
||||
|
@ -928,6 +944,7 @@
|
|||
<Compile Include="Parser\IsoLanguage.cs" />
|
||||
<Compile Include="Parser\IsoLanguages.cs" />
|
||||
<Compile Include="Parser\LanguageParser.cs" />
|
||||
<Compile Include="Parser\Model\ArtistTitleInfo.cs" />
|
||||
<Compile Include="Parser\Model\LocalTrack.cs" />
|
||||
<Compile Include="Parser\Model\ParsedTrackInfo.cs" />
|
||||
<Compile Include="Profiles\Delay\DelayProfile.cs" />
|
||||
|
|
|
@ -18,10 +18,15 @@ namespace NzbDrone.Core.Organizer
|
|||
public interface IBuildFileNames
|
||||
{
|
||||
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 BuildTrackFilePath(Artist artist, Album album, string fileName, string extension);
|
||||
string BuildSeasonPath(Series series, int seasonNumber);
|
||||
string BuildAlbumPath(Artist artist, Album album);
|
||||
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
||||
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);
|
||||
|
||||
// TODO: Implement Music functions
|
||||
|
@ -42,6 +47,9 @@ namespace NzbDrone.Core.Organizer
|
|||
private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})",
|
||||
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+)?})",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
|
@ -59,6 +67,12 @@ namespace NzbDrone.Core.Organizer
|
|||
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
|
||||
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 TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled);
|
||||
|
||||
|
@ -140,6 +154,47 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
||||
|
@ -149,6 +204,15 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
var path = series.Path;
|
||||
|
@ -172,6 +236,24 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault();
|
||||
|
@ -232,6 +314,20 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
if (namingConfig == null)
|
||||
|
@ -247,6 +343,21 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
title = title.Replace("&", "and");
|
||||
|
@ -284,8 +395,15 @@ namespace NzbDrone.Core.Organizer
|
|||
|
||||
private void AddArtistTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Artist artist)
|
||||
{
|
||||
tokenHandlers["{Artist Name}"] = m => artist.ArtistName;
|
||||
tokenHandlers["{Artist CleanTitle}"] = m => CleanTitle(artist.ArtistName);
|
||||
tokenHandlers["{Artist Name}"] = m => artist.Name;
|
||||
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)
|
||||
|
@ -432,6 +550,12 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile);
|
||||
|
@ -439,6 +563,13 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
var qualityTitle = _qualityDefinitionService.Get(episodeFile.Quality.Quality).Title;
|
||||
|
@ -451,6 +582,18 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
if (episodeFile.MediaInfo == null) return;
|
||||
|
@ -646,6 +789,20 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
var pattern = string.Empty;
|
||||
|
@ -728,6 +885,30 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
//this will remove (1),(2) from the end of multi part episodes.
|
||||
|
@ -769,6 +950,16 @@ namespace NzbDrone.Core.Organizer
|
|||
return episodeFile.SceneName;
|
||||
}
|
||||
|
||||
private string GetOriginalTitle(TrackFile trackFile)
|
||||
{
|
||||
if (trackFile.SceneName.IsNullOrWhiteSpace())
|
||||
{
|
||||
return GetOriginalFileName(trackFile);
|
||||
}
|
||||
|
||||
return trackFile.SceneName;
|
||||
}
|
||||
|
||||
private string GetOriginalFileName(EpisodeFile episodeFile)
|
||||
{
|
||||
if (episodeFile.RelativePath.IsNullOrWhiteSpace())
|
||||
|
@ -779,35 +970,16 @@ namespace NzbDrone.Core.Organizer
|
|||
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
|
||||
}
|
||||
|
||||
//public string GetArtistFolder(Artist artist, NamingConfig namingConfig = null)
|
||||
//{
|
||||
// if (namingConfig == null)
|
||||
// {
|
||||
// namingConfig = _namingConfigService.GetConfig();
|
||||
// }
|
||||
private string GetOriginalFileName(TrackFile trackFile)
|
||||
{
|
||||
if (trackFile.RelativePath.IsNullOrWhiteSpace())
|
||||
{
|
||||
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
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
|
||||
namespace NzbDrone.Core.Organizer
|
||||
|
@ -9,26 +10,34 @@ namespace NzbDrone.Core.Organizer
|
|||
public interface IFilenameSampleService
|
||||
{
|
||||
SampleResult GetStandardSample(NamingConfig nameSpec);
|
||||
SampleResult GetStandardTrackSample(NamingConfig nameSpec);
|
||||
SampleResult GetMultiEpisodeSample(NamingConfig nameSpec);
|
||||
SampleResult GetDailySample(NamingConfig nameSpec);
|
||||
SampleResult GetAnimeSample(NamingConfig nameSpec);
|
||||
SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec);
|
||||
string GetSeriesFolderSample(NamingConfig nameSpec);
|
||||
string GetSeasonFolderSample(NamingConfig nameSpec);
|
||||
string GetArtistFolderSample(NamingConfig nameSpec);
|
||||
string GetAlbumFolderSample(NamingConfig nameSpec);
|
||||
}
|
||||
|
||||
public class FileNameSampleService : IFilenameSampleService
|
||||
{
|
||||
private readonly IBuildFileNames _buildFileNames;
|
||||
private static Series _standardSeries;
|
||||
private static Artist _standardArtist;
|
||||
private static Album _standardAlbum;
|
||||
private static Track _track1;
|
||||
private static Series _dailySeries;
|
||||
private static Series _animeSeries;
|
||||
private static Episode _episode1;
|
||||
private static Episode _episode2;
|
||||
private static Episode _episode3;
|
||||
private static List<Episode> _singleEpisode;
|
||||
private static List<Track> _singleTrack;
|
||||
private static List<Episode> _multiEpisodes;
|
||||
private static EpisodeFile _singleEpisodeFile;
|
||||
private static TrackFile _singleTrackFile;
|
||||
private static EpisodeFile _multiEpisodeFile;
|
||||
private static EpisodeFile _dailyEpisodeFile;
|
||||
private static EpisodeFile _animeEpisodeFile;
|
||||
|
@ -44,6 +53,17 @@ namespace NzbDrone.Core.Organizer
|
|||
Title = "Series Title (2010)"
|
||||
};
|
||||
|
||||
_standardArtist = new Artist
|
||||
{
|
||||
Name = "Artist Name"
|
||||
};
|
||||
|
||||
_standardAlbum = new Album
|
||||
{
|
||||
Title = "Album Title",
|
||||
ReleaseDate = System.DateTime.Today
|
||||
};
|
||||
|
||||
_dailySeries = new Series
|
||||
{
|
||||
SeriesType = SeriesTypes.Daily,
|
||||
|
@ -56,6 +76,14 @@ namespace NzbDrone.Core.Organizer
|
|||
Title = "Series Title (2010)"
|
||||
};
|
||||
|
||||
_track1 = new Track
|
||||
{
|
||||
TrackNumber = 3,
|
||||
|
||||
Title = "Track Title (1)",
|
||||
|
||||
};
|
||||
|
||||
_episode1 = new Episode
|
||||
{
|
||||
SeasonNumber = 1,
|
||||
|
@ -82,6 +110,7 @@ namespace NzbDrone.Core.Organizer
|
|||
};
|
||||
|
||||
_singleEpisode = new List<Episode> { _episode1 };
|
||||
_singleTrack = new List<Track> { _track1 };
|
||||
_multiEpisodes = new List<Episode> { _episode1, _episode2, _episode3 };
|
||||
|
||||
var mediaInfo = new MediaInfoModel()
|
||||
|
@ -115,6 +144,15 @@ namespace NzbDrone.Core.Organizer
|
|||
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
|
||||
{
|
||||
Quality = new QualityModel(Quality.MP3256, new Revision(2)),
|
||||
|
@ -165,6 +203,20 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
var result = new SampleResult
|
||||
|
@ -227,6 +279,16 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
try
|
||||
|
@ -238,5 +300,17 @@ namespace NzbDrone.Core.Organizer
|
|||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildTrackSample(List<Track> tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig nameSpec)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _buildFileNames.BuildTrackFileName(tracks, artist, album, trackFile, nameSpec);
|
||||
}
|
||||
catch (NamingFormatException)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,12 @@ namespace NzbDrone.Core.Organizer
|
|||
return ruleBuilder.SetValidator(new ValidStandardEpisodeFormatValidator());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||
|
@ -41,6 +47,17 @@ namespace NzbDrone.Core.Organizer
|
|||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||
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
|
||||
|
@ -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 ValidDailyEpisodeFormatValidator()
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace NzbDrone.Core.Organizer
|
|||
public interface IFilenameValidationService
|
||||
{
|
||||
ValidationFailure ValidateStandardFilename(SampleResult sampleResult);
|
||||
ValidationFailure ValidateTrackFilename(SampleResult sampleResult);
|
||||
ValidationFailure ValidateDailyFilename(SampleResult sampleResult);
|
||||
ValidationFailure ValidateAnimeFilename(SampleResult sampleResult);
|
||||
}
|
||||
|
@ -35,6 +36,27 @@ namespace NzbDrone.Core.Organizer
|
|||
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)
|
||||
{
|
||||
var validationFailure = new ValidationFailure("DailyEpisodeFormat", ERROR_MESSAGE);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Datastore;
|
||||
|
||||
namespace NzbDrone.Core.Organizer
|
||||
{
|
||||
|
@ -7,21 +7,25 @@ namespace NzbDrone.Core.Organizer
|
|||
public static NamingConfig Default => new NamingConfig
|
||||
{
|
||||
RenameEpisodes = false,
|
||||
RenameTracks = false,
|
||||
ReplaceIllegalCharacters = true,
|
||||
MultiEpisodeStyle = 0,
|
||||
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}",
|
||||
AnimeEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
|
||||
SeriesFolderFormat = "{Series Title}",
|
||||
SeasonFolderFormat = "Season {season}",
|
||||
ArtistFolderFormat = "{Artist Name}",
|
||||
AlbumFolderFormat = "{Album Name} ({Year})"
|
||||
AlbumFolderFormat = "{Album Title} ({Release Year})"
|
||||
};
|
||||
|
||||
public bool RenameEpisodes { get; set; }
|
||||
public bool RenameTracks { get; set; }
|
||||
public bool ReplaceIllegalCharacters { get; set; }
|
||||
public int MultiEpisodeStyle { get; set; }
|
||||
public string StandardEpisodeFormat { get; set; }
|
||||
public string StandardTrackFormat { get; set; }
|
||||
public string DailyEpisodeFormat { get; set; }
|
||||
public string AnimeEpisodeFormat { get; set; }
|
||||
public string SeriesFolderFormat { get; set; }
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.Organizer
|
||||
{
|
||||
|
@ -8,7 +9,11 @@ namespace NzbDrone.Core.Organizer
|
|||
{
|
||||
public string FileName { get; set; }
|
||||
public Series Series { get; set; }
|
||||
public Artist Artist { get; set; }
|
||||
public Album Album { get; set; }
|
||||
public List<Episode> Episodes { get; set; }
|
||||
public EpisodeFile EpisodeFile { get; set; }
|
||||
public List<Track> Tracks { get; set; }
|
||||
public TrackFile TrackFile { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
14
src/NzbDrone.Core/Parser/Model/ArtistTitleInfo.cs
Normal file
14
src/NzbDrone.Core/Parser/Model/ArtistTitleInfo.cs
Normal 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; }
|
||||
}
|
||||
}
|
|
@ -19,20 +19,12 @@ namespace NzbDrone.Core.Parser.Model
|
|||
public long Size { get; set; }
|
||||
public ParsedTrackInfo ParsedTrackInfo { get; set; }
|
||||
public Artist Artist { get; set; }
|
||||
public Album Album { get; set; }
|
||||
public List<Track> Tracks { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public MediaInfoModel MediaInfo { 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()
|
||||
{
|
||||
|
|
|
@ -9,88 +9,35 @@ namespace NzbDrone.Core.Parser.Model
|
|||
{
|
||||
public class ParsedTrackInfo
|
||||
{
|
||||
// [TODO]: Properly fill this out
|
||||
public string ArtistTitle { get; set; }
|
||||
public string AlbumTitle { get; set; }
|
||||
public SeriesTitleInfo SeriesTitleInfo { get; set; }
|
||||
public ArtistTitleInfo ArtistTitleInfo { get; set; }
|
||||
public QualityModel Quality { get; set; }
|
||||
public int SeasonNumber { get; set; }
|
||||
public int[] EpisodeNumbers { get; set; }
|
||||
public int[] AbsoluteEpisodeNumbers { get; set; }
|
||||
public string AirDate { get; set; }
|
||||
public Language Language { get; set; }
|
||||
public bool FullSeason { get; set; }
|
||||
public bool Special { get; set; }
|
||||
public string AlbumId { get; set; } // maybe
|
||||
public int[] TrackNumbers { get; set; }
|
||||
//public Language Language { get; set; }
|
||||
public string ReleaseGroup { get; set; }
|
||||
public string ReleaseHash { get; set; }
|
||||
|
||||
public ParsedTrackInfo()
|
||||
{
|
||||
EpisodeNumbers = new int[0];
|
||||
AbsoluteEpisodeNumbers = new int[0];
|
||||
TrackNumbers = 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()
|
||||
{
|
||||
string episodeString = "[Unknown Episode]";
|
||||
string episodeString = "[Unknown Track]";
|
||||
|
||||
if (IsDaily && EpisodeNumbers.Empty())
|
||||
|
||||
if (TrackNumbers != null && TrackNumbers.Any())
|
||||
{
|
||||
episodeString = string.Format("{0}", AirDate);
|
||||
}
|
||||
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"))));
|
||||
episodeString = string.Format("T{1}", string.Join("-", TrackNumbers.Select(c => c.ToString("00"))));
|
||||
}
|
||||
|
||||
|
||||
return string.Format("{0} - {1} {2}", ArtistTitle, episodeString, Quality);
|
||||
}
|
||||
}
|
||||
|
|
26
src/NzbDrone.Core/Parser/Model/RemoteAlbum.cs
Normal file
26
src/NzbDrone.Core/Parser/Model/RemoteAlbum.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Music;
|
||||
|
||||
namespace NzbDrone.Core.Parser.Model
|
||||
{
|
||||
public class RemoteAlbum
|
||||
{
|
||||
public ReleaseInfo Release { get; set; }
|
||||
public ParsedTrackInfo ParsedTrackInfo { get; set; }
|
||||
public Artist Artist { get; set; }
|
||||
public List<Album> Albums { get; set; }
|
||||
public bool DownloadAllowed { get; set; }
|
||||
|
||||
public bool IsRecentAlbum()
|
||||
{
|
||||
return Albums.Any(e => e.ReleaseDate >= DateTime.UtcNow.Date.AddDays(-14));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Release.Title;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue