mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-19 13:10:13 -07:00
parent
0da6f6e3c7
commit
de5e0871cf
31 changed files with 900 additions and 209 deletions
152
src/NzbDrone.Api/Music/ArtistBulkImportModule.cs
Normal file
152
src/NzbDrone.Api/Music/ArtistBulkImportModule.cs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Nancy;
|
||||||
|
using NzbDrone.Api.REST;
|
||||||
|
using NzbDrone.Api.Extensions;
|
||||||
|
using NzbDrone.Core.MediaCover;
|
||||||
|
using NzbDrone.Core.MetadataSource;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using System.Linq;
|
||||||
|
using System;
|
||||||
|
using Marr.Data;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
|
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||||
|
using NzbDrone.Core.RootFolders;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Music
|
||||||
|
{
|
||||||
|
|
||||||
|
public class UnmappedComparer : IComparer<UnmappedFolder>
|
||||||
|
{
|
||||||
|
public int Compare(UnmappedFolder a, UnmappedFolder b)
|
||||||
|
{
|
||||||
|
return a.Name.CompareTo(b.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MusicBulkImportModule : NzbDroneRestModule<ArtistResource>
|
||||||
|
{
|
||||||
|
private readonly ISearchForNewArtist _searchProxy;
|
||||||
|
private readonly IRootFolderService _rootFolderService;
|
||||||
|
private readonly IMakeImportDecision _importDecisionMaker;
|
||||||
|
private readonly IDiskScanService _diskScanService;
|
||||||
|
private readonly ICached<Core.Music.Artist> _mappedArtists;
|
||||||
|
private readonly IArtistService _artistService;
|
||||||
|
|
||||||
|
public MusicBulkImportModule(ISearchForNewArtist searchProxy,
|
||||||
|
IRootFolderService rootFolderService,
|
||||||
|
IMakeImportDecision importDecisionMaker,
|
||||||
|
IDiskScanService diskScanService,
|
||||||
|
ICacheManager cacheManager,
|
||||||
|
IArtistService artistService
|
||||||
|
)
|
||||||
|
: base("/artist/bulkimport")
|
||||||
|
{
|
||||||
|
_searchProxy = searchProxy;
|
||||||
|
_rootFolderService = rootFolderService;
|
||||||
|
_importDecisionMaker = importDecisionMaker;
|
||||||
|
_diskScanService = diskScanService;
|
||||||
|
_mappedArtists = cacheManager.GetCache<Artist>(GetType(), "mappedArtistsCache");
|
||||||
|
_artistService = artistService;
|
||||||
|
Get["/"] = x => Search();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Response Search()
|
||||||
|
{
|
||||||
|
if (Request.Query.Id == 0)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Invalid Query");
|
||||||
|
}
|
||||||
|
|
||||||
|
RootFolder rootFolder = _rootFolderService.Get(Request.Query.Id);
|
||||||
|
|
||||||
|
var unmapped = rootFolder.UnmappedFolders.OrderBy(f => f.Name).ToList();
|
||||||
|
|
||||||
|
var paged = unmapped;
|
||||||
|
|
||||||
|
var mapped = paged.Select(page =>
|
||||||
|
{
|
||||||
|
Artist m = null;
|
||||||
|
|
||||||
|
var mappedArtist = _mappedArtists.Find(page.Name);
|
||||||
|
|
||||||
|
if (mappedArtist != null)
|
||||||
|
{
|
||||||
|
return mappedArtist;
|
||||||
|
}
|
||||||
|
|
||||||
|
var files = _diskScanService.GetMusicFiles(page.Path);
|
||||||
|
|
||||||
|
// Check for music files in directory
|
||||||
|
if (files.Count() == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedTitle = Parser.ParseMusicPath(files.FirstOrDefault());
|
||||||
|
if (parsedTitle == null || parsedTitle.ArtistTitle == null)
|
||||||
|
{
|
||||||
|
m = new Artist
|
||||||
|
{
|
||||||
|
Name = page.Name.Replace(".", " ").Replace("-", " "),
|
||||||
|
Path = page.Path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m = new Artist
|
||||||
|
{
|
||||||
|
Name = parsedTitle.ArtistTitle,
|
||||||
|
Path = page.Path
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var searchResults = _searchProxy.SearchForNewArtist(m.Name);
|
||||||
|
|
||||||
|
if (searchResults == null || searchResults.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
mappedArtist = searchResults.First();
|
||||||
|
|
||||||
|
if (mappedArtist != null)
|
||||||
|
{
|
||||||
|
mappedArtist.Monitored = true;
|
||||||
|
mappedArtist.Path = page.Path;
|
||||||
|
|
||||||
|
_mappedArtists.Set(page.Name, mappedArtist, TimeSpan.FromDays(2));
|
||||||
|
|
||||||
|
return mappedArtist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
var mapping = MapToResource(mapped.Where(m => m != null)).ToList().AsResponse();
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static IEnumerable<ArtistResource> MapToResource(IEnumerable<Artist> artists)
|
||||||
|
{
|
||||||
|
foreach (var currentArtist in artists)
|
||||||
|
{
|
||||||
|
var resource = currentArtist.ToResource();
|
||||||
|
var poster = currentArtist.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
|
||||||
|
if (poster != null)
|
||||||
|
{
|
||||||
|
resource.RemotePoster = poster.Url;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield return resource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,9 +11,9 @@ namespace NzbDrone.Api.Music
|
||||||
{
|
{
|
||||||
public class ArtistLookupModule : NzbDroneRestModule<ArtistResource>
|
public class ArtistLookupModule : NzbDroneRestModule<ArtistResource>
|
||||||
{
|
{
|
||||||
private readonly ISearchForNewSeries _searchProxy; //TODO: Switch out for Music varriant
|
private readonly ISearchForNewArtist _searchProxy;
|
||||||
|
|
||||||
public ArtistLookupModule(ISearchForNewSeries searchProxy)
|
public ArtistLookupModule(ISearchForNewArtist searchProxy)
|
||||||
: base("/artist/lookup")
|
: base("/artist/lookup")
|
||||||
{
|
{
|
||||||
_searchProxy = searchProxy;
|
_searchProxy = searchProxy;
|
||||||
|
|
30
src/NzbDrone.Api/Music/ListImport.cs
Normal file
30
src/NzbDrone.Api/Music/ListImport.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Nancy;
|
||||||
|
using Nancy.Extensions;
|
||||||
|
using NzbDrone.Api.Extensions;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Music
|
||||||
|
{
|
||||||
|
public class ListImportModule : NzbDroneApiModule
|
||||||
|
{
|
||||||
|
private readonly IAddArtistService _artistService;
|
||||||
|
|
||||||
|
public ListImportModule(IAddArtistService artistService)
|
||||||
|
: base("/artist/import")
|
||||||
|
{
|
||||||
|
_artistService = artistService;
|
||||||
|
Put["/"] = Artist => SaveAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Response SaveAll()
|
||||||
|
{
|
||||||
|
var resources = Request.Body.FromJson<List<ArtistResource>>();
|
||||||
|
|
||||||
|
var Artists = resources.Select(ArtistResource => (ArtistResource.ToModel())).Where(m => m != null).DistinctBy(m => m.ForeignArtistId).ToList();
|
||||||
|
|
||||||
|
return _artistService.AddArtists(Artists).ToResource().AsResponse(HttpStatusCode.Accepted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -108,7 +108,9 @@
|
||||||
<Compile Include="Commands\CommandResource.cs" />
|
<Compile Include="Commands\CommandResource.cs" />
|
||||||
<Compile Include="AlbumStudio\AlbumStudioModule.cs" />
|
<Compile Include="AlbumStudio\AlbumStudioModule.cs" />
|
||||||
<Compile Include="AlbumStudio\AlbumStudioResource.cs" />
|
<Compile Include="AlbumStudio\AlbumStudioResource.cs" />
|
||||||
|
<Compile Include="Music\ArtistBulkImportModule.cs" />
|
||||||
<Compile Include="Music\ArtistEditorModule.cs" />
|
<Compile Include="Music\ArtistEditorModule.cs" />
|
||||||
|
<Compile Include="Music\ListImport.cs" />
|
||||||
<Compile Include="TrackFiles\TrackFileModule.cs" />
|
<Compile Include="TrackFiles\TrackFileModule.cs" />
|
||||||
<Compile Include="TrackFiles\TrackFileResource.cs" />
|
<Compile Include="TrackFiles\TrackFileResource.cs" />
|
||||||
<Compile Include="Albums\AlbumModule.cs" />
|
<Compile Include="Albums\AlbumModule.cs" />
|
||||||
|
@ -244,7 +246,6 @@
|
||||||
<Compile Include="Series\SeasonResource.cs" />
|
<Compile Include="Series\SeasonResource.cs" />
|
||||||
<Compile Include="SeasonPass\SeasonPassModule.cs" />
|
<Compile Include="SeasonPass\SeasonPassModule.cs" />
|
||||||
<Compile Include="Series\SeriesEditorModule.cs" />
|
<Compile Include="Series\SeriesEditorModule.cs" />
|
||||||
<Compile Include="Series\SeriesLookupModule.cs" />
|
|
||||||
<Compile Include="Series\SeriesModule.cs" />
|
<Compile Include="Series\SeriesModule.cs" />
|
||||||
<Compile Include="Series\SeriesResource.cs" />
|
<Compile Include="Series\SeriesResource.cs" />
|
||||||
<Compile Include="Series\SeasonStatisticsResource.cs" />
|
<Compile Include="Series\SeasonStatisticsResource.cs" />
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using Nancy;
|
|
||||||
using NzbDrone.Api.Extensions;
|
|
||||||
using NzbDrone.Core.MediaCover;
|
|
||||||
using NzbDrone.Core.MetadataSource;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace NzbDrone.Api.Series
|
|
||||||
{
|
|
||||||
public class SeriesLookupModule : NzbDroneRestModule<SeriesResource>
|
|
||||||
{
|
|
||||||
private readonly ISearchForNewSeries _searchProxy;
|
|
||||||
|
|
||||||
public SeriesLookupModule(ISearchForNewSeries searchProxy)
|
|
||||||
: base("/series/lookup")
|
|
||||||
{
|
|
||||||
_searchProxy = searchProxy;
|
|
||||||
Get["/"] = x => Search();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Response Search()
|
|
||||||
{
|
|
||||||
var tvDbResults = _searchProxy.SearchForNewSeries((string)Request.Query.term);
|
|
||||||
return MapToResource(tvDbResults).AsResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static IEnumerable<SeriesResource> MapToResource(IEnumerable<Core.Tv.Series> series)
|
|
||||||
{
|
|
||||||
foreach (var currentSeries in series)
|
|
||||||
{
|
|
||||||
var resource = currentSeries.ToResource();
|
|
||||||
var poster = currentSeries.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
|
|
||||||
if (poster != null)
|
|
||||||
{
|
|
||||||
resource.RemotePoster = poster.Url;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return resource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
51
src/NzbDrone.Core.Test/BulkImport/BulkImportFixture.cs
Normal file
51
src/NzbDrone.Core.Test/BulkImport/BulkImportFixture.cs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Moq;
|
||||||
|
using NzbDrone.Core.Organizer;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Music.Events;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.BulkImport
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class BulkImportFixture : CoreTest<AddArtistService>
|
||||||
|
{
|
||||||
|
private List<Artist> fakeArtists;
|
||||||
|
private FluentValidation.Results.ValidationResult fakeValidation;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
fakeArtists = Builder<Artist>.CreateListOfSize(3).BuildList();
|
||||||
|
fakeArtists.ForEach(m =>
|
||||||
|
{
|
||||||
|
m.Path = null;
|
||||||
|
m.RootFolderPath = @"C:\Test\Music";
|
||||||
|
});
|
||||||
|
|
||||||
|
fakeValidation = Builder<FluentValidation.Results.ValidationResult>.CreateNew().Build();
|
||||||
|
}
|
||||||
|
[Test]
|
||||||
|
public void artist_added_event_should_have_proper_path()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IBuildFileNames>()
|
||||||
|
.Setup(s => s.GetArtistFolder(It.IsAny<Artist>(), null))
|
||||||
|
.Returns((Artist m, NamingConfig n) => m.Name);
|
||||||
|
|
||||||
|
Mocker.GetMock<IAddArtistValidator>()
|
||||||
|
.Setup(s => s.Validate(It.IsAny<Artist>()))
|
||||||
|
.Returns(fakeValidation);
|
||||||
|
|
||||||
|
var artists = Subject.AddArtists(fakeArtists);
|
||||||
|
|
||||||
|
foreach (Artist artist in artists)
|
||||||
|
{
|
||||||
|
artist.Path.Should().NotBeNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,37 +17,34 @@ namespace NzbDrone.Core.Test.MetadataSource.SkyHook
|
||||||
UseRealHttp();
|
UseRealHttp();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("The Simpsons", "The Simpsons")]
|
[TestCase("Coldplay", "Coldplay")]
|
||||||
[TestCase("South Park", "South Park")]
|
[TestCase("Avenged Sevenfold", "Avenged Sevenfold")]
|
||||||
[TestCase("Franklin & Bash", "Franklin & Bash")]
|
[TestCase("3OH!3", "3OH!3")]
|
||||||
[TestCase("House", "House")]
|
[TestCase("Where's Kitty?", "Where's Kitty?")]
|
||||||
[TestCase("Mr. D", "Mr. D")]
|
[TestCase("The Academy Is...", "The Academy Is...")]
|
||||||
[TestCase("Rob & Big", "Rob & Big")]
|
[TestCase("lidarr:f59c5520-5f46-4d2c-b2c4-822eabf53419", "Linkin Park")]
|
||||||
[TestCase("M*A*S*H", "M*A*S*H")]
|
[TestCase("lidarrid:f59c5520-5f46-4d2c-b2c4-822eabf53419", "Linkin Park")]
|
||||||
//[TestCase("imdb:tt0436992", "Doctor Who (2005)")]
|
[TestCase("lidarrid: f59c5520-5f46-4d2c-b2c4-822eabf53419 ", "Linkin Park")]
|
||||||
[TestCase("tvdb:78804", "Doctor Who (2005)")]
|
|
||||||
[TestCase("tvdbid:78804", "Doctor Who (2005)")]
|
|
||||||
[TestCase("tvdbid: 78804 ", "Doctor Who (2005)")]
|
|
||||||
public void successful_search(string title, string expected)
|
public void successful_search(string title, string expected)
|
||||||
{
|
{
|
||||||
var result = Subject.SearchForNewSeries(title);
|
var result = Subject.SearchForNewArtist(title);
|
||||||
|
|
||||||
result.Should().NotBeEmpty();
|
result.Should().NotBeEmpty();
|
||||||
|
|
||||||
result[0].Title.Should().Be(expected);
|
result[0].Name.Should().Be(expected);
|
||||||
|
|
||||||
ExceptionVerification.IgnoreWarns();
|
ExceptionVerification.IgnoreWarns();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("tvdbid:")]
|
[TestCase("lidarrid:")]
|
||||||
[TestCase("tvdbid: 99999999999999999999")]
|
[TestCase("lidarrid: 99999999999999999999")]
|
||||||
[TestCase("tvdbid: 0")]
|
[TestCase("lidarrid: 0")]
|
||||||
[TestCase("tvdbid: -12")]
|
[TestCase("lidarrid: -12")]
|
||||||
[TestCase("tvdbid:289578")]
|
[TestCase("lidarrid:289578")]
|
||||||
[TestCase("adjalkwdjkalwdjklawjdlKAJD;EF")]
|
[TestCase("adjalkwdjkalwdjklawjdlKAJD;EF")]
|
||||||
public void no_search_result(string term)
|
public void no_search_result(string term)
|
||||||
{
|
{
|
||||||
var result = Subject.SearchForNewSeries(term);
|
var result = Subject.SearchForNewArtist(term);
|
||||||
result.Should().BeEmpty();
|
result.Should().BeEmpty();
|
||||||
|
|
||||||
ExceptionVerification.IgnoreWarns();
|
ExceptionVerification.IgnoreWarns();
|
||||||
|
|
|
@ -115,6 +115,7 @@
|
||||||
<Compile Include="ArtistStatsTests\ArtistStatisticsFixture.cs" />
|
<Compile Include="ArtistStatsTests\ArtistStatisticsFixture.cs" />
|
||||||
<Compile Include="Blacklisting\BlacklistRepositoryFixture.cs" />
|
<Compile Include="Blacklisting\BlacklistRepositoryFixture.cs" />
|
||||||
<Compile Include="Blacklisting\BlacklistServiceFixture.cs" />
|
<Compile Include="Blacklisting\BlacklistServiceFixture.cs" />
|
||||||
|
<Compile Include="BulkImport\BulkImportFixture.cs" />
|
||||||
<Compile Include="Configuration\ConfigCachingFixture.cs" />
|
<Compile Include="Configuration\ConfigCachingFixture.cs" />
|
||||||
<Compile Include="Configuration\ConfigServiceFixture.cs" />
|
<Compile Include="Configuration\ConfigServiceFixture.cs" />
|
||||||
<Compile Include="DataAugmentation\SceneNumbering\XemServiceFixture.cs" />
|
<Compile Include="DataAugmentation\SceneNumbering\XemServiceFixture.cs" />
|
||||||
|
|
|
@ -26,6 +26,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
void Scan(Artist artist);
|
void Scan(Artist artist);
|
||||||
string[] GetVideoFiles(string path, bool allDirectories = true);
|
string[] GetVideoFiles(string path, bool allDirectories = true);
|
||||||
|
string[] GetMusicFiles(string path, bool allDirectories = true);
|
||||||
string[] GetNonVideoFiles(string path, bool allDirectories = true);
|
string[] GetNonVideoFiles(string path, bool allDirectories = true);
|
||||||
List<string> FilterFiles(Series series, IEnumerable<string> files);
|
List<string> FilterFiles(Series series, IEnumerable<string> files);
|
||||||
}
|
}
|
||||||
|
@ -166,7 +167,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
_logger.Trace("{0} files were found in {1}", filesOnDisk.Count, path);
|
_logger.Trace("{0} files were found in {1}", filesOnDisk.Count, path);
|
||||||
_logger.Debug("{0} video files were found in {1}", mediaFileList.Count, path);
|
_logger.Debug("{0} audio files were found in {1}", mediaFileList.Count, path);
|
||||||
return mediaFileList.ToArray();
|
return mediaFileList.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Core.Tv;
|
|
||||||
using NzbDrone.Core.Music;
|
using NzbDrone.Core.Music;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MetadataSource
|
namespace NzbDrone.Core.MetadataSource
|
||||||
{
|
{
|
||||||
public interface ISearchForNewSeries
|
public interface ISearchForNewArtist
|
||||||
{
|
{
|
||||||
List<Series> SearchForNewSeries(string title);
|
|
||||||
List<Artist> SearchForNewArtist(string title);
|
List<Artist> SearchForNewArtist(string title);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MetadataSource.SkyHook
|
namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
{
|
{
|
||||||
public class SkyHookProxy : IProvideSeriesInfo, IProvideArtistInfo, ISearchForNewSeries
|
public class SkyHookProxy : IProvideSeriesInfo, IProvideArtistInfo, ISearchForNewArtist
|
||||||
{
|
{
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
@ -30,50 +30,11 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public Tuple<Series, List<Episode>> GetSeriesInfo(int tvdbSeriesId)
|
public Tuple<Series, List<Episode>> GetSeriesInfo(int tvdbSeriesId)
|
||||||
{
|
{
|
||||||
Console.WriteLine("[GetSeriesInfo] id:" + tvdbSeriesId);
|
throw new NotImplementedException();
|
||||||
var httpRequest = _requestBuilder.Create()
|
|
||||||
.SetSegment("route", "shows")
|
|
||||||
.Resource(tvdbSeriesId.ToString())
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
httpRequest.AllowAutoRedirect = true;
|
|
||||||
httpRequest.SuppressHttpError = true;
|
|
||||||
|
|
||||||
var httpResponse = _httpClient.Get<ShowResource>(httpRequest);
|
|
||||||
|
|
||||||
if (httpResponse.HasHttpError)
|
|
||||||
{
|
|
||||||
if (httpResponse.StatusCode == HttpStatusCode.NotFound)
|
|
||||||
{
|
|
||||||
throw new SeriesNotFoundException(tvdbSeriesId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new HttpException(httpRequest, httpResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var episodes = httpResponse.Resource.Episodes.Select(MapEpisode);
|
|
||||||
var series = MapSeries(httpResponse.Resource);
|
|
||||||
|
|
||||||
return new Tuple<Series, List<Episode>>(series, episodes.ToList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Series> SearchForNewSeries(string title)
|
|
||||||
{
|
|
||||||
// TODO: Remove this API
|
|
||||||
var tempList = new List<Series>();
|
|
||||||
var tempSeries = new Series();
|
|
||||||
tempSeries.Title = "AFI";
|
|
||||||
tempList.Add(tempSeries);
|
|
||||||
return tempList;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Tuple<Artist, List<Album>> GetArtistInfo(string foreignArtistId)
|
public Tuple<Artist, List<Album>> GetArtistInfo(string foreignArtistId)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -203,65 +164,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
return artist;
|
return artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Series MapSeries(ShowResource show)
|
|
||||||
{
|
|
||||||
var series = new Series();
|
|
||||||
series.TvdbId = show.TvdbId;
|
|
||||||
|
|
||||||
if (show.TvRageId.HasValue)
|
|
||||||
{
|
|
||||||
series.TvRageId = show.TvRageId.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (show.TvMazeId.HasValue)
|
|
||||||
{
|
|
||||||
series.TvMazeId = show.TvMazeId.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
series.ImdbId = show.ImdbId;
|
|
||||||
series.Title = show.Title;
|
|
||||||
series.CleanTitle = Parser.Parser.CleanSeriesTitle(show.Title);
|
|
||||||
series.SortTitle = SeriesTitleNormalizer.Normalize(show.Title, show.TvdbId);
|
|
||||||
|
|
||||||
if (show.FirstAired != null)
|
|
||||||
{
|
|
||||||
series.FirstAired = DateTime.Parse(show.FirstAired).ToUniversalTime();
|
|
||||||
series.Year = series.FirstAired.Value.Year;
|
|
||||||
}
|
|
||||||
|
|
||||||
series.Overview = show.Overview;
|
|
||||||
|
|
||||||
if (show.Runtime != null)
|
|
||||||
{
|
|
||||||
series.Runtime = show.Runtime.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
series.Network = show.Network;
|
|
||||||
|
|
||||||
if (show.TimeOfDay != null)
|
|
||||||
{
|
|
||||||
series.AirTime = string.Format("{0:00}:{1:00}", show.TimeOfDay.Hours, show.TimeOfDay.Minutes);
|
|
||||||
}
|
|
||||||
|
|
||||||
series.TitleSlug = show.Slug;
|
|
||||||
series.Status = MapSeriesStatus(show.Status);
|
|
||||||
//series.Ratings = MapRatings(show.Rating);
|
|
||||||
series.Genres = show.Genres;
|
|
||||||
|
|
||||||
if (show.ContentRating.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
series.Certification = show.ContentRating.ToUpper();
|
|
||||||
}
|
|
||||||
|
|
||||||
series.Actors = show.Actors.Select(MapActors).ToList();
|
|
||||||
series.Seasons = show.Seasons.Select(MapSeason).ToList();
|
|
||||||
series.Images = show.Images.Select(MapImage).ToList();
|
|
||||||
series.Monitored = true;
|
|
||||||
|
|
||||||
return series;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Actor MapActors(ActorResource arg)
|
private static Actor MapActors(ActorResource arg)
|
||||||
{
|
{
|
||||||
var newActor = new Actor
|
var newActor = new Actor
|
||||||
|
@ -281,40 +183,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
||||||
return newActor;
|
return newActor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Episode MapEpisode(EpisodeResource oracleEpisode)
|
private static SeriesStatusType MapArtistStatus(string status)
|
||||||
{
|
|
||||||
var episode = new Episode();
|
|
||||||
episode.Overview = oracleEpisode.Overview;
|
|
||||||
episode.SeasonNumber = oracleEpisode.SeasonNumber;
|
|
||||||
episode.EpisodeNumber = oracleEpisode.EpisodeNumber;
|
|
||||||
episode.AbsoluteEpisodeNumber = oracleEpisode.AbsoluteEpisodeNumber;
|
|
||||||
episode.Title = oracleEpisode.Title;
|
|
||||||
|
|
||||||
episode.AirDate = oracleEpisode.AirDate;
|
|
||||||
episode.AirDateUtc = oracleEpisode.AirDateUtc;
|
|
||||||
|
|
||||||
//episode.Ratings = MapRatings(oracleEpisode.Rating);
|
|
||||||
|
|
||||||
//Don't include series fanart images as episode screenshot
|
|
||||||
if (oracleEpisode.Image != null)
|
|
||||||
{
|
|
||||||
episode.Images.Add(new MediaCover.MediaCover(MediaCoverTypes.Screenshot, oracleEpisode.Image));
|
|
||||||
}
|
|
||||||
|
|
||||||
return episode;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Season MapSeason(SeasonResource seasonResource)
|
|
||||||
{
|
|
||||||
return new Season
|
|
||||||
{
|
|
||||||
SeasonNumber = seasonResource.SeasonNumber,
|
|
||||||
Images = seasonResource.Images.Select(MapImage).ToList(),
|
|
||||||
Monitored = seasonResource.SeasonNumber > 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SeriesStatusType MapSeriesStatus(string status)
|
|
||||||
{
|
{
|
||||||
if (status.Equals("ended", StringComparison.InvariantCultureIgnoreCase))
|
if (status.Equals("ended", StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,6 +17,7 @@ namespace NzbDrone.Core.Music
|
||||||
public interface IAddArtistService
|
public interface IAddArtistService
|
||||||
{
|
{
|
||||||
Artist AddArtist(Artist newArtist);
|
Artist AddArtist(Artist newArtist);
|
||||||
|
List<Artist> AddArtists(List<Artist> newArtists);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AddArtistService : IAddArtistService
|
public class AddArtistService : IAddArtistService
|
||||||
|
@ -44,7 +45,7 @@ namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
Ensure.That(newArtist, () => newArtist).IsNotNull();
|
Ensure.That(newArtist, () => newArtist).IsNotNull();
|
||||||
|
|
||||||
newArtist = AddSkyhookData(newArtist);
|
//newArtist = AddSkyhookData(newArtist);
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(newArtist.Path))
|
if (string.IsNullOrWhiteSpace(newArtist.Path))
|
||||||
{
|
{
|
||||||
|
@ -64,11 +65,21 @@ namespace NzbDrone.Core.Music
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Info("Adding Artist {0} Path: [{1}]", newArtist, newArtist.Path);
|
_logger.Info("Adding Artist {0} Path: [{1}]", newArtist, newArtist.Path);
|
||||||
_artistService.AddArtist(newArtist);
|
newArtist = _artistService.AddArtist(newArtist);
|
||||||
|
|
||||||
return newArtist;
|
return newArtist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Artist> AddArtists(List<Artist> newArtists)
|
||||||
|
{
|
||||||
|
newArtists.ForEach(artist =>
|
||||||
|
{
|
||||||
|
AddArtist(artist);
|
||||||
|
});
|
||||||
|
|
||||||
|
return newArtists;
|
||||||
|
}
|
||||||
|
|
||||||
private Artist AddSkyhookData(Artist newArtist)
|
private Artist AddSkyhookData(Artist newArtist)
|
||||||
{
|
{
|
||||||
Tuple<Artist, List<Album>> tuple;
|
Tuple<Artist, List<Album>> tuple;
|
||||||
|
|
|
@ -827,6 +827,7 @@
|
||||||
<Compile Include="Messaging\Events\IHandle.cs" />
|
<Compile Include="Messaging\Events\IHandle.cs" />
|
||||||
<Compile Include="Messaging\IProcessMessage.cs" />
|
<Compile Include="Messaging\IProcessMessage.cs" />
|
||||||
<Compile Include="MetadataSource\IProvideArtistInfo.cs" />
|
<Compile Include="MetadataSource\IProvideArtistInfo.cs" />
|
||||||
|
<Compile Include="MetadataSource\IProvideSeriesInfo.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\Resource\ActorResource.cs" />
|
<Compile Include="MetadataSource\SkyHook\Resource\ActorResource.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\Resource\AlbumResource.cs" />
|
<Compile Include="MetadataSource\SkyHook\Resource\AlbumResource.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\Resource\ArtistInfoResource.cs" />
|
<Compile Include="MetadataSource\SkyHook\Resource\ArtistInfoResource.cs" />
|
||||||
|
@ -860,8 +861,7 @@
|
||||||
<Compile Include="Extras\Metadata\MetadataRepository.cs" />
|
<Compile Include="Extras\Metadata\MetadataRepository.cs" />
|
||||||
<Compile Include="Extras\Metadata\MetadataService.cs" />
|
<Compile Include="Extras\Metadata\MetadataService.cs" />
|
||||||
<Compile Include="Extras\Metadata\MetadataType.cs" />
|
<Compile Include="Extras\Metadata\MetadataType.cs" />
|
||||||
<Compile Include="MetadataSource\IProvideSeriesInfo.cs" />
|
<Compile Include="MetadataSource\ISearchForNewArtist.cs" />
|
||||||
<Compile Include="MetadataSource\ISearchForNewSeries.cs" />
|
|
||||||
<Compile Include="Music\MonitoringOptions.cs" />
|
<Compile Include="Music\MonitoringOptions.cs" />
|
||||||
<Compile Include="Music\AlbumMonitoredService.cs" />
|
<Compile Include="Music\AlbumMonitoredService.cs" />
|
||||||
<Compile Include="Music\TrackMonitoredService.cs" />
|
<Compile Include="Music\TrackMonitoredService.cs" />
|
||||||
|
|
|
@ -6,6 +6,7 @@ var ExistingArtistCollectionView = require('./Existing/AddExistingArtistCollecti
|
||||||
var AddArtistView = require('./AddArtistView');
|
var AddArtistView = require('./AddArtistView');
|
||||||
var ProfileCollection = require('../Profile/ProfileCollection');
|
var ProfileCollection = require('../Profile/ProfileCollection');
|
||||||
var RootFolderCollection = require('./RootFolders/RootFolderCollection');
|
var RootFolderCollection = require('./RootFolders/RootFolderCollection');
|
||||||
|
var BulkImportView = require('./BulkImport/BulkImportView');
|
||||||
require('../Artist/ArtistCollection');
|
require('../Artist/ArtistCollection');
|
||||||
|
|
||||||
module.exports = Marionette.Layout.extend({
|
module.exports = Marionette.Layout.extend({
|
||||||
|
@ -17,6 +18,7 @@ module.exports = Marionette.Layout.extend({
|
||||||
|
|
||||||
events : {
|
events : {
|
||||||
'click .x-import' : '_importArtist',
|
'click .x-import' : '_importArtist',
|
||||||
|
'click .x-bulk-import' : '_bulkImportArtist',
|
||||||
'click .x-add-new' : '_addArtist'
|
'click .x-add-new' : '_addArtist'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -41,6 +43,11 @@ module.exports = Marionette.Layout.extend({
|
||||||
this.workspace.show(new ExistingArtistCollectionView({ model : options.model }));
|
this.workspace.show(new ExistingArtistCollectionView({ model : options.model }));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_bulkFolderSelected : function(options) {
|
||||||
|
vent.trigger(vent.Commands.CloseModalCommand);
|
||||||
|
this.workspace.show(new BulkImportView({ model : options.model}));
|
||||||
|
},
|
||||||
|
|
||||||
_importArtist : function() {
|
_importArtist : function() {
|
||||||
this.rootFolderLayout = new RootFolderLayout();
|
this.rootFolderLayout = new RootFolderLayout();
|
||||||
this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected);
|
this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected);
|
||||||
|
@ -49,5 +56,11 @@ module.exports = Marionette.Layout.extend({
|
||||||
|
|
||||||
_addArtist : function() {
|
_addArtist : function() {
|
||||||
this.workspace.show(new AddArtistView());
|
this.workspace.show(new AddArtistView());
|
||||||
|
},
|
||||||
|
|
||||||
|
_bulkImportArtist : function() {
|
||||||
|
this.bulkRootFolderLayout = new RootFolderLayout();
|
||||||
|
this.listenTo(this.bulkRootFolderLayout, 'folderSelected', this._bulkFolderSelected);
|
||||||
|
AppLayout.modalRegion.show(this.bulkRootFolderLayout);
|
||||||
}
|
}
|
||||||
});
|
});
|
|
@ -1,10 +1,11 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="btn-group add-artist-btn-group btn-group-lg btn-block">
|
<div class="btn-group add-artist-btn-group btn-group-lg btn-block">
|
||||||
<button type="button" class="btn btn-default col-md-10 col-xs-8 add-artist-import-btn x-import">
|
<button type="button" class="btn btn-default col-md-7 col-xs-4 add-artist-import-btn x-import">
|
||||||
<i class="icon-lidarr-hdd"/>
|
<i class="icon-lidarr-hdd"/>
|
||||||
Import existing artists on disk
|
Import existing artists on disk
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-default col-md-3 col-xs-4 x-bulk-import"><i class="icon-lidarr-view-list hidden-xs"></i> Bulk Import Artists</button>
|
||||||
<button class="btn btn-default col-md-2 col-xs-4 x-add-new"><i class="icon-lidarr-active hidden-xs"></i> Add New Artist</button>
|
<button class="btn btn-default col-md-2 col-xs-4 x-add-new"><i class="icon-lidarr-active hidden-xs"></i> Add New Artist</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
7
src/UI/AddArtist/BulkImport/ArtistPathCell.js
Normal file
7
src/UI/AddArtist/BulkImport/ArtistPathCell.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
var TemplatedCell = require('../../Cells/TemplatedCell');
|
||||||
|
|
||||||
|
module.exports = TemplatedCell.extend({
|
||||||
|
className : 'artist-title-cell',
|
||||||
|
template : 'AddArtist/BulkImport/ArtistPathTemplate',
|
||||||
|
|
||||||
|
});
|
1
src/UI/AddArtist/BulkImport/ArtistPathTemplate.hbs
Normal file
1
src/UI/AddArtist/BulkImport/ArtistPathTemplate.hbs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{{path}}<br>
|
21
src/UI/AddArtist/BulkImport/BulkImportArtistNameCell.js
Normal file
21
src/UI/AddArtist/BulkImport/BulkImportArtistNameCell.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||||
|
var BulkImportCollection = require('./BulkImportCollection');
|
||||||
|
|
||||||
|
module.exports = NzbDroneCell.extend({
|
||||||
|
className : 'artist-title-cell',
|
||||||
|
|
||||||
|
render : function() {
|
||||||
|
var collection = this.model.collection;
|
||||||
|
this.listenTo(collection, 'sync', this._renderCell);
|
||||||
|
|
||||||
|
this._renderCell();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_renderCell : function() {
|
||||||
|
this.$el.empty();
|
||||||
|
|
||||||
|
this.$el.html('<a href="https://www.musicbrainz.org/artist/' + this.cellValue.get('foreignArtistId') +'">' + this.cellValue.get('name') +'</a><br><span class="hint">' + this.cellValue.get('overview') + '</span>');
|
||||||
|
}
|
||||||
|
});
|
49
src/UI/AddArtist/BulkImport/BulkImportCollection.js
Normal file
49
src/UI/AddArtist/BulkImport/BulkImportCollection.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
var _ = require('underscore');
|
||||||
|
var PageableCollection = require('backbone.pageable');
|
||||||
|
var ArtistModel = require('../../Artist/ArtistModel');
|
||||||
|
var AsSortedCollection = require('../../Mixins/AsSortedCollection');
|
||||||
|
var AsPageableCollection = require('../../Mixins/AsPageableCollection');
|
||||||
|
var AsPersistedStateCollection = require('../../Mixins/AsPersistedStateCollection');
|
||||||
|
|
||||||
|
var BulkImportCollection = PageableCollection.extend({
|
||||||
|
url : window.NzbDrone.ApiRoot + '/artist/bulkimport',
|
||||||
|
model : ArtistModel,
|
||||||
|
tableName : 'bulkimport',
|
||||||
|
|
||||||
|
state : {
|
||||||
|
pageSize : 100000,
|
||||||
|
sortKey: 'sortName',
|
||||||
|
firstPage: 1
|
||||||
|
},
|
||||||
|
|
||||||
|
fetch : function(options) {
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
var data = options.data || {};
|
||||||
|
|
||||||
|
if (!data.id || !data.folder) {
|
||||||
|
data.id = this.folderId;
|
||||||
|
data.folder = this.folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.data = data;
|
||||||
|
return PageableCollection.prototype.fetch.call(this, options);
|
||||||
|
},
|
||||||
|
|
||||||
|
parseLinks : function(options) {
|
||||||
|
|
||||||
|
return {
|
||||||
|
first : this.url,
|
||||||
|
next: this.url,
|
||||||
|
last : this.url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
BulkImportCollection = AsSortedCollection.call(BulkImportCollection);
|
||||||
|
BulkImportCollection = AsPageableCollection.call(BulkImportCollection);
|
||||||
|
BulkImportCollection = AsPersistedStateCollection.call(BulkImportCollection);
|
||||||
|
|
||||||
|
module.exports = BulkImportCollection;
|
65
src/UI/AddArtist/BulkImport/BulkImportMonitorCell.js
Normal file
65
src/UI/AddArtist/BulkImport/BulkImportMonitorCell.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
var Backgrid = require('backgrid');
|
||||||
|
var Config = require('../../Config');
|
||||||
|
var _ = require('underscore');
|
||||||
|
var vent = require('vent');
|
||||||
|
var TemplatedCell = require('../../Cells/TemplatedCell');
|
||||||
|
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
|
||||||
|
module.exports = TemplatedCell.extend({
|
||||||
|
className : 'monitor-cell',
|
||||||
|
template : 'AddArtist/BulkImport/BulkImportMonitorCell',
|
||||||
|
|
||||||
|
_orig : TemplatedCell.prototype.initialize,
|
||||||
|
_origRender : TemplatedCell.prototype.initialize,
|
||||||
|
|
||||||
|
ui : {
|
||||||
|
monitor : '.x-monitor',
|
||||||
|
},
|
||||||
|
|
||||||
|
events: { 'change .x-monitor' : '_monitorChanged' },
|
||||||
|
|
||||||
|
initialize : function () {
|
||||||
|
this._orig.apply(this, arguments);
|
||||||
|
|
||||||
|
this.defaultMonitor = Config.getValue(Config.Keys.MonitorEpisodes, 'all');
|
||||||
|
|
||||||
|
this.model.set('monitored', this._convertMonitorToBool(this.defaultMonitor));
|
||||||
|
|
||||||
|
this.$el.find('.x-monitor').val(this._convertBooltoMonitor(this.model.get('monitored')));
|
||||||
|
},
|
||||||
|
|
||||||
|
_convertMonitorToBool : function(monitorString) {
|
||||||
|
return monitorString === 'all' ? true : false;
|
||||||
|
},
|
||||||
|
|
||||||
|
_convertBooltoMonitor : function(monitorBool) {
|
||||||
|
return monitorBool === true ? 'all' : 'none';
|
||||||
|
},
|
||||||
|
|
||||||
|
_monitorChanged : function() {
|
||||||
|
Config.setValue(Config.Keys.MonitorEpisodes, this.$el.find('.x-monitor').val());
|
||||||
|
this.defaultMonitor = this.$el.find('.x-monitor').val();
|
||||||
|
this.model.set('monitored', this._convertMonitorToBool(this.$el.find('.x-monitor').val()));
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function() {
|
||||||
|
var templateName = this.column.get('template') || this.template;
|
||||||
|
|
||||||
|
this.templateFunction = Marionette.TemplateCache.get(templateName);
|
||||||
|
this.$el.empty();
|
||||||
|
|
||||||
|
if (this.cellValue) {
|
||||||
|
var data = this.cellValue.toJSON();
|
||||||
|
var html = this.templateFunction(data);
|
||||||
|
this.$el.html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.delegateEvents();
|
||||||
|
|
||||||
|
this.$el.find('.x-monitor').val(this._convertBooltoMonitor(this.model.get('monitored')));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,4 @@
|
||||||
|
<select class="col-md-2 form-control x-monitor">
|
||||||
|
<option value="all">Yes</option>
|
||||||
|
<option value="none">No</option>
|
||||||
|
</select>
|
32
src/UI/AddArtist/BulkImport/BulkImportProfileCell.js
Normal file
32
src/UI/AddArtist/BulkImport/BulkImportProfileCell.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
var Backgrid = require('backgrid');
|
||||||
|
var ProfileCollection = require('../../Profile/ProfileCollection');
|
||||||
|
var Config = require('../../Config');
|
||||||
|
var _ = require('underscore');
|
||||||
|
|
||||||
|
module.exports = Backgrid.SelectCell.extend({
|
||||||
|
className : 'profile-cell',
|
||||||
|
|
||||||
|
_orig : Backgrid.SelectCell.prototype.initialize,
|
||||||
|
|
||||||
|
initialize : function () {
|
||||||
|
this._orig.apply(this, arguments);
|
||||||
|
|
||||||
|
this.defaultProfile = Config.getValue(Config.Keys.DefaultProfileId);
|
||||||
|
if(ProfileCollection.get(this.defaultProfile))
|
||||||
|
{
|
||||||
|
this.profile = this.defaultProfile;
|
||||||
|
} else {
|
||||||
|
this.profile = ProfileCollection.get(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
optionValues : function() {
|
||||||
|
return _.map(ProfileCollection.models, function(model){
|
||||||
|
return [model.get('name'), model.get('id')+""];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
77
src/UI/AddArtist/BulkImport/BulkImportProfileCellT.js
Normal file
77
src/UI/AddArtist/BulkImport/BulkImportProfileCellT.js
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
var Backgrid = require('backgrid');
|
||||||
|
var ProfileCollection = require('../../Profile/ProfileCollection');
|
||||||
|
var Config = require('../../Config');
|
||||||
|
var _ = require('underscore');
|
||||||
|
var vent = require('vent');
|
||||||
|
var TemplatedCell = require('../../Cells/TemplatedCell');
|
||||||
|
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
|
||||||
|
module.exports = TemplatedCell.extend({
|
||||||
|
className : 'profile-cell',
|
||||||
|
template : 'AddArtist/BulkImport/BulkImportProfileCell',
|
||||||
|
|
||||||
|
_orig : TemplatedCell.prototype.initialize,
|
||||||
|
_origRender : TemplatedCell.prototype.initialize,
|
||||||
|
|
||||||
|
ui : {
|
||||||
|
profile : '.x-profile',
|
||||||
|
},
|
||||||
|
|
||||||
|
events: { 'change .x-profile' : '_profileChanged' },
|
||||||
|
|
||||||
|
initialize : function () {
|
||||||
|
this._orig.apply(this, arguments);
|
||||||
|
|
||||||
|
this.listenTo(vent, Config.Events.ConfigUpdatedEvent, this._onConfigUpdated);
|
||||||
|
|
||||||
|
this.defaultProfile = Config.getValue(Config.Keys.DefaultProfileId);
|
||||||
|
|
||||||
|
this.profile = this.defaultProfile;
|
||||||
|
|
||||||
|
if(ProfileCollection.get(this.defaultProfile))
|
||||||
|
{
|
||||||
|
this.profile = this.defaultProfile;
|
||||||
|
this.model.set('profileId', this.defaultProfile);
|
||||||
|
} else {
|
||||||
|
this.profile = 1;
|
||||||
|
this.model.set('profileId', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$('.x-profile').val(this.model.get('profileId'));
|
||||||
|
|
||||||
|
this.cellValue = ProfileCollection;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
_profileChanged : function() {
|
||||||
|
Config.setValue(Config.Keys.DefaultProfileId, this.$('.x-profile').val());
|
||||||
|
this.model.set('profileId', this.$('.x-profile').val());
|
||||||
|
},
|
||||||
|
|
||||||
|
_onConfigUpdated : function(options) {
|
||||||
|
if (options.key === Config.Keys.DefaultProfileId) {
|
||||||
|
this.defaultProfile = options.value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function() {
|
||||||
|
var templateName = this.column.get('template') || this.template;
|
||||||
|
|
||||||
|
this.cellValue = ProfileCollection;
|
||||||
|
|
||||||
|
this.templateFunction = Marionette.TemplateCache.get(templateName);
|
||||||
|
this.$el.empty();
|
||||||
|
|
||||||
|
if (this.cellValue) {
|
||||||
|
var data = this.cellValue.toJSON();
|
||||||
|
var html = this.templateFunction(data);
|
||||||
|
this.$el.html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.delegateEvents();
|
||||||
|
this.$('.x-profile').val(this.model.get('profileId'));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,5 @@
|
||||||
|
<select class="col-md-2 form-control x-profile">
|
||||||
|
{{#each this}}
|
||||||
|
<option value="{{id}}">{{name}}</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
54
src/UI/AddArtist/BulkImport/BulkImportSelectAllCell.js
Normal file
54
src/UI/AddArtist/BulkImport/BulkImportSelectAllCell.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
var $ = require('jquery');
|
||||||
|
var _ = require('underscore');
|
||||||
|
var SelectAllCell = require('../../Cells/SelectAllCell');
|
||||||
|
var Backgrid = require('backgrid');
|
||||||
|
var FullArtistCollection = require('../../Artist/ArtistCollection');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = SelectAllCell.extend({
|
||||||
|
_originalRender : SelectAllCell.prototype.render,
|
||||||
|
|
||||||
|
_originalInit : SelectAllCell.prototype.initialize,
|
||||||
|
|
||||||
|
initialize : function() {
|
||||||
|
this._originalInit.apply(this, arguments);
|
||||||
|
|
||||||
|
this._refreshIsDuplicate();
|
||||||
|
|
||||||
|
this.listenTo(this.model, 'change', this._refresh);
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange : function(e) {
|
||||||
|
if(!this.isDuplicate) {
|
||||||
|
var checked = $(e.target).prop('checked');
|
||||||
|
this.$el.parent().toggleClass('selected', checked);
|
||||||
|
this.model.trigger('backgrid:selected', this.model, checked);
|
||||||
|
} else {
|
||||||
|
$(e.target).prop('checked', false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function() {
|
||||||
|
this._originalRender.apply(this, arguments);
|
||||||
|
|
||||||
|
this.$el.children(':first').prop('disabled', this.isDuplicate);
|
||||||
|
|
||||||
|
if (!this.isDuplicate) {
|
||||||
|
this.$el.children(':first').prop('checked', this.isChecked);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_refresh: function() {
|
||||||
|
this.isChecked = this.$el.children(':first').prop('checked');
|
||||||
|
this._refreshIsDuplicate();
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
_refreshIsDuplicate: function() {
|
||||||
|
var foreignArtistId = this.model.get('foreignArtistId');
|
||||||
|
var existingArtist = FullArtistCollection.where({ foreignArtistId: foreignArtistId });
|
||||||
|
this.isDuplicate = existingArtist.length > 0 ? true : false;
|
||||||
|
}
|
||||||
|
});
|
191
src/UI/AddArtist/BulkImport/BulkImportView.js
Normal file
191
src/UI/AddArtist/BulkImport/BulkImportView.js
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
var $ = require('jquery');
|
||||||
|
var _ = require('underscore');
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
var Backgrid = require('backgrid');
|
||||||
|
var ArtistNameCell = require('./BulkImportArtistNameCell');
|
||||||
|
var BulkImportCollection = require('./BulkImportCollection');
|
||||||
|
var ForeignIdCell = require('./ForeignIdCell');
|
||||||
|
var GridPager = require('../../Shared/Grid/Pager');
|
||||||
|
var SelectAllCell = require('./BulkImportSelectAllCell');
|
||||||
|
var ProfileCell = require('./BulkImportProfileCellT');
|
||||||
|
var MonitorCell = require('./BulkImportMonitorCell');
|
||||||
|
var ArtistPathCell = require('./ArtistPathCell');
|
||||||
|
var LoadingView = require('../../Shared/LoadingView');
|
||||||
|
var EmptyView = require('./EmptyView');
|
||||||
|
var ToolbarLayout = require('../../Shared/Toolbar/ToolbarLayout');
|
||||||
|
var CommandController = require('../../Commands/CommandController');
|
||||||
|
var Messenger = require('../../Shared/Messenger');
|
||||||
|
var ArtistCollection = require('../../Artist/ArtistCollection');
|
||||||
|
var ProfileCollection = require('../../Profile/ProfileCollection');
|
||||||
|
|
||||||
|
require('backgrid.selectall');
|
||||||
|
require('../../Mixins/backbone.signalr.mixin');
|
||||||
|
|
||||||
|
module.exports = Marionette.Layout.extend({
|
||||||
|
template : 'AddArtist/BulkImport/BulkImportViewTemplate',
|
||||||
|
|
||||||
|
regions : {
|
||||||
|
toolbar : '#x-toolbar',
|
||||||
|
table : '#x-artists-bulk',
|
||||||
|
},
|
||||||
|
|
||||||
|
ui : {
|
||||||
|
addSelectdBtn : '.x-add-selected'
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize : function(options) {
|
||||||
|
ProfileCollection.fetch();
|
||||||
|
this.bulkImportCollection = new BulkImportCollection().bindSignalR({ updateOnly : true });
|
||||||
|
this.model = options.model;
|
||||||
|
this.folder = this.model.get('path');
|
||||||
|
this.folderId = this.model.get('id');
|
||||||
|
this.bulkImportCollection.folderId = this.folderId;
|
||||||
|
this.bulkImportCollection.folder = this.folder;
|
||||||
|
this.bulkImportCollection.fetch();
|
||||||
|
this.listenTo(this.bulkImportCollection, {'sync' : this._showContent, 'error' : this._showContent, 'backgrid:selected' : this._select});
|
||||||
|
},
|
||||||
|
|
||||||
|
columns : [
|
||||||
|
{
|
||||||
|
name : '',
|
||||||
|
cell : SelectAllCell,
|
||||||
|
headerCell : 'select-all',
|
||||||
|
sortable : false,
|
||||||
|
cellValue : 'this'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'movie',
|
||||||
|
label : 'Artist',
|
||||||
|
cell : ArtistNameCell,
|
||||||
|
cellValue : 'this',
|
||||||
|
sortable : false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'path',
|
||||||
|
label : 'Path',
|
||||||
|
cell : ArtistPathCell,
|
||||||
|
cellValue : 'this',
|
||||||
|
sortable : false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'foreignArtistId',
|
||||||
|
label : 'MB Id',
|
||||||
|
cell : ForeignIdCell,
|
||||||
|
cellValue : 'this',
|
||||||
|
sortable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name :'monitor',
|
||||||
|
label: 'Monitor',
|
||||||
|
cell : MonitorCell,
|
||||||
|
cellValue : 'this',
|
||||||
|
sortable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name : 'profileId',
|
||||||
|
label : 'Profile',
|
||||||
|
cell : ProfileCell,
|
||||||
|
cellValue : 'this',
|
||||||
|
sortable: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
_showContent : function() {
|
||||||
|
this._showToolbar();
|
||||||
|
this._showTable();
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow : function() {
|
||||||
|
this.table.show(new LoadingView());
|
||||||
|
},
|
||||||
|
|
||||||
|
_showToolbar : function() {
|
||||||
|
var leftSideButtons = {
|
||||||
|
type : 'default',
|
||||||
|
storeState: false,
|
||||||
|
collapse : true,
|
||||||
|
items : [
|
||||||
|
{
|
||||||
|
title : 'Add Selected',
|
||||||
|
icon : 'icon-lidarr-add',
|
||||||
|
callback : this._addSelected,
|
||||||
|
ownerContext : this,
|
||||||
|
className : 'x-add-selected'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.toolbar.show(new ToolbarLayout({
|
||||||
|
left : [leftSideButtons],
|
||||||
|
right : [],
|
||||||
|
context : this
|
||||||
|
}));
|
||||||
|
|
||||||
|
$('#x-toolbar').addClass('inline');
|
||||||
|
},
|
||||||
|
|
||||||
|
_addSelected : function() {
|
||||||
|
var selected = _.filter(this.bulkImportCollection.models, function(elem){
|
||||||
|
return elem.selected;
|
||||||
|
});
|
||||||
|
|
||||||
|
var promise = ArtistCollection.importFromList(selected);
|
||||||
|
this.ui.addSelectdBtn.spinForPromise(promise);
|
||||||
|
this.ui.addSelectdBtn.addClass('disabled');
|
||||||
|
|
||||||
|
if (selected.length === 0) {
|
||||||
|
Messenger.show({
|
||||||
|
type : 'error',
|
||||||
|
message : 'No artists selected'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Messenger.show({
|
||||||
|
message : 'Importing {0} artists. This can take multiple minutes depending on how many artists should be imported. Don\'t close this browser window until it is finished!'.format(selected.length),
|
||||||
|
hideOnNavigate : false,
|
||||||
|
hideAfter : 30,
|
||||||
|
type : 'error'
|
||||||
|
});
|
||||||
|
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
promise.done(function() {
|
||||||
|
Messenger.show({
|
||||||
|
message : 'Imported artists from folder.',
|
||||||
|
hideAfter : 8,
|
||||||
|
hideOnNavigate : true
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
_.forEach(selected, function(artist) {
|
||||||
|
artist.destroy(); //update the collection without the added movies
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_handleEvent : function(eventName, data) {
|
||||||
|
if (eventName === 'sync' || eventName === 'content') {
|
||||||
|
this._showContent();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_select : function(model, selected) {
|
||||||
|
model.selected = selected;
|
||||||
|
},
|
||||||
|
|
||||||
|
_showTable : function() {
|
||||||
|
if (this.bulkImportCollection.length === 0) {
|
||||||
|
this.table.show(new EmptyView({ folder : this.folder }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.importGrid = new Backgrid.Grid({
|
||||||
|
columns : this.columns,
|
||||||
|
collection : this.bulkImportCollection,
|
||||||
|
className : 'table table-hover'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.table.show(this.importGrid);
|
||||||
|
}
|
||||||
|
});
|
13
src/UI/AddArtist/BulkImport/BulkImportViewTemplate.hbs
Normal file
13
src/UI/AddArtist/BulkImport/BulkImportViewTemplate.hbs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<div id="x-toolbar"/>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<span><b>Disabled artists are possible duplicates. If the match is incorrect, update the MB Id cell to import the proper artist.</b><span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id="x-artists-bulk" class="queue table-responsive"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
10
src/UI/AddArtist/BulkImport/EmptyView.js
Normal file
10
src/UI/AddArtist/BulkImport/EmptyView.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
var Marionette = require('marionette');
|
||||||
|
|
||||||
|
module.exports = Marionette.CompositeView.extend({
|
||||||
|
template : 'AddArtist/BulkImport/EmptyViewTemplate',
|
||||||
|
|
||||||
|
initialize : function (options) {
|
||||||
|
this.templateHelpers = {};
|
||||||
|
this.templateHelpers.folder = options.folder;
|
||||||
|
}
|
||||||
|
});
|
3
src/UI/AddArtist/BulkImport/EmptyViewTemplate.hbs
Normal file
3
src/UI/AddArtist/BulkImport/EmptyViewTemplate.hbs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="text-center hint col-md-12">
|
||||||
|
<span>No artists found in folder {{folder}}. Have you already added all of them?</span>
|
||||||
|
</div>
|
57
src/UI/AddArtist/BulkImport/ForeignIdCell.js
Normal file
57
src/UI/AddArtist/BulkImport/ForeignIdCell.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
var vent = require('vent');
|
||||||
|
var _ = require('underscore');
|
||||||
|
var $ = require('jquery');
|
||||||
|
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||||
|
var CommandController = require('../../Commands/CommandController');
|
||||||
|
|
||||||
|
module.exports = NzbDroneCell.extend({
|
||||||
|
className : 'foreignId-cell',
|
||||||
|
|
||||||
|
events : {
|
||||||
|
'blur input.foreignId-input' : '_updateId'
|
||||||
|
},
|
||||||
|
|
||||||
|
render : function() {
|
||||||
|
this.$el.empty();
|
||||||
|
|
||||||
|
this.$el.html('<i class="icon-lidarr-info hidden"></i><input type="text" class="x-foreignId foreignId-input form-control" value="' + this.cellValue.get('foreignArtistId') + '" />');
|
||||||
|
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateId : function() {
|
||||||
|
var field = this.$el.find('.x-foreignId');
|
||||||
|
var data = field.val();
|
||||||
|
|
||||||
|
var promise = $.ajax({
|
||||||
|
url : window.NzbDrone.ApiRoot + '/artist/lookup?term=lidarrid:' + data,
|
||||||
|
type : 'GET',
|
||||||
|
});
|
||||||
|
|
||||||
|
field.prop('disabled', true);
|
||||||
|
|
||||||
|
var icon = this.$('.icon-lidarr-info');
|
||||||
|
|
||||||
|
icon.removeClass('hidden');
|
||||||
|
|
||||||
|
icon.spinForPromise(promise);
|
||||||
|
var _self = this;
|
||||||
|
var cacheMonitored = this.model.get('monitored');
|
||||||
|
var cacheProfile = this.model.get('profileId');
|
||||||
|
var cachePath = this.model.get('path');
|
||||||
|
var cacheRoot = this.model.get('rootFolderPath');
|
||||||
|
|
||||||
|
promise.success(function(response) {
|
||||||
|
_self.model.set(response[0]);
|
||||||
|
_self.model.set('monitored', cacheMonitored);
|
||||||
|
_self.model.set('profileId', cacheProfile);
|
||||||
|
_self.model.set('path', cachePath);
|
||||||
|
field.prop('disabled', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
promise.error(function(request, status, error) {
|
||||||
|
console.error('Status: ' + status, 'Error: ' + error);
|
||||||
|
field.prop('disabled', false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -24,6 +24,27 @@ var Collection = PageableCollection.extend({
|
||||||
|
|
||||||
mode : 'client',
|
mode : 'client',
|
||||||
|
|
||||||
|
importFromList : function(models) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var proxy = _.extend(new Backbone.Model(), {
|
||||||
|
id : '',
|
||||||
|
|
||||||
|
url : self.url + '/import',
|
||||||
|
|
||||||
|
toJSON : function() {
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.listenTo(proxy, 'sync', function(proxyModel, models) {
|
||||||
|
this.add(models, { merge : true});
|
||||||
|
this.trigger('save', this);
|
||||||
|
});
|
||||||
|
|
||||||
|
return proxy.save();
|
||||||
|
},
|
||||||
|
|
||||||
save : function() {
|
save : function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue