Add Track and TrackFile API Resources

Add Track and TrackFile API Resources, Add Rename Track Resource, Add GetFilesByAlbum function to Media File Service, Add Links to Artist Detail Page, Misc other UI work
pull/6/head
Qstick 8 years ago
parent fafe4e93f3
commit 916db8d356

@ -20,6 +20,10 @@ namespace NzbDrone.Api.Music
//View Only //View Only
public string Name { get; set; } public string Name { get; set; }
public string ForeignArtistId { get; set; } public string ForeignArtistId { get; set; }
public string MBId { get; set; }
public int TADBId { get; set; }
public int DiscogsId { get; set; }
public string AMId { get; set; }
public string Overview { get; set; } public string Overview { get; set; }
public int AlbumCount public int AlbumCount
@ -53,7 +57,7 @@ namespace NzbDrone.Api.Music
public bool Monitored { get; set; } public bool Monitored { get; set; }
public string RootFolderPath { get; set; } public string RootFolderPath { get; set; }
public string Certification { get; set; } //public string Certification { get; set; }
public List<string> Genres { get; set; } public List<string> Genres { get; set; }
public HashSet<int> Tags { get; set; } public HashSet<int> Tags { get; set; }
public DateTime Added { get; set; } public DateTime Added { get; set; }
@ -71,7 +75,10 @@ namespace NzbDrone.Api.Music
return new ArtistResource return new ArtistResource
{ {
Id = model.Id, Id = model.Id,
MBId = model.MBId,
TADBId = model.TADBId,
DiscogsId = model.DiscogsId,
AMId = model.AMId,
Name = model.Name, Name = model.Name,
//AlternateTitles //AlternateTitles
//SortTitle = resource.SortTitle, //SortTitle = resource.SortTitle,
@ -127,7 +134,10 @@ namespace NzbDrone.Api.Music
Name = resource.Name, Name = resource.Name,
//AlternateTitles //AlternateTitles
//SortTitle = resource.SortTitle, //SortTitle = resource.SortTitle,
MBId = resource.MBId,
TADBId = resource.TADBId,
DiscogsId = resource.DiscogsId,
AMId = resource.AMId,
//TotalEpisodeCount //TotalEpisodeCount
//EpisodeCount //EpisodeCount
//EpisodeFileCount //EpisodeFileCount

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

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

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

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

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

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

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

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

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

@ -19,6 +19,7 @@ namespace NzbDrone.Core.MediaFiles
void Update(TrackFile trackFile); void Update(TrackFile trackFile);
void Delete(TrackFile trackFile, DeleteMediaFileReason reason); void Delete(TrackFile trackFile, DeleteMediaFileReason reason);
List<TrackFile> GetFilesByArtist(int artistId); List<TrackFile> GetFilesByArtist(int artistId);
List<TrackFile> GetFilesByAlbum(int artistId, int albumId);
List<TrackFile> GetFilesWithoutMediaInfo(); List<TrackFile> GetFilesWithoutMediaInfo();
List<string> FilterExistingFiles(List<string> files, Artist artist); List<string> FilterExistingFiles(List<string> files, Artist artist);
TrackFile Get(int id); TrackFile Get(int id);
@ -97,5 +98,10 @@ namespace NzbDrone.Core.MediaFiles
{ {
return _mediaFileRepository.GetFilesByArtist(artistId); return _mediaFileRepository.GetFilesByArtist(artistId);
} }
public List<TrackFile> GetFilesByAlbum(int artistId, int albumId)
{
return _mediaFileRepository.GetFilesByArtist(artistId);
}
} }
} }

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

