New: UI Updates, Tag manager, More custom filters (#437)

* New: UI Updates, Tag manager, More custom filters

* fixup! Fix ScanFixture Unit Tests

* Fixed: Sentry Errors from UI don't have release, branch, environment

* Changed: Bump Mobile Detect for New Device Detection

* Fixed: Build on changes to package.json

* fixup! Add MetadataProfile filter option

* fixup! Tag Note, Blacklist, Manual Import

* fixup: Remove connectSection

* fixup: root folder comment
This commit is contained in:
Qstick 2018-08-07 20:57:15 -04:00 committed by GitHub
commit 6581b3a2c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
198 changed files with 3057 additions and 888 deletions

View file

@ -96,7 +96,7 @@ namespace Lidarr.Api.V1.Artist
});
}
return _artistService.UpdateArtists(artistToUpdate)
return _artistService.UpdateArtists(artistToUpdate, !resource.MoveFiles)
.ToResource()
.AsResponse(HttpStatusCode.Accepted);
}

View file

@ -9,6 +9,7 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.ArtistStats;
using NzbDrone.Core.Music;
using NzbDrone.Core.Music.Commands;
@ -19,7 +20,6 @@ using Lidarr.Api.V1.Albums;
using NzbDrone.SignalR;
using Lidarr.Http;
using Lidarr.Http.Extensions;
using Lidarr.Http.Mapping;
namespace Lidarr.Api.V1.Artist
{
@ -39,6 +39,7 @@ namespace Lidarr.Api.V1.Artist
private readonly IArtistStatisticsService _artistStatisticsService;
private readonly IMapCoversToLocal _coverMapper;
private readonly IManageCommandQueue _commandQueueManager;
private readonly IRootFolderService _rootFolderService;
public ArtistModule(IBroadcastSignalRMessage signalRBroadcaster,
IArtistService artistService,
@ -47,6 +48,7 @@ namespace Lidarr.Api.V1.Artist
IArtistStatisticsService artistStatisticsService,
IMapCoversToLocal coverMapper,
IManageCommandQueue commandQueueManager,
IRootFolderService rootFolderService,
RootFolderValidator rootFolderValidator,
ArtistPathValidator artistPathValidator,
ArtistExistsValidator artistExistsValidator,
@ -65,6 +67,7 @@ namespace Lidarr.Api.V1.Artist
_coverMapper = coverMapper;
_commandQueueManager = commandQueueManager;
_rootFolderService = rootFolderService;
GetResourceAll = AllArtists;
GetResourceById = GetArtist;
@ -112,6 +115,7 @@ namespace Lidarr.Api.V1.Artist
FetchAndLinkArtistStatistics(resource);
LinkNextPreviousAlbums(resource);
//PopulateAlternateTitles(resource);
LinkRootFolderPath(resource);
return resource;
}
@ -225,6 +229,11 @@ namespace Lidarr.Api.V1.Artist
// resource.AlternateTitles = mappings.Select(v => new AlternateTitleResource { Title = v.Title, SeasonNumber = v.SeasonNumber, SceneSeasonNumber = v.SceneSeasonNumber }).ToList();
//}
private void LinkRootFolderPath(ArtistResource resource)
{
resource.RootFolderPath = _rootFolderService.GetBestRootFolderPath(resource.Path);
}
public void Handle(TrackImportedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.ImportedTrack.ArtistId);

View file

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Music;
using Lidarr.Api.V1.Albums;
using Lidarr.Http.REST;
namespace Lidarr.Api.V1.Artist
@ -96,7 +96,8 @@ namespace Lidarr.Api.V1.Artist
CleanName = model.CleanName,
ForeignArtistId = model.ForeignArtistId,
RootFolderPath = model.RootFolderPath,
// Root folder path is now calculated from the artist path
// RootFolderPath = model.RootFolderPath,
Genres = model.Genres,
Tags = model.Tags,
Added = model.Added,

View file

@ -30,6 +30,9 @@ namespace Lidarr.Api.V1.Calendar
var includeArtist = Request.GetBooleanQueryParameter("includeArtist");
var includeEpisodeFile = Request.GetBooleanQueryParameter("includeEpisodeFile");
//TODO: Add Album Image support to AlbumModuleWithSignalR
var includeAlbumImages = Request.GetBooleanQueryParameter("includeAlbumImages");
var queryStart = Request.Query.Start;
var queryEnd = Request.Query.End;

View file

@ -16,6 +16,7 @@ namespace Lidarr.Api.V1.ManualImport
{
public string Path { get; set; }
public string RelativePath { get; set; }
public string FolderName { get; set; }
public string Name { get; set; }
public long Size { get; set; }
public ArtistResource Artist { get; set; }
@ -39,6 +40,7 @@ namespace Lidarr.Api.V1.ManualImport
Id = HashConverter.GetHashInt31(model.Path),
Path = model.Path,
RelativePath = model.RelativePath,
FolderName = model.FolderName,
Name = model.Name,
Size = model.Size,
Artist = model.Artist.ToResource(),

View file

@ -4,6 +4,7 @@ using FluentValidation;
using FluentValidation.Results;
using Nancy;
using Newtonsoft.Json;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using Lidarr.Http;
@ -150,7 +151,7 @@ namespace Lidarr.Api.V1
var query = ((IDictionary<string, object>)Request.Query.ToDictionary()).ToDictionary(k => k.Key, k => k.Value.ToString());
var data = _providerFactory.RequestAction(providerDefinition, action, query);
Response resp = JsonConvert.SerializeObject(data);
Response resp = data.ToJson();
resp.ContentType = "application/json";
return resp;
}

View file

@ -57,6 +57,24 @@ namespace Lidarr.Api.V1.Queue
fullQueue.OrderByDescending(q => q.EstimatedCompletionTime, new EstimatedCompletionTimeComparer());
}
else if (pagingSpec.SortKey == "protocol")
{
ordered = ascending ? fullQueue.OrderBy(q => q.Protocol) :
fullQueue.OrderByDescending(q => q.Protocol);
}
else if (pagingSpec.SortKey == "indexer")
{
ordered = ascending ? fullQueue.OrderBy(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase) :
fullQueue.OrderByDescending(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase);
}
else if (pagingSpec.SortKey == "downloadClient")
{
ordered = ascending ? fullQueue.OrderBy(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase) :
fullQueue.OrderByDescending(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase);
}
else
{
ordered = ascending ? fullQueue.OrderBy(orderByFunc) : fullQueue.OrderByDescending(orderByFunc);

View file

@ -48,7 +48,7 @@ namespace Lidarr.Api.V1.System
private Response GetStatus()
{
return new
{
{
Version = BuildInfo.Version.ToString(),
BuildTime = BuildInfo.BuildDateTime,
IsDebug = BuildInfo.IsDebug,
@ -71,7 +71,8 @@ namespace Lidarr.Api.V1.System
MigrationVersion = _database.Migration,
UrlBase = _configFileProvider.UrlBase,
RuntimeVersion = _platformInfo.Version,
RuntimeName = PlatformInfo.Platform
RuntimeName = PlatformInfo.Platform,
StartTime = _runtimeInfo.StartTime
}.AsResponse();
}

View file

@ -1,3 +1,4 @@
using System.Collections.Generic;
using NzbDrone.Core.Tags;
using Lidarr.Http;
@ -8,16 +9,24 @@ namespace Lidarr.Api.V1.Tags
private readonly ITagService _tagService;
public TagDetailsModule(ITagService tagService)
: base("/tag/details")
: base("/tag/detail")
{
_tagService = tagService;
GetResourceById = GetTagDetails;
GetResourceAll = GetAll;
}
private TagDetailsResource GetTagDetails(int id)
{
return _tagService.Details(id).ToResource();
}
private List<TagDetailsResource> GetAll()
{
var tags = _tagService.Details().ToResource();
return tags;
}
}
}

