using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using MySql.Data.MySqlClient; using Roadie.Library.Caching; using Roadie.Library.Configuration; using Roadie.Library.Data; using Roadie.Library.Encoding; using Roadie.Library.Enums; using Roadie.Library.Extensions; using Roadie.Library.Imaging; using Roadie.Library.MetaData.Audio; using Roadie.Library.Processors; using Roadie.Library.Utility; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; namespace Roadie.Library.Factories { #pragma warning disable EF1000 public sealed class ArtistFactory : FactoryBase { private List _addedArtistIds = new List(); private ReleaseFactory _releaseFactory = null; public IEnumerable AddedArtistIds { get { return this._addedArtistIds; } } private ReleaseFactory ReleaseFactory { get { return this._releaseFactory; } } public ArtistFactory(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context, ICacheManager cacheManager, ILogger logger, ReleaseFactory releaseFactory = null) : base(configuration, context, cacheManager, logger, httpEncoder) { this._releaseFactory = releaseFactory ?? new ReleaseFactory(configuration, httpEncoder, context, CacheManager, logger, null, this); } public async Task> Add(Artist artist) { SimpleContract.Requires(artist != null, "Invalid Artist"); try { var ArtistGenreTables = artist.Genres; var ArtistImages = artist.Images; var now = DateTime.UtcNow; artist.AlternateNames = artist.AlternateNames.AddToDelimitedList(new string[] { artist.Name.ToAlphanumericName() }); artist.Genres = null; artist.Images = null; if (artist.Thumbnail == null && ArtistImages != null) { // Set the thumbnail to the first image var firstImageWithNotNullBytes = ArtistImages.Where(x => x.Bytes != null).FirstOrDefault(); if (firstImageWithNotNullBytes != null) { artist.Thumbnail = firstImageWithNotNullBytes.Bytes; if (artist.Thumbnail != null) { artist.Thumbnail = ImageHelper.ResizeImage(artist.Thumbnail, this.Configuration.Thumbnails.Width, this.Configuration.Thumbnails.Height); artist.Thumbnail = ImageHelper.ConvertToJpegFormat(artist.Thumbnail); } } } if (!artist.IsValid) { return new OperationResult { Errors = new Exception[1] { new Exception("Artist is Invalid") } }; } var addArtistResult = this.DbContext.Artists.Add(artist); int inserted = 0; inserted = await this.DbContext.SaveChangesAsync(); this._addedArtistIds.Add(artist.Id); if (artist.Id < 1 && addArtistResult.Entity.Id > 0) { artist.Id = addArtistResult.Entity.Id; } if (inserted > 0 && artist.Id > 0) { if (ArtistGenreTables != null && ArtistGenreTables.Any(x => x.GenreId == null)) { string sql = null; try { foreach (var ArtistGenreTable in ArtistGenreTables) { var genre = this.DbContext.Genres.FirstOrDefault(x => x.Name.ToLower().Trim() == ArtistGenreTable.Genre.Name.ToLower().Trim()); if (genre == null) { genre = new Genre { Name = ArtistGenreTable.Genre.Name }; this.DbContext.Genres.Add(genre); await this.DbContext.SaveChangesAsync(); } if (genre != null && genre.Id > 0) { sql = string.Format("INSERT INTO `ArtistGenreTable` (ArtistId, genreId) VALUES ({0}, {1});", artist.Id, genre.Id); await this.DbContext.Database.ExecuteSqlCommandAsync(sql); } } } catch (Exception ex) { this._logger.LogError(ex, "Sql [" + sql + "] Exception [" + ex.Serialize() + "]"); } } if (ArtistImages != null && ArtistImages.Any(x => x.Status == Statuses.New)) { foreach (var ArtistImage in ArtistImages) { this.DbContext.Images.Add(new Image { ArtistId = artist.Id, Url = ArtistImage.Url, Signature = ArtistImage.Signature, Bytes = ArtistImage.Bytes }); } inserted = await this.DbContext.SaveChangesAsync(); } this.Logger.LogInformation("Added New Artist: [{0}]", artist.ToString()); } } catch (Exception ex) { this.Logger.LogError(ex, ex.Serialize()); } return new OperationResult { IsSuccess = artist.Id > 0, Data = artist }; } public async Task> Delete(Guid RoadieId) { var isSuccess = false; var Artist = this.DbContext.Artists.FirstOrDefault(x => x.RoadieId == RoadieId); if (Artist != null) { return await this.Delete(Artist); } return new OperationResult { Data = isSuccess }; } public async Task> Delete(Artist Artist) { var isSuccess = false; try { if (Artist != null) { this.DbContext.Artists.Remove(Artist); await this.DbContext.SaveChangesAsync(); this._cacheManager.ClearRegion(Artist.CacheRegion); this.Logger.LogInformation(string.Format("x DeleteArtist [{0}]", Artist.Id)); isSuccess = true; } } catch (Exception ex) { this.Logger.LogError(ex, ex.Serialize()); return new OperationResult { Errors = new Exception[1] { ex } }; } return new OperationResult { IsSuccess = isSuccess, Data = isSuccess }; } public OperationResult GetByExternalIds(string musicBrainzId = null, string iTunesId = null, string amgId = null, string spotifyId = null) { var sw = new Stopwatch(); sw.Start(); var Artist = (from a in this.DbContext.Artists where ((a.MusicBrainzId != null && (musicBrainzId != null && a.MusicBrainzId == musicBrainzId)) || (a.ITunesId != null || (iTunesId != null && a.ITunesId == iTunesId)) || (a.AmgId != null || (amgId != null && a.AmgId == amgId)) || (a.SpotifyId != null || (spotifyId != null && a.SpotifyId == spotifyId))) select a).FirstOrDefault(); sw.Stop(); if (Artist == null || !Artist.IsValid) { this._logger.LogTrace("ArtistFactory: Artist Not Found By External Ids: MusicbrainzId [{0}], iTunesIs [{1}], AmgId [{2}], SpotifyId [{3}]", musicBrainzId, iTunesId, amgId, spotifyId); } return new OperationResult { IsSuccess = Artist != null, OperationTime = sw.ElapsedMilliseconds, Data = Artist }; } public async Task> GetByName(AudioMetaData metaData, bool doFindIfNotInDatabase = false) { try { var sw = new Stopwatch(); sw.Start(); var ArtistName = metaData.Artist ?? metaData.TrackArtist; var cacheRegion = (new Artist { Name = ArtistName }).CacheRegion; var cacheKey = string.Format("urn:Artist_by_name:{0}", ArtistName); var resultInCache = this.CacheManager.Get(cacheKey, cacheRegion); if (resultInCache != null) { sw.Stop(); return new OperationResult { IsSuccess = true, OperationTime = sw.ElapsedMilliseconds, Data = resultInCache }; } var Artist = this.DatabaseQueryForArtistName(ArtistName); sw.Stop(); if (Artist == null || !Artist.IsValid) { this._logger.LogInformation("ArtistFactory: Artist Not Found By Name [{0}]", ArtistName); if (doFindIfNotInDatabase) { OperationResult ArtistSearch = null; try { ArtistSearch = await this.PerformMetaDataProvidersArtistSearch(metaData); } catch (Exception ex) { this.Logger.LogError(ex, ex.Serialize()); } if (ArtistSearch.IsSuccess) { Artist = ArtistSearch.Data; // See if Artist already exist with either Name or Sort Name var alreadyExists = this.DatabaseQueryForArtistName(ArtistSearch.Data.Name, ArtistSearch.Data.SortNameValue); if (alreadyExists == null || !alreadyExists.IsValid) { var addResult = await this.Add(Artist); if (!addResult.IsSuccess) { sw.Stop(); this.Logger.LogWarning("Unable To Add Artist For MetaData [{0}]", metaData.ToString()); return new OperationResult { OperationTime = sw.ElapsedMilliseconds, Errors = addResult.Errors }; } Artist = addResult.Data; } else { Artist = alreadyExists; } } } } if (Artist != null && Artist.IsValid) { this.CacheManager.Add(cacheKey, Artist); } return new OperationResult { IsSuccess = Artist != null, OperationTime = sw.ElapsedMilliseconds, Data = Artist }; } catch (Exception ex) { this.Logger.LogError(ex, ex.Serialize()); } return new OperationResult(); } /// /// Merge one Artist into another one /// /// The Artist to be merged /// The Artist to merge into /// public async Task> MergeArtists(Artist ArtistToMerge, Artist ArtistToMergeInto, bool doDbUpdates = false) { SimpleContract.Requires(ArtistToMerge != null, "Invalid Artist"); SimpleContract.Requires(ArtistToMergeInto != null, "Invalid Artist"); var result = false; var now = DateTime.UtcNow; var sw = new Stopwatch(); sw.Start(); ArtistToMergeInto.RealName = ArtistToMerge.RealName ?? ArtistToMergeInto.RealName; ArtistToMergeInto.MusicBrainzId = ArtistToMerge.MusicBrainzId ?? ArtistToMergeInto.MusicBrainzId; ArtistToMergeInto.ITunesId = ArtistToMerge.ITunesId ?? ArtistToMergeInto.ITunesId; ArtistToMergeInto.AmgId = ArtistToMerge.AmgId ?? ArtistToMergeInto.AmgId; ArtistToMergeInto.SpotifyId = ArtistToMerge.SpotifyId ?? ArtistToMergeInto.SpotifyId; ArtistToMergeInto.Thumbnail = ArtistToMerge.Thumbnail ?? ArtistToMergeInto.Thumbnail; ArtistToMergeInto.Profile = ArtistToMerge.Profile ?? ArtistToMergeInto.Profile; ArtistToMergeInto.BirthDate = ArtistToMerge.BirthDate ?? ArtistToMergeInto.BirthDate; ArtistToMergeInto.BeginDate = ArtistToMerge.BeginDate ?? ArtistToMergeInto.BeginDate; ArtistToMergeInto.EndDate = ArtistToMerge.EndDate ?? ArtistToMergeInto.EndDate; if (!string.IsNullOrEmpty(ArtistToMerge.ArtistType) && !ArtistToMerge.ArtistType.Equals("Other", StringComparison.OrdinalIgnoreCase)) { ArtistToMergeInto.ArtistType = ArtistToMerge.ArtistType; } ArtistToMergeInto.BioContext = ArtistToMerge.BioContext ?? ArtistToMergeInto.BioContext; ArtistToMergeInto.DiscogsId = ArtistToMerge.DiscogsId ?? ArtistToMergeInto.DiscogsId; ArtistToMergeInto.Tags = ArtistToMergeInto.Tags.AddToDelimitedList(ArtistToMerge.Tags.ToListFromDelimited()); var altNames = ArtistToMerge.AlternateNames.ToListFromDelimited().ToList(); altNames.Add(ArtistToMerge.Name); altNames.Add(ArtistToMerge.SortName); ArtistToMergeInto.AlternateNames = ArtistToMergeInto.AlternateNames.AddToDelimitedList(altNames); ArtistToMergeInto.URLs = ArtistToMergeInto.URLs.AddToDelimitedList(ArtistToMerge.URLs.ToListFromDelimited()); ArtistToMergeInto.ISNIList = ArtistToMergeInto.ISNIList.AddToDelimitedList(ArtistToMerge.ISNIList.ToListFromDelimited()); ArtistToMergeInto.LastUpdated = now; if (doDbUpdates) { string sql = null; sql = "UPDATE `ArtistGenreTable` set ArtistId = " + ArtistToMergeInto.Id + " WHERE ArtistId = " + ArtistToMerge.Id + ";"; await this.DbContext.Database.ExecuteSqlCommandAsync(sql); sql = "UPDATE `image` set ArtistId = " + ArtistToMergeInto.Id + " WHERE ArtistId = " + ArtistToMerge.Id + ";"; await this.DbContext.Database.ExecuteSqlCommandAsync(sql); sql = "UPDATE `userArtist` set ArtistId = " + ArtistToMergeInto.Id + " WHERE ArtistId = " + ArtistToMerge.Id + ";"; await this.DbContext.Database.ExecuteSqlCommandAsync(sql); sql = "UPDATE `track` set ArtistId = " + ArtistToMergeInto.Id + " WHERE ArtistId = " + ArtistToMerge.Id + ";"; await this.DbContext.Database.ExecuteSqlCommandAsync(sql); try { sql = "UPDATE `release` set ArtistId = " + ArtistToMergeInto.Id + " WHERE ArtistId = " + ArtistToMerge.Id + ";"; await this.DbContext.Database.ExecuteSqlCommandAsync(sql); } catch (Exception ex) { this._logger.LogWarning(ex.ToString()); } var artistFolder = ArtistToMerge.ArtistFileFolder(this.Configuration, this.Configuration.LibraryFolder); foreach (var release in this.DbContext.Releases.Include("Artist").Where(x => x.ArtistId == ArtistToMerge.Id).ToArray()) { var originalReleaseFolder = release.ReleaseFileFolder(artistFolder); await this.ReleaseFactory.Update(release, null, originalReleaseFolder); } await this.Delete(ArtistToMerge); } result = true; sw.Stop(); return new OperationResult { Data = ArtistToMergeInto, IsSuccess = result, OperationTime = sw.ElapsedMilliseconds }; } public async Task> PerformMetaDataProvidersArtistSearch(AudioMetaData metaData) { SimpleContract.Requires(metaData != null, "Invalid MetaData"); SimpleContract.Requires(!string.IsNullOrEmpty(metaData.Artist), "Invalid MetaData, Missing Artist"); var sw = new Stopwatch(); sw.Start(); var result = new Artist { Name = metaData.Artist.ToTitleCase(false) }; var resultsExceptions = new List(); var ArtistGenres = new List(); var ArtistImageUrls = new List(); var ArtistName = metaData.Artist; try { if (this.ITunesArtistSearchEngine.IsEnabled) { var iTunesResult = await this.ITunesArtistSearchEngine.PerformArtistSearch(ArtistName, 1); if (iTunesResult.IsSuccess) { var i = iTunesResult.Data.First(); if (i.AlternateNames != null) { result.AlternateNames = result.AlternateNames.AddToDelimitedList(i.AlternateNames); } if (i.Tags != null) { result.Tags = result.Tags.AddToDelimitedList(i.Tags); } if (i.Urls != null) { result.URLs = result.URLs.AddToDelimitedList(i.Urls); } if (i.ISNIs != null) { result.ISNIList = result.ISNIList.AddToDelimitedList(i.ISNIs); } if (i.ImageUrls != null) { ArtistImageUrls.AddRange(i.ImageUrls); } if (i.ArtistGenres != null) { ArtistGenres.AddRange(i.ArtistGenres); } result.CopyTo(new Artist { EndDate = i.EndDate, BioContext = i.Bio, Profile = i.Profile, ITunesId = i.iTunesId, BeginDate = i.BeginDate, Name = result.Name ?? i.ArtistName, SortName = result.SortName ?? i.ArtistSortName, Thumbnail = i.ArtistThumbnailUrl != null ? WebHelper.BytesForImageUrl(i.ArtistThumbnailUrl) : null, ArtistType = result.ArtistType ?? i.ArtistType }); } if (iTunesResult.Errors != null) { resultsExceptions.AddRange(iTunesResult.Errors); } } } catch (Exception ex) { this.Logger.LogError(ex, "iTunesArtistSearch: " + ex.Serialize()); } try { if (this.MusicBrainzArtistSearchEngine.IsEnabled) { var mbResult = await this.MusicBrainzArtistSearchEngine.PerformArtistSearch(result.Name, 1); if (mbResult.IsSuccess) { var mb = mbResult.Data.First(); if (mb.AlternateNames != null) { result.AlternateNames = result.AlternateNames.AddToDelimitedList(mb.AlternateNames); } if (mb.Tags != null) { result.Tags = result.Tags.AddToDelimitedList(mb.Tags); } if (mb.Urls != null) { result.URLs = result.URLs.AddToDelimitedList(mb.Urls); } if (mb.ISNIs != null) { result.ISNIList = result.ISNIList.AddToDelimitedList(mb.ISNIs); } if (mb.ImageUrls != null) { ArtistImageUrls.AddRange(mb.ImageUrls); } if (mb.ArtistGenres != null) { ArtistGenres.AddRange(mb.ArtistGenres); } if (!string.IsNullOrEmpty(mb.ArtistName) && !mb.ArtistName.Equals(result.Name, StringComparison.OrdinalIgnoreCase)) { result.AlternateNames.AddToDelimitedList(new string[] { mb.ArtistName }); } result.CopyTo(new Artist { EndDate = mb.EndDate, BioContext = mb.Bio, Profile = mb.Profile, MusicBrainzId = mb.MusicBrainzId, BeginDate = mb.BeginDate, Name = result.Name ?? mb.ArtistName, SortName = result.SortName ?? mb.ArtistSortName, Thumbnail = mb.ArtistThumbnailUrl != null ? WebHelper.BytesForImageUrl(mb.ArtistThumbnailUrl) : null, ArtistType = mb.ArtistType }); } if (mbResult.Errors != null) { resultsExceptions.AddRange(mbResult.Errors); } } } catch (Exception ex) { this.Logger.LogError(ex, "MusicBrainzArtistSearch: " + ex.Serialize()); } try { if (this.LastFmArtistSearchEngine.IsEnabled) { var lastFmResult = await this.LastFmArtistSearchEngine.PerformArtistSearch(result.Name, 1); if (lastFmResult.IsSuccess) { var l = lastFmResult.Data.First(); if (l.AlternateNames != null) { result.AlternateNames = result.AlternateNames.AddToDelimitedList(l.AlternateNames); } if (l.Tags != null) { result.Tags = result.Tags.AddToDelimitedList(l.Tags); } if (l.Urls != null) { result.URLs = result.URLs.AddToDelimitedList(l.Urls); } if (l.ISNIs != null) { result.ISNIList = result.ISNIList.AddToDelimitedList(l.ISNIs); } if (l.ImageUrls != null) { ArtistImageUrls.AddRange(l.ImageUrls); } if (l.ArtistGenres != null) { ArtistGenres.AddRange(l.ArtistGenres); } if (!string.IsNullOrEmpty(l.ArtistName) && !l.ArtistName.Equals(result.Name, StringComparison.OrdinalIgnoreCase)) { result.AlternateNames.AddToDelimitedList(new string[] { l.ArtistName }); } result.CopyTo(new Artist { EndDate = l.EndDate, BioContext = this.HttpEncoder.HtmlEncode(l.Bio), Profile = this.HttpEncoder.HtmlEncode(l.Profile), MusicBrainzId = l.MusicBrainzId, BeginDate = l.BeginDate, Name = result.Name ?? l.ArtistName, SortName = result.SortName ?? l.ArtistSortName, Thumbnail = l.ArtistThumbnailUrl != null ? WebHelper.BytesForImageUrl(l.ArtistThumbnailUrl) : null, ArtistType = result.ArtistType ?? l.ArtistType }); } if (lastFmResult.Errors != null) { resultsExceptions.AddRange(lastFmResult.Errors); } } } catch (Exception ex) { this.Logger.LogError(ex, "LastFMArtistSearch: " + ex.Serialize()); } try { if (this.SpotifyArtistSearchEngine.IsEnabled) { var spotifyResult = await this.SpotifyArtistSearchEngine.PerformArtistSearch(result.Name, 1); if (spotifyResult.IsSuccess) { var s = spotifyResult.Data.First(); if (s.Tags != null) { result.Tags = result.Tags.AddToDelimitedList(s.Tags); } if (s.Urls != null) { result.URLs = result.URLs.AddToDelimitedList(s.Urls); } if (s.ImageUrls != null) { ArtistImageUrls.AddRange(s.ImageUrls); } if (s.ArtistGenres != null) { ArtistGenres.AddRange(s.ArtistGenres); } if (!string.IsNullOrEmpty(s.ArtistName) && !s.ArtistName.Equals(result.Name, StringComparison.OrdinalIgnoreCase)) { result.AlternateNames.AddToDelimitedList(new string[] { s.ArtistName }); } result.CopyTo(new Artist { EndDate = s.EndDate, BioContext = s.Bio, Profile = this.HttpEncoder.HtmlEncode(s.Profile), MusicBrainzId = s.MusicBrainzId, BeginDate = s.BeginDate, Name = result.Name ?? s.ArtistName, SortName = result.SortName ?? s.ArtistSortName, Thumbnail = s.ArtistThumbnailUrl != null ? WebHelper.BytesForImageUrl(s.ArtistThumbnailUrl) : null, ArtistType = result.ArtistType ?? s.ArtistType }); } if (spotifyResult.Errors != null) { resultsExceptions.AddRange(spotifyResult.Errors); } } } catch (Exception ex) { this.Logger.LogError(ex, "SpotifyArtistSearch: " + ex.Serialize()); } try { if (this.DiscogsArtistSearchEngine.IsEnabled) { var discogsResult = await this.DiscogsArtistSearchEngine.PerformArtistSearch(result.Name, 1); if (discogsResult.IsSuccess) { var d = discogsResult.Data.First(); if (d.Urls != null) { result.URLs = result.URLs.AddToDelimitedList(d.Urls); } if (d.ImageUrls != null) { ArtistImageUrls.AddRange(d.ImageUrls); } if (d.AlternateNames != null) { result.AlternateNames = result.AlternateNames.AddToDelimitedList(d.AlternateNames); } if (!string.IsNullOrEmpty(d.ArtistName) && !d.ArtistName.Equals(result.Name, StringComparison.OrdinalIgnoreCase)) { result.AlternateNames.AddToDelimitedList(new string[] { d.ArtistName }); } result.CopyTo(new Artist { Profile = this.HttpEncoder.HtmlEncode(d.Profile), DiscogsId = d.DiscogsId, Name = result.Name ?? d.ArtistName, RealName = result.RealName ?? d.ArtistRealName, Thumbnail = d.ArtistThumbnailUrl != null ? WebHelper.BytesForImageUrl(d.ArtistThumbnailUrl) : null, ArtistType = result.ArtistType ?? d.ArtistType }); } if (discogsResult.Errors != null) { resultsExceptions.AddRange(discogsResult.Errors); } } } catch (Exception ex) { this.Logger.LogError(ex, "DiscogsArtistSearch: " + ex.Serialize()); } try { if (this.WikipediaArtistSearchEngine.IsEnabled) { var wikiName = result.Name; // Help get better results for bands with proper nouns (e.g. "Poison") if (!result.ArtistType.Equals("Person", StringComparison.OrdinalIgnoreCase)) { wikiName = wikiName + " band"; } var wikipediaResult = await this.WikipediaArtistSearchEngine.PerformArtistSearch(wikiName, 1); if (wikipediaResult != null) { if (wikipediaResult.IsSuccess) { var w = wikipediaResult.Data.First(); result.CopyTo(new Artist { BioContext = this.HttpEncoder.HtmlEncode(w.Bio) }); } if (wikipediaResult.Errors != null) { resultsExceptions.AddRange(wikipediaResult.Errors); } } } } catch (Exception ex) { this.Logger.LogError(ex, "WikipediaArtistSearch: " + ex.Serialize()); } try { if (result.AlternateNames != null) { result.AlternateNames = string.Join("|", result.AlternateNames.ToListFromDelimited().Distinct().OrderBy(x => x)); } if (result.URLs != null) { result.URLs = string.Join("|", result.URLs.ToListFromDelimited().Distinct().OrderBy(x => x)); } if (result.Tags != null) { result.Tags = string.Join("|", result.Tags.ToListFromDelimited().Distinct().OrderBy(x => x)); } if (ArtistGenres.Any()) { var genreInfos = (from ag in ArtistGenres join g in this.DbContext.Genres on ag equals g.Name into gg from g in gg.DefaultIfEmpty() select new { newGenre = ag.ToTitleCase(), existingGenre = g }); result.Genres = new List(); foreach (var genreInfo in genreInfos) { result.Genres.Add(new ArtistGenre { Genre = genreInfo.existingGenre != null ? genreInfo.existingGenre : new Genre { Name = genreInfo.newGenre } }); } } if (ArtistImageUrls.Any()) { var imageBag = new ConcurrentBag(); var i = ArtistImageUrls.Select(async url => { imageBag.Add(await WebHelper.GetImageFromUrlAsync(url)); }); await Task.WhenAll(i); result.Images = imageBag.Where(x => x != null && x.Bytes != null).GroupBy(x => x.Signature).Select(x => x.First()).Take(this.Configuration.Processing.MaximumArtistImagesToAdd).ToList(); if (result.Thumbnail == null && result.Images != null) { result.Thumbnail = result.Images.First().Bytes; } } if (result.Thumbnail != null) { result.Thumbnail = ImageHelper.ResizeImage(result.Thumbnail, this.Configuration.Thumbnails.Width, this.Configuration.Thumbnails.Height); result.Thumbnail = ImageHelper.ConvertToJpegFormat(result.Thumbnail); } } catch (Exception ex) { this.Logger.LogError(ex, "CombiningResults: " + ex.Serialize()); } result.SortName = result.SortName.ToTitleCase(); if (!string.IsNullOrEmpty(result.ArtistType)) { switch (result.ArtistType.ToLower().Replace('-', ' ')) { case "Artist": case "one man band": case "one woman band": case "solo": case "person": result.ArtistType = "Person"; break; case "band": case "big band": case "duet": case "jug band": case "quartet": case "quartette": case "sextet": case "trio": case "group": result.ArtistType = "Group"; break; case "orchestra": result.ArtistType = "Orchestra"; break; case "choir band": case "choir": result.ArtistType = "Choir"; break; case "movie part": case "movie role": case "role": case "character": result.ArtistType = "Character"; break; default: this.Logger.LogWarning(string.Format("Unknown Artist Type [{0}]", result.ArtistType)); result.ArtistType = "Other"; break; } } sw.Stop(); return new OperationResult { Data = result, IsSuccess = result != null, Errors = resultsExceptions, OperationTime = sw.ElapsedMilliseconds }; } /// /// Perform a Metadata Provider search and then merge the results into the given Artist /// /// Given Artist RoadieId /// Operation Result public async Task> RefreshArtistMetadata(Guid ArtistId) { SimpleContract.Requires(ArtistId != Guid.Empty, "Invalid ArtistId"); var result = true; var resultErrors = new List(); var sw = new Stopwatch(); sw.Start(); try { var Artist = this.DbContext.Artists.FirstOrDefault(x => x.RoadieId == ArtistId); if (Artist == null) { this.Logger.LogWarning("Unable To Find Artist [{0}]", ArtistId); return new OperationResult(); } OperationResult ArtistSearch = null; try { ArtistSearch = await this.PerformMetaDataProvidersArtistSearch(new AudioMetaData { Artist = Artist.Name }); } catch (Exception ex) { this.Logger.LogError(ex, ex.Serialize()); } if (ArtistSearch.IsSuccess) { // Do metadata search for Artist like if new Artist then set some overides and merge var mergeResult = await this.MergeArtists(ArtistSearch.Data, Artist); if (mergeResult.IsSuccess) { Artist = mergeResult.Data; await this.DbContext.SaveChangesAsync(); sw.Stop(); this.CacheManager.ClearRegion(Artist.CacheRegion); this.Logger.LogInformation("Scanned RefreshArtistMetadata [{0}], OperationTime [{1}]", Artist.ToString(), sw.ElapsedMilliseconds); } else { sw.Stop(); } } } catch (Exception ex) { this.Logger.LogError(ex, ex.Serialize()); resultErrors.Add(ex); } return new OperationResult { Data = result, IsSuccess = result, Errors = resultErrors, OperationTime = sw.ElapsedMilliseconds }; } public async Task> ScanArtistReleasesFolders(Guid artistId, string destinationFolder, bool doJustInfo) { SimpleContract.Requires(artistId == Guid.Empty, "Invalid ArtistId"); var result = true; var resultErrors = new List(); var sw = new Stopwatch(); sw.Start(); try { var Artist = this.DbContext.Artists.Include("releases").FirstOrDefault(x => x.RoadieId == artistId); if (Artist == null) { this.Logger.LogWarning("Unable To Find Artist [{0}]", artistId); return new OperationResult(); } var releaseScannedCount = 0; var ArtistFolder = Artist.ArtistFileFolder(this.Configuration, destinationFolder); var scannedArtistFolders = new List(); // Scan known releases for changes if (Artist.Releases != null) { foreach (var release in Artist.Releases) { try { result = result && (await this.ReleaseFactory.ScanReleaseFolder(Guid.Empty, destinationFolder, doJustInfo, release)).Data; releaseScannedCount++; scannedArtistFolders.Add(release.ReleaseFileFolder(ArtistFolder)); } catch (Exception ex) { this.Logger.LogError(ex, ex.Serialize()); } } } // Any folder found in Artist folder not already scanned scan var folderProcessor = new FolderProcessor(this.Configuration, this.HttpEncoder, destinationFolder, this.DbContext, this.CacheManager, this.Logger); var nonReleaseFolders = (from d in Directory.EnumerateDirectories(ArtistFolder) where !(from r in scannedArtistFolders select r).Contains(d) orderby d select d); foreach (var folder in nonReleaseFolders) { await folderProcessor.Process(new DirectoryInfo(folder), doJustInfo); } if (!doJustInfo) { folderProcessor.DeleteEmptyFolders(new DirectoryInfo(ArtistFolder)); } sw.Stop(); this.CacheManager.ClearRegion(Artist.CacheRegion); this.Logger.LogInformation("Scanned Artist [{0}], Releases Scanned [{1}], OperationTime [{2}]", Artist.ToString(), releaseScannedCount, sw.ElapsedMilliseconds); } catch (Exception ex) { this.Logger.LogError(ex, ex.Serialize()); resultErrors.Add(ex); } return new OperationResult { Data = result, IsSuccess = result, Errors = resultErrors, OperationTime = sw.ElapsedMilliseconds }; } public async Task> Update(Artist Artist, IEnumerable ArtistImages, string destinationFolder = null) { SimpleContract.Requires(Artist != null, "Invalid Artist"); var sw = new Stopwatch(); sw.Start(); var artistGenreTables = Artist.Genres.Select(x => new ArtistGenre { ArtistId = Artist.Id, GenreId = x.GenreId }).ToList(); var artistAssociatedWith = Artist.AssociatedArtists.Select(x => new ArtistAssociation { ArtistId = Artist.Id, AssociatedArtistId = x.AssociatedArtistId }).ToList(); var result = true; var now = DateTime.UtcNow; var originalArtistFolder = Artist.ArtistFileFolder(this.Configuration, destinationFolder ?? this.Configuration.LibraryFolder); var originalName = Artist.Name; var originalSortName = Artist.SortName; Artist.LastUpdated = now; await this.DbContext.SaveChangesAsync(); this.DbContext.ArtistGenres.RemoveRange((from at in this.DbContext.ArtistGenres where at.ArtistId == Artist.Id select at)); Artist.Genres = artistGenreTables; this.DbContext.ArtistAssociations.RemoveRange((from at in this.DbContext.ArtistAssociations where at.ArtistId == Artist.Id select at)); Artist.AssociatedArtists = artistAssociatedWith; await this.DbContext.SaveChangesAsync(); var existingImageIds = (from ai in ArtistImages where ai.Status != Statuses.New select ai.RoadieId).ToArray(); this.DbContext.Images.RemoveRange((from i in this.DbContext.Images where i.ArtistId == Artist.Id where !(from x in existingImageIds select x).Contains(i.RoadieId) select i)); await this.DbContext.SaveChangesAsync(); if (ArtistImages != null && ArtistImages.Any(x => x.Status == Statuses.New)) { foreach (var ArtistImage in ArtistImages.Where(x => x.Status == Statuses.New)) { this.DbContext.Images.Add(ArtistImage); } try { await this.DbContext.SaveChangesAsync(); } catch (Exception ex) { this.Logger.LogError(ex, ex.Serialize()); } } var newArtistFolder = Artist.ArtistFileFolder(this.Configuration, destinationFolder ?? this.Configuration.LibraryFolder); if (!originalArtistFolder.Equals(newArtistFolder, StringComparison.OrdinalIgnoreCase)) { this.Logger.LogTrace("Moving Artist From Folder [{0}] To [{1}]", originalArtistFolder, newArtistFolder); // Directory.Move(originalArtistFolder, Artist.ArtistFileFolder(destinationFolder ?? SettingsHelper.Instance.LibraryFolder)); // TODO if name changed then update Artist track files to have new Artist name } this._cacheManager.ClearRegion(Artist.CacheRegion); sw.Stop(); return new OperationResult { Data = Artist, IsSuccess = result, OperationTime = sw.ElapsedMilliseconds }; } private Artist DatabaseQueryForArtistName(string name, string sortName = null) { if (string.IsNullOrEmpty(name)) { return null; } try { var getParams = new List(); var searchName = name.NormalizeName().ToLower(); var searchSortName = !string.IsNullOrEmpty(sortName) ? sortName.NormalizeName().ToLower() : searchName; var specialSearchName = name.ToAlphanumericName(); getParams.Add(new MySqlParameter("@isName", searchName)); getParams.Add(new MySqlParameter("@isSortName", searchSortName)); getParams.Add(new MySqlParameter("@startAlt", string.Format("{0}|%", searchName))); getParams.Add(new MySqlParameter("@inAlt", string.Format("%|{0}|%", searchName))); getParams.Add(new MySqlParameter("@endAlt", string.Format("%|{0}", searchName))); getParams.Add(new MySqlParameter("@sstartAlt", string.Format("{0}|%", specialSearchName))); getParams.Add(new MySqlParameter("@sinAlt", string.Format("%|{0}|%", specialSearchName))); getParams.Add(new MySqlParameter("@sendAlt", string.Format("%|{0}", specialSearchName))); return this.DbContext.Artists.FromSql(@"SELECT * FROM `Artist` WHERE LCASE(name) = @isName OR LCASE(sortName) = @isName OR LCASE(sortName) = @isSortName OR LCASE(alternatenames) = @isName OR alternatenames like @startAlt OR alternatenames like @sstartAlt OR alternatenames like @inAlt OR alternatenames like @sinAlt OR (alternatenames like @endAlt OR alternatenames like @sendAlt) LIMIT 1;", getParams.ToArray()).FirstOrDefault(); } catch (Exception ex) { this.Logger.LogError(ex, ex.Serialize()); } return null; } } }