@ -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,7 +12,6 @@ namespace NzbDrone.Core.MediaFiles
{ {
public class TrackFile : ModelBase public class TrackFile : ModelBase
{ {
public string SpotifyTrackId { get; set; }
public int AlbumId { get; set; } public int AlbumId { get; set; }
public int ArtistId { get; set; } public int ArtistId { get; set; }
public string RelativePath { get; set; } public string RelativePath { get; set; }

@ -23,6 +23,10 @@ namespace NzbDrone.Core.Music
} }
public string ForeignArtistId { 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 Name { get; set; }
public string NameSlug { get; set; } public string NameSlug { get; set; }
public string CleanName { get; set; } public string CleanName { get; set; }
@ -52,6 +56,10 @@ namespace NzbDrone.Core.Music
{ {
ForeignArtistId = otherArtist.ForeignArtistId; ForeignArtistId = otherArtist.ForeignArtistId;
MBId = otherArtist.MBId;
TADBId = otherArtist.TADBId;
DiscogsId = otherArtist.DiscogsId;
AMId = otherArtist.AMId;
Name = otherArtist.Name; Name = otherArtist.Name;
NameSlug = otherArtist.NameSlug; NameSlug = otherArtist.NameSlug;
CleanName = otherArtist.CleanName; CleanName = otherArtist.CleanName;

@ -19,7 +19,7 @@ namespace NzbDrone.Core.Music
public string ForeignTrackId { get; set; } public string ForeignTrackId { get; set; }
public int AlbumId { get; set; } public int AlbumId { get; set; }
public LazyLoaded<Artist> 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 ArtistId { get; set; } // This is the DB Id of the Artist, not the SpotifyId
//public int CompilationId { get; set; } //public int CompilationId { get; set; }

@ -724,7 +724,10 @@
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" /> <Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" /> <Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" /> <Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameArtistCommand.cs" />
<Compile Include="MediaFiles\Events\TrackDownloadedEvent.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\TrackFileMovingService.cs" />
<Compile Include="MediaFiles\TrackFileMoveResult.cs" /> <Compile Include="MediaFiles\TrackFileMoveResult.cs" />
<Compile Include="MediaFiles\TrackImport\ImportMode.cs" /> <Compile Include="MediaFiles\TrackImport\ImportMode.cs" />

@ -20,7 +20,9 @@ namespace NzbDrone.Core.Organizer
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null); string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
string BuildTrackFileName(List<Track> tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null); string BuildTrackFileName(List<Track> tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null);
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension); string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
string BuildTrackFilePath(Artist artist, Album album, string fileName, string extension);
string BuildSeasonPath(Series series, int seasonNumber); string BuildSeasonPath(Series series, int seasonNumber);
string BuildAlbumPath(Artist artist, Album album);
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
string GetSeriesFolder(Series series, NamingConfig namingConfig = null); string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
string GetArtistFolder(Artist artist, NamingConfig namingConfig = null); string GetArtistFolder(Artist artist, NamingConfig namingConfig = null);
@ -202,6 +204,15 @@ namespace NzbDrone.Core.Organizer
return Path.Combine(path, fileName + extension); return Path.Combine(path, fileName + extension);
} }
public string BuildTrackFilePath(Artist artist, Album album, string fileName, string extension)
{
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
var path = BuildAlbumPath(artist, album);
return Path.Combine(path, fileName + extension);
}
public string BuildSeasonPath(Series series, int seasonNumber) public string BuildSeasonPath(Series series, int seasonNumber)
{ {
var path = series.Path; var path = series.Path;
@ -225,6 +236,24 @@ namespace NzbDrone.Core.Organizer
return path; return path;
} }
public string BuildAlbumPath(Artist artist, Album album)
{
var path = artist.Path;
if (artist.AlbumFolder)
{
var albumFolder = GetAlbumFolder(artist, album);
albumFolder = CleanFileName(albumFolder);
path = Path.Combine(path, albumFolder);
}
return path;
}
public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec)
{ {
var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault(); var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault();

@ -16,11 +16,11 @@
</h2> </h2>
</div> </div>
</div> </div>
<div class="row new-artist-overview x-overview"> <!--<div class="row new-artist-overview x-overview">
<div class="col-md-12 overview-internal"> <div class="col-md-12 overview-internal">
{{overview}} {{overview}}
</div> </div>
</div> </div>-->
<div class="row"> <div class="row">
{{#unless existing}} {{#unless existing}}
{{#unless path}} {{#unless path}}

@ -29,20 +29,18 @@
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<span class="artist-info-links"> <span class="artist-info-links">
<a href="{{traktUrl}}" class="label label-info">Trakt</a> <a href="{{MBUrl}}" class="label label-info">MusicBrainz</a>
<a href="{{tvdbUrl}}" class="label label-info">The TVDB</a> {{#if tadbId}}
<a href="{{TADBUrl}}" class="label label-info">The AudioDB</a>
{{#if imdbId}}
<a href="{{imdbUrl}}" class="label label-info">IMDB</a>
{{/if}} {{/if}}
{{#if tvRageId}} {{#if discogsId}}
<a href="{{tvRageUrl}}" class="label label-info">TV Rage</a> <a href="{{discogsUrl}}" class="label label-info">Discogs</a>
{{/if}} {{/if}}
{{#if tvMazeId}} {{#if amId}}
<a href="{{tvMazeUrl}}" class="label label-info">TV Maze</a> <a href="{{allMusicUrl}}" class="label label-info">AllMusic</a>
{{/if}} {{/if}}
</span> </span>
</div> </div>

@ -321,15 +321,17 @@ module.exports = Marionette.Layout.extend({
_showFooter : function() { _showFooter : function() {
var footerModel = new FooterModel(); var footerModel = new FooterModel();
var artist = this.artistCollection.models.length; var artist = this.artistCollection.models.length;
var episodes = 0; var albums = 0;
var episodeFiles = 0; var tracks = 0;
var trackFiles = 0;
var ended = 0; var ended = 0;
var continuing = 0; var continuing = 0;
var monitored = 0; var monitored = 0;
_.each(this.artistCollection.models, function(model) { _.each(this.artistCollection.models, function(model) {
episodes += model.get('episodeCount'); // TODO: Refactor to Seasons and Tracks albums += model.get('albumCount');
episodeFiles += model.get('episodeFileCount'); tracks += model.get('episodeCount'); // TODO: Refactor to Seasons and Tracks
trackFiles += model.get('episodeFileCount');
/*if (model.get('status').toLowerCase() === 'ended') { /*if (model.get('status').toLowerCase() === 'ended') {
ended++; ended++;
@ -348,8 +350,9 @@ module.exports = Marionette.Layout.extend({
continuing : continuing, continuing : continuing,
monitored : monitored, monitored : monitored,
unmonitored : artist - monitored, unmonitored : artist - monitored,
episodes : episodes, albums : albums,
episodeFiles : episodeFiles tracks : tracks,
trackFiles : trackFiles
}); });
this.footer.show(new FooterView({ model : footerModel })); this.footer.show(new FooterView({ model : footerModel }));

@ -34,11 +34,14 @@
<div class="artist-stats col-sm-4"> <div class="artist-stats col-sm-4">
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>Albums</dt>
<dd>{{albums}}</dd>
<dt>Tracks</dt> <dt>Tracks</dt>
<dd>{{episodes}}</dd> <dd>{{tracks}}</dd>
<dt>Files</dt> <dt>Files</dt>
<dd>{{episodeFiles}}</dd> <dd>{{trackFiles}}</dd>
</dl> </dl>
</div> </div>
</div> </div>

@ -1,4 +1,4 @@
<div class="progress episode-progress"> <div class="progress track-progress">
<span class="progressbar-back-text">{{episodeFileCount}} / {{episodeCount}}</span> <span class="progressbar-back-text">{{trackFileCount}} / {{trackCount}}</span>
<div class="progress-bar {{EpisodeProgressClass}} episode-progress" style="width:{{percentOfEpisodes}}%"><span class="progressbar-front-text">{{episodeFileCount}} / {{episodeCount}}</span></div> <div class="progress-bar {{TrackProgressClass}} track-progress" style="width:{{percentOfTracks}}%"><span class="progressbar-front-text">{{trackFileCount}} / {{trackCount}}</span></div>
</div> </div>

@ -4,11 +4,11 @@ var TrackModel = require('./TrackModel');
require('./TrackCollection'); require('./TrackCollection');
module.exports = PageableCollection.extend({ module.exports = PageableCollection.extend({
url : window.NzbDrone.ApiRoot + '/episode', url : window.NzbDrone.ApiRoot + '/track',
model : TrackModel, model : TrackModel,
state : { state : {
sortKey : 'episodeNumber', sortKey : 'trackNumber',
order : 1, order : 1,
pageSize : 100000 pageSize : 100000
}, },
@ -18,28 +18,28 @@ module.exports = PageableCollection.extend({
originalFetch : Backbone.Collection.prototype.fetch, originalFetch : Backbone.Collection.prototype.fetch,
initialize : function(options) { initialize : function(options) {
this.seriesId = options.seriesId; this.artistId = options.artistId;
}, },
bySeason : function(season) { bySeason : function(album) {
var filtered = this.filter(function(episode) { var filtered = this.filter(function(track) {
return episode.get('seasonNumber') === season; 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) { comparator : function(model1, model2) {
var episode1 = model1.get('episodeNumber'); var track1 = model1.get('trackNumber');
var episode2 = model2.get('episodeNumber'); var track2 = model2.get('trackNumber');
if (episode1 < episode2) { if (track1 < track2) {
return 1; return 1;
} }
if (episode1 > episode2) { if (track1 > track2) {
return -1; return -1;
} }
@ -47,15 +47,15 @@ module.exports = PageableCollection.extend({
}, },
fetch : function(options) { fetch : function(options) {
if (!this.seriesId) { if (!this.artistId) {
throw 'seriesId is required'; throw 'artistId is required';
} }
if (!options) { if (!options) {
options = {}; options = {};
} }
options.data = { seriesId : this.seriesId }; options.data = { artistId : this.artistId };
return this.originalFetch.call(this, options); return this.originalFetch.call(this, options);
} }

@ -2,7 +2,7 @@ var Backbone = require('backbone');
var TrackFileModel = require('./TrackFileModel'); var TrackFileModel = require('./TrackFileModel');
module.exports = Backbone.Collection.extend({ module.exports = Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/episodefile', url : window.NzbDrone.ApiRoot + '/trackfile',
model : TrackFileModel, model : TrackFileModel,
originalFetch : Backbone.Collection.prototype.fetch, originalFetch : Backbone.Collection.prototype.fetch,
@ -21,7 +21,7 @@ module.exports = Backbone.Collection.extend({
options = {}; options = {};
} }
options.data = { seriesId : this.seriesId }; options.data = { artistId : this.artistId };
return this.originalFetch.call(this, options); return this.originalFetch.call(this, options);
} }

@ -2,12 +2,12 @@ var Backbone = require('backbone');
module.exports = Backbone.Model.extend({ module.exports = Backbone.Model.extend({
defaults : { defaults : {
seasonNumber : 0, albumId : 0,
status : 0 status : 0
}, },
methodUrls : { methodUrls : {
'update' : window.NzbDrone.ApiRoot + '/episode' 'update' : window.NzbDrone.ApiRoot + '/track'
}, },
sync : function(method, model, options) { sync : function(method, model, options) {

@ -19,11 +19,6 @@ module.exports = NzbDroneController.extend({
this.showMainRegion(new AddArtistLayout({ action : action })); this.showMainRegion(new AddArtistLayout({ action : action }));
}, },
artistDetails: function(query) {
this.setTitle('Artist Detail');
this.showMainRegion(new SeriesDetailsLayout());
},
calendar : function() { calendar : function() {
this.setTitle('Calendar'); this.setTitle('Calendar');
this.showMainRegion(new CalendarLayout()); this.showMainRegion(new CalendarLayout());

@ -19,24 +19,20 @@ Handlebars.registerHelper('poster', function() {
return new Handlebars.SafeString('<img class="series-poster placeholder-image" src="{0}">'.format(placeholder)); return new Handlebars.SafeString('<img class="series-poster placeholder-image" src="{0}">'.format(placeholder));
}); });
Handlebars.registerHelper('traktUrl', function() { Handlebars.registerHelper('MBUrl', function() {
return 'http://trakt.tv/search/tvdb/' + this.tvdbId + '?id_type=show'; return 'https://musicbrainz.org/artist/' + this.mbId;
}); });
Handlebars.registerHelper('imdbUrl', function() { Handlebars.registerHelper('TADBUrl', function() {
return 'http://imdb.com/title/' + this.imdbId; return 'http://www.theaudiodb.com/artist/' + this.tadbId;
}); });
Handlebars.registerHelper('tvdbUrl', function() { Handlebars.registerHelper('discogsUrl', function() {
return 'http://www.thetvdb.com/?tab=series&id=' + this.tvdbId; return 'https://www.discogs.com/artist/' + this.discogsId;
}); });
Handlebars.registerHelper('tvRageUrl', function() { Handlebars.registerHelper('allMusicUrl', function() {
return 'http://www.tvrage.com/shows/id-' + this.tvRageId; return 'http://www.allmusic.com/artist/' + this.amId;
});
Handlebars.registerHelper('tvMazeUrl', function() {
return 'http://www.tvmaze.com/shows/' + this.tvMazeId + '/_';
}); });
Handlebars.registerHelper('route', function() { Handlebars.registerHelper('route', function() {
@ -56,6 +52,19 @@ Handlebars.registerHelper('percentOfEpisodes', function() {
return percent; 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() { Handlebars.registerHelper('seasonCountHelper', function() {
var seasonCount = this.seasonCount; var seasonCount = this.seasonCount;
var continuing = this.status === 'continuing'; var continuing = this.status === 'continuing';
@ -87,7 +96,7 @@ Handlebars.registerHelper('albumCountHelper', function() {
var albumCount = this.albumCount; var albumCount = this.albumCount;
if (albumCount === 1) { if (albumCount === 1) {
return new Handlebars.SafeString('<span class="label label-info">{0} Albums</span>'.format(albumCount)); return new Handlebars.SafeString('<span class="label label-info">{0} Album</span>'.format(albumCount));
} }
return new Handlebars.SafeString('<span class="label label-info">{0} Albums</span>'.format(albumCount)); return new Handlebars.SafeString('<span class="label label-info">{0} Albums</span>'.format(albumCount));

@ -18,7 +18,6 @@ module.exports = Marionette.AppRouter.extend({
'rss' : 'rss', 'rss' : 'rss',
'system' : 'system', 'system' : 'system',
'system/:action' : 'system', 'system/:action' : 'system',
'artist/:query' : 'artistDetails',
'seasonpass' : 'seasonPass', 'seasonpass' : 'seasonPass',
'artisteditor' : 'artistEditor', 'artisteditor' : 'artistEditor',
':whatever' : 'showNotFound' ':whatever' : 'showNotFound'

Loading…
Cancel
Save