View file

@ -1,9 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Tags;
using Lidarr.Api.V1.Notifications;
using Lidarr.Api.V1.Profiles.Delay;
using Lidarr.Api.V1.Restrictions;
using Lidarr.Http.REST;
namespace Lidarr.Api.V1.Tags
@ -11,16 +8,14 @@ namespace Lidarr.Api.V1.Tags
public class TagDetailsResource : RestResource
{
public string Label { get; set; }
public List<DelayProfileResource> DelayProfiles { get; set; }
public List<NotificationResource> Notifications { get; set; }
public List<RestrictionResource> Restrictions { get; set; }
public List<int> DelayProfileIds { get; set; }
public List<int> NotificationIds { get; set; }
public List<int> RestrictionIds { get; set; }
public List<int> ArtistIds { get; set; }
}
public static class TagDetailsResourceMapper
{
private static readonly NotificationResourceMapper NotificationResourceMapper = new NotificationResourceMapper();
public static TagDetailsResource ToResource(this TagDetails model)
{
if (model == null) return null;
@ -29,10 +24,10 @@ namespace Lidarr.Api.V1.Tags
{
Id = model.Id,
Label = model.Label,
DelayProfiles = model.DelayProfiles.ToResource(),
Notifications = model.Notifications.Select(NotificationResourceMapper.ToResource).ToList(),
Restrictions = model.Restrictions.ToResource(),
ArtistIds = model.Artist.Select(s => s.Id).ToList()
DelayProfileIds = model.DelayProfileIds,
NotificationIds = model.NotificationIds,
RestrictionIds = model.RestrictionIds,
ArtistIds = model.ArtistIds
};
}

View file

@ -57,7 +57,6 @@ namespace Lidarr.Http.ClientSchema
return (T) ReadFromSchema(fields, typeof(T));
}
// Ideally this function should begin a System.Linq.Expression expression tree since it's faster.
// But it's probably not needed till performance issues pop up.
public static FieldMapping[] GetFieldMappings(Type type)

