mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-20 13:33:34 -07:00
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:
parent
afa78b1d20
commit
6581b3a2c5
198 changed files with 3057 additions and 888 deletions
|
@ -96,7 +96,7 @@ namespace Lidarr.Api.V1.Artist
|
|||
});
|
||||
}
|
||||
|
||||
return _artistService.UpdateArtists(artistToUpdate)
|
||||
return _artistService.UpdateArtists(artistToUpdate, !resource.MoveFiles)
|
||||
.ToResource()
|
||||
.AsResponse(HttpStatusCode.Accepted);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace NzbDrone.Core.Annotations
|
|||
public enum FieldType
|
||||
{
|
||||
Textbox,
|
||||
Number,
|
||||
Password,
|
||||
Checkbox,
|
||||
Select,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
|
|
48
src/NzbDrone.Core/Music/ArtistPathBuilder.cs
Normal file
48
src/NzbDrone.Core/Music/ArtistPathBuilder.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue