diff --git a/src/NzbDrone.Api/Music/ArtistResource.cs b/src/NzbDrone.Api/Music/ArtistResource.cs index d75fbebdb..4405c0d38 100644 --- a/src/NzbDrone.Api/Music/ArtistResource.cs +++ b/src/NzbDrone.Api/Music/ArtistResource.cs @@ -20,6 +20,10 @@ namespace NzbDrone.Api.Music //View Only 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,7 +57,7 @@ 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 Genres { get; set; } public HashSet Tags { get; set; } public DateTime Added { get; set; } @@ -71,7 +75,10 @@ namespace NzbDrone.Api.Music return new ArtistResource { Id = model.Id, - + MBId = model.MBId, + TADBId = model.TADBId, + DiscogsId = model.DiscogsId, + AMId = model.AMId, Name = model.Name, //AlternateTitles //SortTitle = resource.SortTitle, @@ -127,7 +134,10 @@ namespace NzbDrone.Api.Music Name = resource.Name, //AlternateTitles //SortTitle = resource.SortTitle, - + MBId = resource.MBId, + TADBId = resource.TADBId, + DiscogsId = resource.DiscogsId, + AMId = resource.AMId, //TotalEpisodeCount //EpisodeCount //EpisodeFileCount diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 80572e068..877563c2a 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -104,6 +104,13 @@ + + + + + + + diff --git a/src/NzbDrone.Api/TrackFiles/TrackFileModule.cs b/src/NzbDrone.Api/TrackFiles/TrackFileModule.cs new file mode 100644 index 000000000..1f679ac5d --- /dev/null +++ b/src/NzbDrone.Api/TrackFiles/TrackFileModule.cs @@ -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, + IHandle + { + 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 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); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/TrackFiles/TrackFileResource.cs b/src/NzbDrone.Api/TrackFiles/TrackFileResource.cs new file mode 100644 index 000000000..4c75dc429 --- /dev/null +++ b/src/NzbDrone.Api/TrackFiles/TrackFileResource.cs @@ -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) + }; + } + } +} diff --git a/src/NzbDrone.Api/Tracks/RenameTrackModule.cs b/src/NzbDrone.Api/Tracks/RenameTrackModule.cs new file mode 100644 index 000000000..9467ec43e --- /dev/null +++ b/src/NzbDrone.Api/Tracks/RenameTrackModule.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using NzbDrone.Api.REST; +using NzbDrone.Core.MediaFiles; + +namespace NzbDrone.Api.Tracks +{ + public class RenameTrackModule : NzbDroneRestModule + { + private readonly IRenameTrackFileService _renameTrackFileService; + + public RenameTrackModule(IRenameTrackFileService renameTrackFileService) + : base("rename") + { + _renameTrackFileService = renameTrackFileService; + + GetResourceAll = GetTracks; + } + + private List 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(); + } + } +} diff --git a/src/NzbDrone.Api/Tracks/RenameTrackResource.cs b/src/NzbDrone.Api/Tracks/RenameTrackResource.cs new file mode 100644 index 000000000..12f67bb60 --- /dev/null +++ b/src/NzbDrone.Api/Tracks/RenameTrackResource.cs @@ -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 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 ToResource(this IEnumerable models) + { + return models.Select(ToResource).ToList(); + } + } +} diff --git a/src/NzbDrone.Api/Tracks/TrackModule.cs b/src/NzbDrone.Api/Tracks/TrackModule.cs new file mode 100644 index 000000000..fa3222c51 --- /dev/null +++ b/src/NzbDrone.Api/Tracks/TrackModule.cs @@ -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 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); + } + } +} diff --git a/src/NzbDrone.Api/Tracks/TrackModuleWithSignalR.cs b/src/NzbDrone.Api/Tracks/TrackModuleWithSignalR.cs new file mode 100644 index 000000000..f5926768e --- /dev/null +++ b/src/NzbDrone.Api/Tracks/TrackModuleWithSignalR.cs @@ -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, + IHandle + { + 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 MapToResource(List tracks, bool includeArtist, bool includeTrackFile) + { + var result = tracks.ToResource(); + + if (includeArtist || includeTrackFile) + { + var artistDict = new Dictionary(); + 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); + } + } + } +} diff --git a/src/NzbDrone.Api/Tracks/TrackResource.cs b/src/NzbDrone.Api/Tracks/TrackResource.cs new file mode 100644 index 000000000..f12a9948c --- /dev/null +++ b/src/NzbDrone.Api/Tracks/TrackResource.cs @@ -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 ToResource(this IEnumerable models) + { + if (models == null) return null; + + return models.Select(ToResource).ToList(); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/Commands/RenameArtistCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/RenameArtistCommand.cs new file mode 100644 index 000000000..af86d061b --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/Commands/RenameArtistCommand.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.Core.MediaFiles.Commands +{ + public class RenameArtistCommand : Command + { + public List ArtistIds { get; set; } + + public override bool SendUpdatesToClient => true; + + public RenameArtistCommand() + { + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs index eb20ae076..ff426b021 100644 --- a/src/NzbDrone.Core/MediaFiles/MediaFileService.cs +++ b/src/NzbDrone.Core/MediaFiles/MediaFileService.cs @@ -19,6 +19,7 @@ namespace NzbDrone.Core.MediaFiles void Update(TrackFile trackFile); void Delete(TrackFile trackFile, DeleteMediaFileReason reason); List GetFilesByArtist(int artistId); + List GetFilesByAlbum(int artistId, int albumId); List GetFilesWithoutMediaInfo(); List FilterExistingFiles(List files, Artist artist); TrackFile Get(int id); @@ -97,5 +98,10 @@ namespace NzbDrone.Core.MediaFiles { return _mediaFileRepository.GetFilesByArtist(artistId); } + + public List GetFilesByAlbum(int artistId, int albumId) + { + return _mediaFileRepository.GetFilesByArtist(artistId); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/RenameTrackFilePreview.cs b/src/NzbDrone.Core/MediaFiles/RenameTrackFilePreview.cs new file mode 100644 index 000000000..25bfe75f7 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/RenameTrackFilePreview.cs @@ -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 TrackNumbers { get; set; } + public int TrackFileId { get; set; } + public string ExistingPath { get; set; } + public string NewPath { get; set; } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/RenameTrackFileService.cs b/src/NzbDrone.Core/MediaFiles/RenameTrackFileService.cs new file mode 100644 index 000000000..001774de5 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/RenameTrackFileService.cs @@ -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 GetRenamePreviews(int artistId); + List GetRenamePreviews(int artistId, int albumId); + } + + public class RenameTrackFileService : IRenameTrackFileService, + IExecute, + IExecute + { + 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 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 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 GetPreviews(Artist artist, Album album, List tracks, List 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 trackFiles, Artist artist) + { + // TODO + throw new NotImplementedException(); + //var renamed = new List(); + + //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); + //} + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/TrackFile.cs b/src/NzbDrone.Core/MediaFiles/TrackFile.cs index e20b3b5dc..cade66616 100644 --- a/src/NzbDrone.Core/MediaFiles/TrackFile.cs +++ b/src/NzbDrone.Core/MediaFiles/TrackFile.cs @@ -12,7 +12,6 @@ namespace NzbDrone.Core.MediaFiles { public class TrackFile : ModelBase { - public string SpotifyTrackId { get; set; } public int AlbumId { get; set; } public int ArtistId { get; set; } public string RelativePath { get; set; } diff --git a/src/NzbDrone.Core/Music/Artist.cs b/src/NzbDrone.Core/Music/Artist.cs index cdce64860..0ed7ff34e 100644 --- a/src/NzbDrone.Core/Music/Artist.cs +++ b/src/NzbDrone.Core/Music/Artist.cs @@ -23,6 +23,10 @@ namespace NzbDrone.Core.Music } 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; } @@ -52,6 +56,10 @@ namespace NzbDrone.Core.Music { ForeignArtistId = otherArtist.ForeignArtistId; + MBId = otherArtist.MBId; + TADBId = otherArtist.TADBId; + DiscogsId = otherArtist.DiscogsId; + AMId = otherArtist.AMId; Name = otherArtist.Name; NameSlug = otherArtist.NameSlug; CleanName = otherArtist.CleanName; diff --git a/src/NzbDrone.Core/Music/Track.cs b/src/NzbDrone.Core/Music/Track.cs index 8e281260a..f0f25ae96 100644 --- a/src/NzbDrone.Core/Music/Track.cs +++ b/src/NzbDrone.Core/Music/Track.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Music public string ForeignTrackId { get; set; } public int AlbumId { get; set; } - public LazyLoaded Artist { 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; } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 0818a8ce7..963338f76 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -724,7 +724,10 @@ + + + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index f5d3c3c24..dd0bf3497 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -20,7 +20,9 @@ namespace NzbDrone.Core.Organizer string BuildFileName(List episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null); string BuildTrackFileName(List 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); @@ -202,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; @@ -225,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(); diff --git a/src/UI/AddArtist/SearchResultViewTemplate.hbs b/src/UI/AddArtist/SearchResultViewTemplate.hbs index 3c27e1b97..0a3bbe016 100644 --- a/src/UI/AddArtist/SearchResultViewTemplate.hbs +++ b/src/UI/AddArtist/SearchResultViewTemplate.hbs @@ -16,11 +16,11 @@ -
+
{{#unless existing}} {{#unless path}} diff --git a/src/UI/Artist/Details/InfoViewTemplate.hbs b/src/UI/Artist/Details/InfoViewTemplate.hbs index c6e689630..f305c976b 100644 --- a/src/UI/Artist/Details/InfoViewTemplate.hbs +++ b/src/UI/Artist/Details/InfoViewTemplate.hbs @@ -29,20 +29,18 @@
- Trakt + MusicBrainz - The TVDB - - {{#if imdbId}} - IMDB + {{#if tadbId}} + The AudioDB {{/if}} - {{#if tvRageId}} - TV Rage + {{#if discogsId}} + Discogs {{/if}} - {{#if tvMazeId}} - TV Maze + {{#if amId}} + AllMusic {{/if}}
diff --git a/src/UI/Artist/Index/ArtistIndexLayout.js b/src/UI/Artist/Index/ArtistIndexLayout.js index b06bd5311..81884d48c 100644 --- a/src/UI/Artist/Index/ArtistIndexLayout.js +++ b/src/UI/Artist/Index/ArtistIndexLayout.js @@ -321,15 +321,17 @@ module.exports = Marionette.Layout.extend({ _showFooter : function() { var footerModel = new FooterModel(); var artist = this.artistCollection.models.length; - var episodes = 0; - var episodeFiles = 0; + var albums = 0; + var tracks = 0; + var trackFiles = 0; var ended = 0; var continuing = 0; var monitored = 0; _.each(this.artistCollection.models, function(model) { - episodes += model.get('episodeCount'); // TODO: Refactor to Seasons and Tracks - episodeFiles += model.get('episodeFileCount'); + albums += model.get('albumCount'); + tracks += model.get('episodeCount'); // TODO: Refactor to Seasons and Tracks + trackFiles += model.get('episodeFileCount'); /*if (model.get('status').toLowerCase() === 'ended') { ended++; @@ -348,8 +350,9 @@ module.exports = Marionette.Layout.extend({ continuing : continuing, monitored : monitored, unmonitored : artist - monitored, - episodes : episodes, - episodeFiles : episodeFiles + albums : albums, + tracks : tracks, + trackFiles : trackFiles }); this.footer.show(new FooterView({ model : footerModel })); diff --git a/src/UI/Artist/Index/FooterViewTemplate.hbs b/src/UI/Artist/Index/FooterViewTemplate.hbs index 58d744386..f61baa356 100644 --- a/src/UI/Artist/Index/FooterViewTemplate.hbs +++ b/src/UI/Artist/Index/FooterViewTemplate.hbs @@ -34,11 +34,14 @@
+
Albums
+
{{albums}}
+
Tracks
-
{{episodes}}
+
{{tracks}}
Files
-
{{episodeFiles}}
+
{{trackFiles}}
diff --git a/src/UI/Artist/Index/TrackProgressPartial.hbs b/src/UI/Artist/Index/TrackProgressPartial.hbs index db5c49a2b..a9cec28f7 100644 --- a/src/UI/Artist/Index/TrackProgressPartial.hbs +++ b/src/UI/Artist/Index/TrackProgressPartial.hbs @@ -1,4 +1,4 @@ -
- {{episodeFileCount}} / {{episodeCount}} -
{{episodeFileCount}} / {{episodeCount}}
+
+ {{trackFileCount}} / {{trackCount}} +
{{trackFileCount}} / {{trackCount}}
\ No newline at end of file diff --git a/src/UI/Artist/TrackCollection.js b/src/UI/Artist/TrackCollection.js index 33bd4c3a3..302c416ea 100644 --- a/src/UI/Artist/TrackCollection.js +++ b/src/UI/Artist/TrackCollection.js @@ -4,11 +4,11 @@ var TrackModel = require('./TrackModel'); require('./TrackCollection'); module.exports = PageableCollection.extend({ - url : window.NzbDrone.ApiRoot + '/episode', + url : window.NzbDrone.ApiRoot + '/track', model : TrackModel, state : { - sortKey : 'episodeNumber', + sortKey : 'trackNumber', order : 1, pageSize : 100000 }, @@ -18,28 +18,28 @@ module.exports = PageableCollection.extend({ originalFetch : Backbone.Collection.prototype.fetch, initialize : function(options) { - this.seriesId = options.seriesId; + this.artistId = options.artistId; }, - bySeason : function(season) { - var filtered = this.filter(function(episode) { - return episode.get('seasonNumber') === season; + bySeason : function(album) { + var filtered = this.filter(function(track) { + return track.get('albumId') === album; }); - var EpisodeCollection = require('./TrackCollection'); + var TrackCollection = require('./TrackCollection'); - return new EpisodeCollection(filtered); + return new TrackCollection(filtered); }, comparator : function(model1, model2) { - var episode1 = model1.get('episodeNumber'); - var episode2 = model2.get('episodeNumber'); + var track1 = model1.get('trackNumber'); + var track2 = model2.get('trackNumber'); - if (episode1 < episode2) { + if (track1 < track2) { return 1; } - if (episode1 > episode2) { + if (track1 > track2) { return -1; } @@ -47,15 +47,15 @@ module.exports = PageableCollection.extend({ }, fetch : function(options) { - if (!this.seriesId) { - throw 'seriesId is required'; + if (!this.artistId) { + throw 'artistId is required'; } if (!options) { options = {}; } - options.data = { seriesId : this.seriesId }; + options.data = { artistId : this.artistId }; return this.originalFetch.call(this, options); } diff --git a/src/UI/Artist/TrackFileCollection.js b/src/UI/Artist/TrackFileCollection.js index 19c58ebee..9b9909ce6 100644 --- a/src/UI/Artist/TrackFileCollection.js +++ b/src/UI/Artist/TrackFileCollection.js @@ -2,7 +2,7 @@ var Backbone = require('backbone'); var TrackFileModel = require('./TrackFileModel'); module.exports = Backbone.Collection.extend({ - url : window.NzbDrone.ApiRoot + '/episodefile', + url : window.NzbDrone.ApiRoot + '/trackfile', model : TrackFileModel, originalFetch : Backbone.Collection.prototype.fetch, @@ -21,7 +21,7 @@ module.exports = Backbone.Collection.extend({ options = {}; } - options.data = { seriesId : this.seriesId }; + options.data = { artistId : this.artistId }; return this.originalFetch.call(this, options); } diff --git a/src/UI/Artist/TrackModel.js b/src/UI/Artist/TrackModel.js index ebb72cf29..30a2702d7 100644 --- a/src/UI/Artist/TrackModel.js +++ b/src/UI/Artist/TrackModel.js @@ -2,12 +2,12 @@ var Backbone = require('backbone'); module.exports = Backbone.Model.extend({ defaults : { - seasonNumber : 0, + albumId : 0, status : 0 }, methodUrls : { - 'update' : window.NzbDrone.ApiRoot + '/episode' + 'update' : window.NzbDrone.ApiRoot + '/track' }, sync : function(method, model, options) { diff --git a/src/UI/Controller.js b/src/UI/Controller.js index 5125bbc30..04dcce602 100644 --- a/src/UI/Controller.js +++ b/src/UI/Controller.js @@ -19,11 +19,6 @@ module.exports = NzbDroneController.extend({ this.showMainRegion(new AddArtistLayout({ action : action })); }, - artistDetails: function(query) { - this.setTitle('Artist Detail'); - this.showMainRegion(new SeriesDetailsLayout()); - }, - calendar : function() { this.setTitle('Calendar'); this.showMainRegion(new CalendarLayout()); diff --git a/src/UI/Handlebars/Helpers/Artist.js b/src/UI/Handlebars/Helpers/Artist.js index 13990530c..36c8983ba 100644 --- a/src/UI/Handlebars/Helpers/Artist.js +++ b/src/UI/Handlebars/Helpers/Artist.js @@ -19,24 +19,20 @@ Handlebars.registerHelper('poster', function() { return new Handlebars.SafeString(''.format(placeholder)); }); -Handlebars.registerHelper('traktUrl', function() { - return 'http://trakt.tv/search/tvdb/' + this.tvdbId + '?id_type=show'; +Handlebars.registerHelper('MBUrl', function() { + return 'https://musicbrainz.org/artist/' + this.mbId; }); -Handlebars.registerHelper('imdbUrl', function() { - return 'http://imdb.com/title/' + this.imdbId; +Handlebars.registerHelper('TADBUrl', function() { + return 'http://www.theaudiodb.com/artist/' + this.tadbId; }); -Handlebars.registerHelper('tvdbUrl', function() { - return 'http://www.thetvdb.com/?tab=series&id=' + this.tvdbId; +Handlebars.registerHelper('discogsUrl', function() { + return 'https://www.discogs.com/artist/' + this.discogsId; }); -Handlebars.registerHelper('tvRageUrl', function() { - return 'http://www.tvrage.com/shows/id-' + this.tvRageId; -}); - -Handlebars.registerHelper('tvMazeUrl', function() { - return 'http://www.tvmaze.com/shows/' + this.tvMazeId + '/_'; +Handlebars.registerHelper('allMusicUrl', function() { + return 'http://www.allmusic.com/artist/' + this.amId; }); Handlebars.registerHelper('route', function() { @@ -56,6 +52,19 @@ Handlebars.registerHelper('percentOfEpisodes', function() { return percent; }); +Handlebars.registerHelper('percentOfTracks', function() { + var trackCount = this.trackCount; + var trackFileCount = this.trackFileCount; + + var percent = 100; + + if (trackCount > 0) { + percent = trackFileCount / trackCount * 100; + } + + return percent; +}); + Handlebars.registerHelper('seasonCountHelper', function() { var seasonCount = this.seasonCount; var continuing = this.status === 'continuing'; @@ -87,7 +96,7 @@ Handlebars.registerHelper('albumCountHelper', function() { var albumCount = this.albumCount; if (albumCount === 1) { - return new Handlebars.SafeString('{0} Albums'.format(albumCount)); + return new Handlebars.SafeString('{0} Album'.format(albumCount)); } return new Handlebars.SafeString('{0} Albums'.format(albumCount)); diff --git a/src/UI/Router.js b/src/UI/Router.js index ccc4b964d..ce8db831c 100644 --- a/src/UI/Router.js +++ b/src/UI/Router.js @@ -18,7 +18,6 @@ module.exports = Marionette.AppRouter.extend({ 'rss' : 'rss', 'system' : 'system', 'system/:action' : 'system', - 'artist/:query' : 'artistDetails', 'seasonpass' : 'seasonPass', 'artisteditor' : 'artistEditor', ':whatever' : 'showNotFound'