View file

@ -1,10 +1,10 @@
using System.IO;
using System;
using System.IO;
using ICSharpCode.SharpZipLib.Core;
using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip;
using NLog;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common
{
@ -27,7 +27,7 @@ namespace NzbDrone.Common
{
_logger.Debug("Extracting archive [{0}] to [{1}]", compressedFile, destination);
if (OsInfo.IsWindows)
if (compressedFile.EndsWith(".zip", StringComparison.InvariantCultureIgnoreCase))
{
ExtractZip(compressedFile, destination);
}
@ -120,4 +120,4 @@ namespace NzbDrone.Common
}
}
}
}
}

View file

@ -10,6 +10,7 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.TrackImport;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Music;
using NzbDrone.Core.RootFolders;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
@ -39,6 +40,10 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetParentFolder(It.IsAny<string>()))
.Returns((string path) => Directory.GetParent(path).FullName);
Mocker.GetMock<IRootFolderService>()
.Setup(s => s.GetBestRootFolderPath(It.IsAny<string>()))
.Returns(_rootFolder);
}
private void GivenRootFolder(params string[] subfolders)

View file

@ -1,9 +1,11 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Music;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Test.Framework;
@ -31,7 +33,7 @@ namespace NzbDrone.Core.Test.MusicTests.ArtistServiceTests
[Test]
public void should_call_repo_updateMany()
{
Subject.UpdateArtists(_artists);
Subject.UpdateArtists(_artists, false);
Mocker.GetMock<IArtistRepository>().Verify(v => v.UpdateMany(_artists), Times.Once());
}
@ -46,13 +48,18 @@ namespace NzbDrone.Core.Test.MusicTests.ArtistServiceTests
var newRoot = @"C:\Test\Music2".AsOsAgnostic();
_artists.ForEach(s => s.RootFolderPath = newRoot);
Subject.UpdateArtists(_artists).ForEach(s => s.Path.Should().StartWith(newRoot));
Mocker.GetMock<IBuildArtistPaths>()
.Setup(s => s.BuildPath(It.IsAny<Artist>(), false))
.Returns<Artist, bool>((s, u) => Path.Combine(s.RootFolderPath, s.Name));
Subject.UpdateArtists(_artists, false).ForEach(s => s.Path.Should().StartWith(newRoot));
}
[Test]
public void should_not_update_path_when_rootFolderPath_is_empty()
{
Subject.UpdateArtists(_artists).ForEach(s =>
Subject.UpdateArtists(_artists, false).ForEach(s =>
{
var expectedPath = _artists.Single(ser => ser.Id == s.Id).Path;
s.Path.Should().Be(expectedPath);
@ -75,7 +82,7 @@ namespace NzbDrone.Core.Test.MusicTests.ArtistServiceTests
var newRoot = @"C:\Test\Music2".AsOsAgnostic();
artist.ForEach(s => s.RootFolderPath = newRoot);
Subject.UpdateArtists(artist);
Subject.UpdateArtists(artist, false);
}
}
}

View file

@ -29,10 +29,10 @@ namespace NzbDrone.Core.Test.MusicTests
_command = new MoveArtistCommand
{
ArtistId = 1,
SourcePath = @"C:\Test\Music\Artist".AsOsAgnostic(),
DestinationPath = @"C:\Test\Music2\Artist".AsOsAgnostic()
};
ArtistId = 1,
SourcePath = @"C:\Test\Music\Artist".AsOsAgnostic(),
DestinationPath = @"C:\Test\Music2\Artist".AsOsAgnostic()
};
_bulkCommand = new BulkMoveArtistCommand
{
@ -48,15 +48,19 @@ namespace NzbDrone.Core.Test.MusicTests
};
Mocker.GetMock<IArtistService>()
.Setup(s => s.GetArtist(It.IsAny<int>()))
.Returns(_artist);
.Setup(s => s.GetArtist(It.IsAny<int>()))
.Returns(_artist);
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(It.IsAny<string>()))
.Returns(true);
}
private void GivenFailedMove()
{
Mocker.GetMock<IDiskTransferService>()
.Setup(s => s.TransferFolder(It.IsAny<string>(), It.IsAny<string>(), TransferMode.Move, true))
.Throws<IOException>();
.Setup(s => s.TransferFolder(It.IsAny<string>(), It.IsAny<string>(), TransferMode.Move, true))
.Throws<IOException>();
}
[Test]
@ -89,7 +93,9 @@ namespace NzbDrone.Core.Test.MusicTests
Subject.Execute(_command);
Mocker.GetMock<IDiskTransferService>()
.Verify(v => v.TransferFolder(_command.SourcePath, _command.DestinationPath, TransferMode.Move, It.IsAny<bool>()), Times.Once());
.Verify(
v => v.TransferFolder(_command.SourcePath, _command.DestinationPath, TransferMode.Move,
It.IsAny<bool>()), Times.Once());
Mocker.GetMock<IBuildFileNames>()
.Verify(v => v.GetArtistFolder(It.IsAny<Artist>(), null), Times.Never());
@ -109,7 +115,29 @@ namespace NzbDrone.Core.Test.MusicTests
Subject.Execute(_bulkCommand);
Mocker.GetMock<IDiskTransferService>()
.Verify(v => v.TransferFolder(_bulkCommand.Artist.First().SourcePath, expectedPath, TransferMode.Move, It.IsAny<bool>()), Times.Once());
.Verify(
v => v.TransferFolder(_bulkCommand.Artist.First().SourcePath, expectedPath, TransferMode.Move,
It.IsAny<bool>()), Times.Once());
}
[Test]
public void should_skip_artist_folder_if_it_does_not_exist()
{
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FolderExists(It.IsAny<string>()))
.Returns(false);
Subject.Execute(_command);
Mocker.GetMock<IDiskTransferService>()
.Verify(
v => v.TransferFolder(_command.SourcePath, _command.DestinationPath, TransferMode.Move,
It.IsAny<bool>()), Times.Never());
Mocker.GetMock<IBuildFileNames>()
.Verify(v => v.GetArtistFolder(It.IsAny<Artist>(), null), Times.Never());
}
}
}

View file

@ -1,4 +1,4 @@
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Core.Analytics
@ -17,6 +17,6 @@ namespace NzbDrone.Core.Analytics
_configFileProvider = configFileProvider;
}
public bool IsEnabled => _configFileProvider.AnalyticsEnabled && RuntimeInfo.IsProduction;
public bool IsEnabled => _configFileProvider.AnalyticsEnabled;
}
}
}

View file

@ -23,6 +23,7 @@ namespace NzbDrone.Core.Annotations
public enum FieldType
{
Textbox,
Number,
Password,
Checkbox,
Select,

View file

@ -13,6 +13,7 @@ using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Music;
using NzbDrone.Core.Music.Events;
using NzbDrone.Core.MediaFiles.TrackImport;
@ -38,6 +39,7 @@ namespace NzbDrone.Core.MediaFiles
private readonly IConfigService _configService;
private readonly IArtistService _artistService;
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService;
private readonly IRootFolderService _rootFolderService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
@ -46,6 +48,7 @@ namespace NzbDrone.Core.MediaFiles
IImportApprovedTracks importApprovedTracks,
IConfigService configService,
IArtistService artistService,
IRootFolderService rootFolderService,
IMediaFileTableCleanupService mediaFileTableCleanupService,
IEventAggregator eventAggregator,
Logger logger)
@ -56,6 +59,7 @@ namespace NzbDrone.Core.MediaFiles
_configService = configService;
_artistService = artistService;
_mediaFileTableCleanupService = mediaFileTableCleanupService;
_rootFolderService = rootFolderService;
_eventAggregator = eventAggregator;
_logger = logger;
}
@ -64,7 +68,7 @@ namespace NzbDrone.Core.MediaFiles
public void Scan(Artist artist)
{
var rootFolder = _diskProvider.GetParentFolder(artist.Path);
var rootFolder = _rootFolderService.GetBestRootFolderPath(artist.Path);
if (!_diskProvider.FolderExists(rootFolder))
{
@ -142,6 +146,7 @@ namespace NzbDrone.Core.MediaFiles
_logger.Trace("{0} files were found in {1}", filesOnDisk.Count, path);
_logger.Debug("{0} audio files were found in {1}", mediaFileList.Count, path);
return mediaFileList.ToArray();
}
@ -157,6 +162,7 @@ namespace NzbDrone.Core.MediaFiles
_logger.Trace("{0} files were found in {1}", filesOnDisk.Count, path);
_logger.Debug("{0} non-music files were found in {1}", mediaFileList.Count, path);
return mediaFileList.ToArray();
}

View file

@ -7,6 +7,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
public class ManualImportFile
{
public string Path { get; set; }
public string FolderName { get; set; }
public int ArtistId { get; set; }
public int AlbumId { get; set; }
public List<int> TrackIds { get; set; }

View file

@ -10,6 +10,7 @@ namespace NzbDrone.Core.MediaFiles.TrackImport.Manual
{
public string Path { get; set; }
public string RelativePath { get; set; }
public string FolderName { get; set; }
public string Name { get; set; }
public long Size { get; set; }
public Artist Artist { get; set; }

View file

@ -0,0 +1,48 @@
using System;
using System.IO;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.RootFolders;
namespace NzbDrone.Core.Music
{
public interface IBuildArtistPaths
{
string BuildPath(Artist artist, bool useExistingRelativeFolder);
}
public class ArtistPathBuilder : IBuildArtistPaths
{
private readonly IBuildFileNames _fileNameBuilder;
private readonly IRootFolderService _rootFolderService;
public ArtistPathBuilder(IBuildFileNames fileNameBuilder, IRootFolderService rootFolderService)
{
_fileNameBuilder = fileNameBuilder;
_rootFolderService = rootFolderService;
}
public string BuildPath(Artist artist, bool useExistingRelativeFolder)
{
if (artist.RootFolderPath.IsNullOrWhiteSpace())
{
throw new ArgumentException("Root folder was not provided", nameof(artist));
}
if (useExistingRelativeFolder && artist.Path.IsNotNullOrWhiteSpace())
{
var relativePath = GetExistingRelativePath(artist);
return Path.Combine(artist.RootFolderPath, relativePath);
}
return Path.Combine(artist.RootFolderPath, _fileNameBuilder.GetArtistFolder(artist));
}
private string GetExistingRelativePath(Artist artist)
{
var rootFolderPath = _rootFolderService.GetBestRootFolderPath(artist.Path);
return rootFolderPath.GetRelativePath(artist.Path);
}
}
}

View file

@ -25,7 +25,7 @@ namespace NzbDrone.Core.Music
List<Artist> GetAllArtists();
List<Artist> AllForTag(int tagId);
Artist UpdateArtist(Artist artist);
List<Artist> UpdateArtists(List<Artist> artist);
List<Artist> UpdateArtists(List<Artist> artist, bool useExistingRelativeFolder);
bool ArtistPathExists(string folder);
void RemoveAddOptions(Artist artist);
}
@ -35,19 +35,19 @@ namespace NzbDrone.Core.Music
private readonly IArtistRepository _artistRepository;
private readonly IEventAggregator _eventAggregator;
private readonly ITrackService _trackService;
private readonly IBuildFileNames _fileNameBuilder;
private readonly IBuildArtistPaths _artistPathBuilder;
private readonly Logger _logger;
public ArtistService(IArtistRepository artistRepository,
IEventAggregator eventAggregator,
ITrackService trackService,
IBuildFileNames fileNameBuilder,
IBuildArtistPaths artistPathBuilder,
Logger logger)
{
_artistRepository = artistRepository;
_eventAggregator = eventAggregator;
_trackService = trackService;
_fileNameBuilder = fileNameBuilder;
_artistPathBuilder = artistPathBuilder;
_logger = logger;
}
@ -141,22 +141,22 @@ namespace NzbDrone.Core.Music
return updatedArtist;
}
public List<Artist> UpdateArtists(List<Artist> artist)
public List<Artist> UpdateArtists(List<Artist> artist, bool useExistingRelativeFolder)
{
_logger.Debug("Updating {0} artist", artist.Count);
foreach (var s in artist)
{
_logger.Trace("Updating: {0}", s.Name);
if (!s.RootFolderPath.IsNullOrWhiteSpace())
{
// Build the artist folder name instead of using the existing folder name.
// This may lead to folder name changes, but consistent with adding a new artist.
s.Path = _artistPathBuilder.BuildPath(s, useExistingRelativeFolder);
s.Path = Path.Combine(s.RootFolderPath, _fileNameBuilder.GetArtistFolder(s));
//s.Path = Path.Combine(s.RootFolderPath, _fileNameBuilder.GetArtistFolder(s));
_logger.Trace("Changing path for {0} to {1}", s.Name, s.Path);
}
else
{
_logger.Trace("Not changing path for: {0}", s.Name);

View file

@ -14,18 +14,21 @@ namespace NzbDrone.Core.Music
{
private readonly IArtistService _artistService;
private readonly IBuildFileNames _filenameBuilder;
private readonly IDiskProvider _diskProvider;
private readonly IDiskTransferService _diskTransferService;
private readonly IEventAggregator _eventAggregator;
private readonly Logger _logger;
public MoveArtistService(IArtistService artistService,
IBuildFileNames filenameBuilder,
IDiskProvider diskProvider,
IDiskTransferService diskTransferService,
IEventAggregator eventAggregator,
Logger logger)
{
_artistService = artistService;
_filenameBuilder = filenameBuilder;
_diskProvider = diskProvider;
_diskTransferService = diskTransferService;
_eventAggregator = eventAggregator;
_logger = logger;
@ -33,6 +36,12 @@ namespace NzbDrone.Core.Music
private void MoveSingleArtist(Artist artist, string sourcePath, string destinationPath)
{
if (!_diskProvider.FolderExists(sourcePath))
{
_logger.Debug("Folder '{0}' for '{1}' does not exist, not moving.", sourcePath, artist.Name);
return;
}
_logger.ProgressInfo("Moving {0} from '{1}' to '{2}'", artist.Name, sourcePath, destinationPath);
try

View file

@ -153,6 +153,18 @@ namespace NzbDrone.Core.Music
return albumsToUpdate;
}
private void RescanArtist(Artist artist)
{
try
{
_diskScanService.Scan(artist);
}
catch (Exception e)
{
_logger.Error(e, "Couldn't rescan artist {0}", artist);
}
}
public void Execute(RefreshArtistCommand message)
{
_eventAggregator.PublishEvent(new ArtistRefreshStartingEvent(message.Trigger == CommandTrigger.Manual));
@ -160,7 +172,17 @@ namespace NzbDrone.Core.Music
if (message.ArtistId.HasValue)
{
var artist = _artistService.GetArtist(message.ArtistId.Value);
RefreshArtistInfo(artist, true);
try
{
RefreshArtistInfo(artist, true);
}
catch (Exception e)
{
_logger.Error(e, "Couldn't refresh info for {0}", artist);
RescanArtist(artist);
throw;
}
}
else
{
@ -178,20 +200,14 @@ namespace NzbDrone.Core.Music
catch (Exception e)
{
_logger.Error(e, "Couldn't refresh info for {0}", artist);
RescanArtist(artist);
}
}
else
{
try
{
_logger.Info("Skipping refresh of artist: {0}", artist.Name);
_diskScanService.Scan(artist);
}
catch (Exception e)
{
_logger.Error(e, "Couldn't rescan artist {0}", artist);
}
_logger.Info("Skipping refresh of artist: {0}", artist.Name);
RescanArtist(artist);
}
}
}

View file

@ -817,6 +817,7 @@
<Compile Include="Music\AlbumAddedService.cs" />
<Compile Include="Music\AlbumEditedService.cs" />
<Compile Include="Music\AlbumRelease.cs" />
<Compile Include="Music\ArtistPathBuilder.cs" />
<Compile Include="Music\ArtistStatusType.cs" />
<Compile Include="Music\AlbumCutoffService.cs" />
<Compile Include="Music\Commands\BulkMoveArtistCommand.cs" />

View file

@ -6,7 +6,6 @@ using NLog;
using NzbDrone.Common;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.RootFolders
@ -18,6 +17,7 @@ namespace NzbDrone.Core.RootFolders
RootFolder Add(RootFolder rootDir);
void Remove(int id);
RootFolder Get(int id);
string GetBestRootFolderPath(string path);
}
public class RootFolderService : IRootFolderService
@ -25,7 +25,6 @@ namespace NzbDrone.Core.RootFolders
private readonly IRootFolderRepository _rootFolderRepository;
private readonly IDiskProvider _diskProvider;
private readonly IArtistRepository _artistRepository;
private readonly IConfigService _configService;
private readonly Logger _logger;
private static readonly HashSet<string> SpecialFolders = new HashSet<string>
@ -45,13 +44,11 @@ namespace NzbDrone.Core.RootFolders
public RootFolderService(IRootFolderRepository rootFolderRepository,
IDiskProvider diskProvider,
IArtistRepository artistRepository,
IConfigService configService,
Logger logger)
{
_rootFolderRepository = rootFolderRepository;
_diskProvider = diskProvider;
_artistRepository = artistRepository;
_configService = configService;
_logger = logger;
}
@ -167,5 +164,20 @@ namespace NzbDrone.Core.RootFolders
rootFolder.UnmappedFolders = GetUnmappedFolders(rootFolder.Path);
return rootFolder;
}
public string GetBestRootFolderPath(string path)
{
var possibleRootFolder = All().Where(r => r.Path.IsParentPath(path))
.OrderByDescending(r => r.Path.Length)
.FirstOrDefault();
if (possibleRootFolder == null)
{
return Path.GetDirectoryName(path);
}
return possibleRootFolder.Path;
}
}
}

View file

@ -1,18 +1,14 @@
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Notifications;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Restrictions;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.Tags
{
public class TagDetails : ModelBase
{
public string Label { get; set; }
public List<Artist> Artist { get; set; }
public List<NotificationDefinition> Notifications { get; set; }
public List<Restriction> Restrictions { get; set; }
public List<DelayProfile> DelayProfiles { get; set; }
public List<int> ArtistIds { get; set; }
public List<int> NotificationIds { get; set; }
public List<int> RestrictionIds { get; set; }
public List<int> DelayProfileIds { get; set; }
}
}

View file

@ -13,6 +13,7 @@ namespace NzbDrone.Core.Tags
Tag GetTag(int tagId);
Tag GetTag(string tag);
TagDetails Details(int tagId);
List<TagDetails> Details();
List<Tag> All();
Tag Add(Tag tag);
Tag Update(Tag tag);
@ -69,14 +70,41 @@ namespace NzbDrone.Core.Tags
var artist = _artistService.AllForTag(tagId);
return new TagDetails
{
Id = tagId,
Label = tag.Label,
DelayProfiles = delayProfiles,
Notifications = notifications,
Restrictions = restrictions,
Artist = artist
};
{
Id = tagId,
Label = tag.Label,
DelayProfileIds = delayProfiles.Select(c => c.Id).ToList(),
NotificationIds = notifications.Select(c => c.Id).ToList(),
RestrictionIds = restrictions.Select(c => c.Id).ToList(),
ArtistIds = artist.Select(c => c.Id).ToList()
};
}
public List<TagDetails> Details()
{
var tags = All();
var delayProfiles = _delayProfileService.All();
var notifications = _notificationFactory.All();
var restrictions = _restrictionService.All();
var artists = _artistService.GetAllArtists();
var details = new List<TagDetails>();
foreach (var tag in tags)
{
details.Add(new TagDetails
{
Id = tag.Id,
Label = tag.Label,
DelayProfileIds = delayProfiles.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(),
NotificationIds = notifications.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(),
RestrictionIds = restrictions.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList(),
ArtistIds = artists.Where(c => c.Tags.Contains(tag.Id)).Select(c => c.Id).ToList()
}
);
}
return details;
}
public List<Tag> All()